##############################################################################
# Braviz, Brain Data interactive visualization #
# Copyright (C) 2014 Diego Angulo #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU Lesser General Public License as #
# published by the Free Software Foundation, either version 3 of the #
# License, or (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public License#
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
##############################################################################
from __future__ import division, print_function
import cPickle
from functools import wraps
from itertools import izip
import itertools
import logging
import matplotlib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
import pandas as pd
import seaborn as sns
from PyQt4 import QtCore
from PyQt4 import QtGui
from braviz.readAndFilter import tabular_data as braviz_tab_data, user_data as braviz_user_data, config_file
from braviz.readAndFilter.tabular_data import get_var_name, is_variable_nominal, get_labels_dict, \
get_data_frame_by_index, get_maximum_value, get_min_max_values
__author__ = 'Diego'
[docs]class RotatedLabel(QtGui.QLabel):
"""
A vertical label useful for labeling rows of data
Args:
parent (QObject) : Qt Parent
"""
def __init__(self, parent):
super(RotatedLabel, self).__init__(parent)
self.color = (255, 0, 0)
[docs] def set_color(self, color):
"""
Sets the color of the label
Args:
color (tuple): a 3-tuple with values in [0,1]
"""
if color is not None:
color = [c * 256 for c in color]
self.color = color
else:
self.color = (0, 0, 0)
def paintEvent(self, QPaintEvent):
color = self.color
painter = QtGui.QPainter(self)
painter.save()
painter.setPen(QtCore.Qt.black)
text = self.text()
font = painter.font()
font.setPointSize(12)
painter.setFont(font)
fm = QtGui.QFontMetrics(painter.font())
# print "g:",self.rect()
# print "t:",fm.boundingRect(text)
g = self.rect()
x = g.width() / 2 + (fm.ascent() / 2)
# -10 is for the square
y = g.height() / 2 + fm.width(text) / 2 - 15
# print "x:",x
painter.translate(x, y)
painter.rotate(270)
qcolor = QtGui.QColor.fromRgb(*color)
painter.fillRect(
QtCore.QRect(-1 * fm.height() - 10, -1 * fm.ascent() + 2, 20, 20), qcolor)
painter.drawText(QtCore.QPoint(0, 0), text)
painter.restore()
[docs]class ListValidator(QtGui.QValidator):
"""
Can be applied to :obj:`QLineEdit` so that it will only accept input from a list of possible values.
Can be used together with :obj:`QCompleter`
Args:
valid_options (set) : Set of valid strings to accept as input
"""
def __init__(self, valid_options):
super(ListValidator, self).__init__()
self.valid = frozenset(valid_options)
def validate(self, QString, p_int):
str_value = QString
if str_value in self.valid:
return QtGui.QValidator.Acceptable, QString, p_int
else:
if len(str_value) == 0:
return QtGui.QValidator.Intermediate, QString, p_int
try:
_ = int(str_value)
except Exception:
return QtGui.QValidator.Invalid, QString, p_int
else:
return QtGui.QValidator.Intermediate, QString, p_int
def repeatatable_plot(func):
@wraps(func)
def saved_plot_func(*args, **kwargs):
self = args[0]
self.last_plot_function = func
self.last_plot_arguments = args
self.last_plot_kw_arguments = kwargs
return func(*args, **kwargs)
return saved_plot_func
class MatplotWidget(FigureCanvas):
box_outlier_pick_signal = QtCore.pyqtSignal(str, tuple)
scatter_pick_signal = QtCore.pyqtSignal(str, tuple)
# TODO: instead of using blit create a @wrapper to save last render command to restore after drawing subjects
# TODO: Unify with MatplotWidget in visualization
def __init__(self, parent=None, dpi=100, initial_message=None):
fig = Figure(figsize=(5, 5), dpi=dpi, tight_layout=True)
self.fig = fig
self.axes = fig.add_subplot(111)
self.axes2 = None
# self.axes.hold(False)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(
self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.updateGeometry()
palette = self.palette()
self.setContentsMargins(0, 0, 0, 0)
fig.set_facecolor(palette.background().color().getRgbF()[0:3])
self.initial_text(initial_message)
self.back_fig = self.copy_from_bbox(self.axes.bbox)
self.xlim = self.axes.get_xlim()
self.ylim = self.axes.get_ylim()
# self.mpl_connect("button_press_event",self.generate_tooltip_event)
self.mpl_connect("pick_event", self.generate_tooltip_event)
self.setMouseTracking(True)
self.mpl_connect('motion_notify_event', self.mouse_move_event_handler)
self.x_order = None
self.x_order_i = None
self.fliers_x_dict = None
self.last_plot_function = None
self.last_plot_arguments = None
self.last_plot_kw_arguments = None
self.limits_vertical = True
def initial_text(self, message):
if message is None:
message = "Welcome"
sns.set_style("dark")
self.fig.clear()
self.axes = self.fig.add_subplot(111)
self.axes.text(0.5, 0.5, message, horizontalalignment='center',
verticalalignment='center', fontsize=12)
# Remove tick marks
self.axes.tick_params(
'y', left='off', right='off', labelleft='off', labelright='off')
self.axes.tick_params(
'x', top='off', bottom='off', labelbottom='off', labeltop='off')
# Remove axes border
for child in self.axes.get_children():
if isinstance(child, matplotlib.spines.Spine):
child.set_visible(False)
# remove minor tick lines
for line in self.axes.xaxis.get_ticklines(minor=True) + self.axes.yaxis.get_ticklines(minor=True):
line.set_markersize(0)
self.draw()
self.x_order = None
@repeatatable_plot
def compute_scatter(self, data, data2=None, x_lab=None, y_lab=None, colors=None, labels=None, urls=None,
xlims=None):
sns.set_style("darkgrid")
log = logging.getLogger(__name__)
self.fig.clear()
if len(data) == 0:
log.warning("Data Frame is empty")
elif isinstance(data, (pd.DataFrame, pd.Series)):
assert pd.isnull(data).sum().sum() == 0
elif isinstance(data, np.ndarray):
assert np.sum(np.isnan(data)) == 0
elif isinstance(data, list):
for each in data:
assert np.sum(np.isnan(each)) == 0
else:
raise ValueError
if data2 is not None:
if len(data2) == 0:
log.warning("Data2 is empty")
elif isinstance(data2, (pd.DataFrame, pd.Series)):
assert pd.isnull(data2).sum().sum() == 0
elif isinstance(data2, np.ndarray):
assert np.sum(np.isnan(data2)) == 0
elif isinstance(data2, list):
for each in data2:
assert np.sum(np.isnan(each)) == 0
else:
raise ValueError
self.axes = self.fig.add_subplot(1, 1, 1)
self.axes.clear()
# self.draw()
self.axes.tick_params(
'x', bottom='on', labelbottom='on', labeltop='off', top="off")
self.axes.yaxis.set_label_position("right")
# print "urls:" ,urls
if data2 is None:
data2 = np.random.rand(len(data))
self.axes.tick_params(
'y', left='off', labelleft='off', labelright='off', right="off")
else:
self.axes.tick_params(
'y', right='on', labelright='on', left='off', labelleft='off')
if x_lab is not None:
self.axes.set_xlabel(x_lab)
if y_lab is not None:
self.axes.set_ylabel(y_lab)
if len(data) == 0:
pass
elif colors is None:
colors = "#2ca25f"
self.axes.scatter(
data, data2, color=colors, picker=5, urls=urls, alpha=0.8)
else:
for c, d, d2, lbl, url in zip(colors, data, data2, labels, urls):
self.axes.scatter(
d, d2, color=c, label=lbl, picker=5, urls=url, alpha=0.8)
self.axes.legend(numpoints=1, fancybox=True, fontsize="small", )
self.axes.get_legend().draggable(True, update="loc")
if xlims is not None:
width = xlims[1] - xlims[0]
if width == 0:
xlims2 = (xlims[0] - 0.5, xlims[0] + 0.5)
else:
xlims2 = (xlims[0] - width / 10, xlims[1] + width / 10,)
self.axes.set_xlim(xlims2, auto=False)
else:
self.axes.set_xlim(auto=True)
self.axes.set_ylim(auto=True)
self.draw()
self.xlim = None
self.ylim = None
self.x_order = None
self.back_fig = None
def redraw_last_plot(self):
if self.last_plot_function is None:
return
else:
self.last_plot_function(
*self.last_plot_arguments, **self.last_plot_kw_arguments)
def add_max_min_opt_lines(self, mini, opti, maxi):
if self.back_fig is None:
self.back_fig = self.copy_from_bbox(self.axes.bbox)
self.xlim = self.axes.get_xlim()
self.ylim = self.axes.get_ylim()
else:
self.restore_region(self.back_fig)
self.axes.set_xlim(self.xlim, auto=False)
self.axes.set_ylim(self.ylim, auto=False)
if mini is None:
self.blit(self.axes.bbox)
return
if self.limits_vertical:
opt_line = self.axes.axvline(opti, color="#8da0cb")
min_line = self.axes.axvline(mini, color="#fc8d62")
max_line = self.axes.axvline(maxi, color="#fc8d62")
else:
opt_line = self.axes.axhline(opti, color="#8da0cb")
min_line = self.axes.axhline(mini, color="#fc8d62")
max_line = self.axes.axhline(maxi, color="#fc8d62")
self.axes.draw_artist(min_line)
self.axes.draw_artist(max_line)
self.axes.draw_artist(opt_line)
self.blit(self.axes.bbox)
def add_threshold_line(self, thr):
thr_line = self.axes.axvline(thr, color="#000000")
self.axes.draw_artist(thr_line)
self.blit(self.axes.bbox)
def add_grayed_scatter(self, data, data2):
colors = "#BBBBBB"
patches = self.axes.scatter(data, data2, color=colors, )
self.axes.draw_artist(patches)
self.blit(self.axes.bbox)
@repeatatable_plot
def make_box_plot(self, data, x_var, y_var, xlabel, ylabel, xticks_labels, ylims=None, intercet=None):
if x_var is None:
plot_data=data[[y_var]]
x_permutation = None
self.x_order = None
else:
plot_data=data[[x_var,y_var]]
# Sort data and labels according to median
x_permutation = plot_data.groupby(x_var).median().sort(y_var).index
plot_data[x_var] = plot_data[x_var].astype("category")
plot_data[x_var]=plot_data[x_var].cat.reorder_categories(x_permutation, ordered=True)
plot_data[x_var].cat.categories = [xticks_labels[x] for x in plot_data[x_var].cat.categories]
self.x_order = {x:i for i,x in enumerate(x_permutation)}
sns.set_style("darkgrid")
self.fig.clear()
self.axes = self.fig.add_subplot(1, 1, 1)
self.axes.clear()
self.axes.tick_params(
'x', bottom='on', labelbottom='on', labeltop='off', top="off")
self.axes.tick_params(
'y', left='off', labelleft='off', labelright='on', right="on")
self.axes.yaxis.set_label_position("right")
self.axes.set_ylim(auto=True)
# artists_dict = self.axes.boxplot(data, sym='gD')
sns.boxplot(x=x_var, y=y_var, data=plot_data, ax=self.axes, fliersize=10)
#sns.boxplot(data_list, ax=self.axes, fliersize=10,
# names=ticks, color="skyblue", width=0.5)
# find fliers
for ls in self.axes.get_lines():
if ls.get_markersize() == 10:
ls.set_picker(5)
poss_ids = dict()
for x, y in izip(*ls.get_data()):
if x_var is not None:
ids=data.loc[(data[x_var] == x_permutation[x]) & (data[y_var] == y)].index
else:
ids=data.loc[(data[y_var] == y)].index
poss_ids.setdefault((x, y), set()).update(ids)
urls = []
for x, y in izip(*ls.get_data()):
u = poss_ids[(x, y)].pop()
urls.append(u)
ls.set_url(urls)
self.axes.set_xlabel(xlabel)
self.axes.set_ylabel(ylabel)
# if xticks_labels is not None:
# self.axes.get_xaxis().set_ticklabels(xticks_labels)
if ylims is not None:
yspan = ylims[1] - ylims[0]
self.axes.set_ylim(ylims[0] - 0.1 * yspan, ylims[1] + 0.1 * yspan)
if intercet is not None:
self.add_intercept_line(intercet)
self.ylim = None
self.xlim = None
self.draw()
self.back_fig = self.copy_from_bbox(self.axes.bbox)
@repeatatable_plot
def make_linked_box_plot(self, data, outcome, x_name, z_name, ylims=None):
sns.set_style("darkgrid")
self.fig.clear()
self.axes = self.fig.add_subplot(1, 1, 1)
plot_data=data[[outcome,x_name, z_name]]
x_labels = braviz_tab_data.get_labels_dict_by_name(x_name)
z_labels = braviz_tab_data.get_labels_dict_by_name(z_name)
# reorder
x_permutation = plot_data.groupby(x_name).median().sort_values(by=outcome).index
z_permutation = plot_data.groupby(z_name).median().sort_values(by=outcome).index
plot_data[x_name] = plot_data[x_name].astype("category")
plot_data[z_name] = plot_data[z_name].astype("category")
plot_data[x_name]=plot_data[x_name].cat.reorder_categories(x_permutation,ordered=True)
plot_data[z_name]=plot_data[z_name].cat.reorder_categories(z_permutation,ordered=True)
# rename categories
plot_data[x_name].cat.categories = [x_labels[x] for x in plot_data[x_name].cat.categories]
plot_data[z_name].cat.categories = [z_labels[z] for z in plot_data[z_name].cat.categories]
fliers_dict = {}
group_width=0.8
sns.boxplot(x_name,outcome,z_name,data=plot_data, ax=self.axes, fliersize=10, width=group_width)
#sns.boxplot(values, ax=self.axes, names=labels, color=colors,
# fliersize=10, widths=box_width, positions=positions)
# find outliers
z_levels = len(plot_data[z_name].unique())
group_centers_space = group_width - group_width / z_levels
offset = group_centers_space/2
each_width=group_centers_space/(z_levels-1)
poss_ids = dict()
#print(offset)
#print(each_width)
for ls in self.axes.get_lines():
if ls.get_markersize() == 10:
ls.set_picker(5)
for x, y in izip(*ls.get_data()):
xx = np.round(x)
zz = x - xx + offset
zz = np.round(zz/each_width)
x_val = x_permutation[xx]
z_val = z_permutation[zz]
ids = data.loc[(data[x_name] == x_val)&(data[outcome] == y)&(data[z_name] == z_val)].index
poss_ids.setdefault((x,y),set()).update(ids)
urls = []
for x, y in izip(*ls.get_data()):
urls.append(poss_ids[(x, y)].pop())
ls.set_url(urls)
self.axes.set_ylabel(outcome)
if ylims is not None:
yspan = ylims[1] - ylims[0]
self.axes.set_ylim(ylims[0] - 0.1 * yspan, ylims[1] + 0.1 * yspan)
self.draw()
self.back_fig = self.copy_from_bbox(self.axes.bbox)
positions_dict = {}
for x,z in itertools.product(data[x_name].unique(),data[z_name].unique()):
xx = x_permutation.get_loc(x)
zz = z_permutation.get_loc(z)
positions_dict[(x,z)]=xx+zz*each_width-offset
self.x_order = positions_dict
self.draw()
@repeatatable_plot
def make_diagnostics(self, residuals, fitted):
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(1, 2, width_ratios=(2, 1))
sns.set_style("darkgrid")
self.fig.clear()
self.axes = self.fig.add_subplot(gs[1])
self.axes.clear()
self.axes.tick_params(
'x', bottom='on', labelbottom='on', labeltop='off', top='off')
self.axes.tick_params(
'y', left='off', labelleft='off', labelright='off', right="off")
self.axes.yaxis.set_label_position("right")
self.axes.set_ylim(auto=True)
# self.axes.set_ylabel("Residuals")
self.axes.set_xlabel("Frequency")
self.axes.hist(
residuals, color="#2ca25f", bins=20, orientation="horizontal")
self.axes2 = self.fig.add_subplot(gs[0], sharey=self.axes)
self.axes2.scatter(fitted, residuals, s=20, color="#2ca25f")
self.axes2.tick_params(
'x', bottom='on', labelbottom='on', labeltop='off', top='off')
self.axes2.tick_params(
'y', left='on', labelleft='on', labelright='off', right="off")
self.axes2.set_ylabel("Residuals")
self.axes2.set_xlabel("Fitted")
self.axes2.yaxis.set_label_position("left")
self.axes2.axhline(color='k')
self.draw()
self.back_fig = self.copy_from_bbox(self.axes.bbox)
self.x_order = None
def add_subject_points(self, x_coords, y_coords, z_coords=None, color=None, urls=None):
# print "adding subjects"
# self.restore_region(self.back_fig)
self.redraw_last_plot()
if self.x_order is not None:
if isinstance(self.x_order, dict):
if z_coords is not None:
x_coords = map(self.x_order.get, izip(x_coords, z_coords))
else:
x_coords = map(self.x_order.get, x_coords)
else:
raise Exception("deprecated")
if color is None:
color = "black"
n_subs = len(x_coords)
alpha = 1
if n_subs > 1:
alpha = 0.5 + 0.5/n_subs
collection = self.axes.scatter(
x_coords, y_coords, marker="o", s=120, edgecolors=color, urls=urls, picker=5, zorder=10, linewidths=3,
alpha=alpha)
collection.set_facecolor('none')
self.axes.draw_artist(collection)
# self.blit(self.axes.bbox)
self.draw()
self.back_fig = self.copy_from_bbox(self.axes.bbox)
def add_intercept_line(self, ycoord):
self.axes.axhline(ycoord,color='k')
self.draw()
self.back_fig = self.copy_from_bbox(self.axes.bbox)
def generate_tooltip_event(self, e):
# print type(e.artist)
if type(e.artist) == matplotlib.lines.Line2D:
urls = e.artist.get_url()
# print e.ind
ind = e.ind
if hasattr(ind, "__iter__"):
ind = ind[0]
u = urls[ind]
self.box_outlier_pick_signal.emit(
str(u), (e.mouseevent.x, self.height() - e.mouseevent.y))
elif type(e.artist) == matplotlib.collections.PathCollection:
if e.artist.get_urls()[0] is None:
return
ind = e.ind
if hasattr(ind, "__iter__"):
ind = ind[0]
subj = str(e.artist.get_urls()[ind])
self.scatter_pick_signal.emit(
subj, (e.mouseevent.x, self.height() - e.mouseevent.y))
else:
return
def mouse_move_event_handler(self, event):
# to avoid interference with draggable legend
# self.pick(event)
legend = self.axes.get_legend()
if (legend is not None) and (legend.legendPatch.contains(event)[0] == 1):
pass
# print "in legend"
else:
self.pick(event)
[docs]class ContextVariablesPanel(QtGui.QGroupBox):
"""
A panel that displays and allows to edit variables for a given subject.
The context menu of the panel allows the user to select variables and to make some of them editable.
In this case a *save changes* button will also be displayed. Pressing it will cause the changes to be
written into the databases
Args:
parent (QObject) : Qt parent
title (str) : Title for the widget
initial_variable_idxs (list) : List of variable indices to display at start
initial_subject : Id of the initial subject whose variable values will be displayed
app : Optional, an application with the ``save_screenshot`` and ``get_state_dict`` method.
In this case a scenario will be automatically created whenever a variable is first modified.
sample (list) : list of subjects. This sample will be passed on to the variable select dialog deployed
by the panel.
"""
def __init__(self, parent, title="Context", initial_variable_idxs=None, initial_subject=None, app=None,
sample=None):
super(ContextVariablesPanel, self).__init__(parent)
if initial_variable_idxs is None or initial_subject is None:
config = config_file.get_apps_config()
if initial_variable_idxs is None:
var_names = config.get_default_variables().values()
initial_variable_idxs = [
braviz_tab_data.get_var_idx(v) for v in var_names]
if initial_subject is None:
initial_subject = config.get_default_subject()
self.setTitle(title)
self.setToolTip(
"Right click to select context variables, and to make them editable")
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.layout = QtGui.QHBoxLayout(self)
self.setLayout(self.layout)
self.app = app
self.sample = sample
if self.sample is None:
self.sample = braviz_tab_data.get_subjects()
self.layout.setContentsMargins(7, 2, 7, 2)
self.customContextMenuRequested.connect(self.create_context_menu)
size_policy = QtGui.QSizePolicy(
QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Maximum)
# size_policy.setHorizontalStretch(0)
size_policy.setVerticalStretch(1)
self.setSizePolicy(size_policy)
# self.setMaximumSize(QtCore.QSize(16777215, 56))
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.setObjectName("context_frame")
# self.setFrameStyle(self.NoFrame)
# internal variables
self.__context_variable_codes = None
self.__context_variable_names = None
self.__is_nominal = None
self.__labels_dict = None
self.__context_labels = None
self.__values_widgets = None
self.__internal_df = None
self.__curent_subject = None
self.__editables_dict = None
self.__save_changes_button = None
self.set_variables(initial_variable_idxs)
if initial_subject is not None:
self.set_subject(initial_subject)
[docs] def set_variables(self, variables, editables=None):
"""
Sets a new set of variables for the panel
Args:
variables (list) : List of variable indices
editables (dict) : Dictionary mapping variable indices to booleans that indicate if a variable should
be modifiable by the user
"""
self.__context_variable_codes = list(variables)
self.__context_variable_names = dict(
(idx, get_var_name(idx)) for idx in self.__context_variable_codes)
self.__is_nominal = dict(
(idx, is_variable_nominal(idx)) for idx in self.__context_variable_codes)
self.__labels_dict = dict((idx, get_labels_dict(idx)) for idx in self.__context_variable_codes if
self.__is_nominal[idx])
self.__internal_df = get_data_frame_by_index(
self.__context_variable_codes)
self.__values_widgets = []
if editables is None:
self.__editables_dict = dict((idx, False) for idx in variables)
else:
assert frozenset(variables) == frozenset(editables.keys())
self.__editables_dict = editables
self._reset_internal_widgets()
[docs] def get_variables(self):
"""
Get a list of current variable codes
"""
return self.__context_variable_codes
[docs] def get_editables(self):
"""
Get a list of tuples (var_code, editable) indicating which variables are editable
"""
return self.__editables_dict.items()
def _reset_internal_widgets(self):
# clear layout
self.__save_changes_button = None
for i in xrange(self.layout.count() - 1, -1, -1):
w = self.layout.takeAt(i)
wgt = w.widget()
if wgt is not None:
wgt.deleteLater()
self.__context_labels = []
self.__values_widgets = []
first = True
any_editable = False
for idx in self.__context_variable_codes:
# add separator
if not first:
self.layout.addStretch()
else:
first = False
# add label
label = QtGui.QLabel("%s : " % self.__context_variable_names[idx])
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.__context_labels.append(label)
self.layout.addWidget(label)
# add value
if self.__editables_dict.get(idx) is True:
value_widget = self._get_editable_widget(idx)
any_editable = True
else:
value_widget = self._get_read_only_widget(idx)
self.layout.addWidget(value_widget)
self.__values_widgets.append(value_widget)
if any_editable is True:
self.__save_changes_button = QtGui.QPushButton("Save")
self.__save_changes_button.setEnabled(False)
self.__save_changes_button.clicked.connect(
self._save_changes_into_db)
self.layout.addWidget(self.__save_changes_button)
return
def _get_read_only_widget(self, idx):
value_widget = QtGui.QLabel("XXXXXXX")
value_widget.setFrameShape(QtGui.QFrame.Box)
value_widget.setFrameShadow(QtGui.QFrame.Raised)
# value_widget.setContentsMargins(7,7,7,7)
value_widget.setMargin(7)
value_widget.setAlignment(QtCore.Qt.AlignCenter)
font = QtGui.QFont()
font.setPointSize(11)
value_widget.setFont(font)
value_widget.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
value_widget.setCursor(QtCore.Qt.IBeamCursor)
# calculate maximum width
if is_variable_nominal(idx):
lens = [(x, len(x))
for x in self.__labels_dict[idx].itervalues() if x is not None]
if len(lens) == 0:
longest = "<Unknown>"
else:
longest = max(lens, key=lambda x: x[1])[0]
value_widget.setText(longest)
longest_size = value_widget.sizeHint()
value_widget.setFixedWidth(longest_size.width())
value_widget.setFixedHeight(longest_size.height())
else:
max_value = get_maximum_value(idx)
if max_value is None:
max_value = 1000
value_widget.setText("%.2f" % max_value)
longest_size = value_widget.sizeHint()
value_widget.setFixedWidth(longest_size.width())
value_widget.setFixedHeight(longest_size.height())
return value_widget
def _get_editable_widget(self, idx):
if self.__is_nominal.get(idx) is True:
value_widget = QtGui.QComboBox()
for i, lbl in self.__labels_dict[idx].iteritems():
if not np.isnan(float(i)):
value_widget.addItem(lbl, i)
value_widget.insertSeparator(value_widget.count())
value_widget.addItem("<Unknown>", float("nan"))
value_widget.currentIndexChanged.connect(self._enable_save_changes)
else:
value_widget = QtGui.QDoubleSpinBox()
minim, maxim = get_min_max_values(idx)
value_widget.setMaximum(10 * maxim)
value_widget.setMinimum(-10 * maxim)
value_widget.setSingleStep((maxim - minim) / 20)
value_widget.valueChanged.connect(self._enable_save_changes)
font = QtGui.QFont()
font.setPointSize(11)
value_widget.setFont(font)
return value_widget
[docs] def set_subject(self, subject_id):
"""
Set the current subject to which variable values are associated
Args:
subject_id : Subject id
"""
values = self.__internal_df.loc[int(subject_id)]
for i, idx in enumerate(self.__context_variable_codes):
try:
value = values[self.__context_variable_names[idx]]
except KeyError:
value = float("nan")
# print self.__context_variable_names[idx], value
value_widget = self.__values_widgets[i]
if self.__is_nominal[idx]:
if isinstance(value_widget, QtGui.QLabel):
label = self.__labels_dict[idx].get(value, "?")
if label is None:
label = "?"
value_widget.setText(label)
elif isinstance(value_widget, QtGui.QComboBox):
label = self.__labels_dict[idx].get(value, "<Unknown>")
index = value_widget.findText(label)
value_widget.setCurrentIndex(index)
else:
if isinstance(value_widget, QtGui.QLabel):
value_widget.setText("%s" % value)
elif isinstance(value_widget, QtGui.QDoubleSpinBox):
value_widget.setValue(value)
self.__curent_subject = subject_id
if self.__save_changes_button is not None:
self.__save_changes_button.setEnabled(False)
def _enable_save_changes(self, *args):
if self.__save_changes_button is None:
return
self.__save_changes_button.setEnabled(True)
def _save_changes_into_db(self):
for i, idx in enumerate(self.__context_variable_codes):
if self.__editables_dict[idx] is True:
value_widget = self.__values_widgets[i]
if isinstance(value_widget, QtGui.QDoubleSpinBox):
value = float(value_widget.value())
elif isinstance(value_widget, QtGui.QComboBox):
value = value_widget.itemData(value_widget.currentIndex())
value = value
if np.isnan(float(value)):
value = None
else:
value = int(value)
# update value
braviz_tab_data.update_variable_value(
int(idx), self.__curent_subject, value)
# update internal
var_name = self.__context_variable_names[idx]
self.__internal_df[var_name][
int(self.__curent_subject)] = value
# check if scenarios exists for this variable
if braviz_user_data.count_variable_scenarios(int(idx)) == 0 and self.app is not None:
# save scenario
name = "<AUTO_%s>" % self.__context_variable_names[idx]
desc = "Created automatically when saving values for variable %s" % self.__context_variable_names[
idx]
data = self.app.get_state_dict()
app = data["meta"]["application"]
scn_idx = braviz_user_data.save_scenario(
app, name, desc, data)
# link
braviz_user_data.link_var_scenario(int(idx), scn_idx)
# save screenshot
self.app.save_screenshot(scn_idx)
self.__save_changes_button.setEnabled(0)
# print idx_value_tuples
[docs] def set_sample(self, new_sample):
"""
Set the sample used in the variable selection dialogs
Args:
new_sample (list) : List of subject ids
"""
self.sample = list(new_sample)
class ImageComboBoxManager(QtCore.QObject):
"""
Reusable component for selecting images from a braviz reader
"""
image_changed = QtCore.pyqtSignal(tuple)
def __init__(self, reader, show_none=False, show_fmri=True):
super(ImageComboBoxManager, self).__init__()
grey_images = sorted(n.upper() for n in reader.get("IMAGE", None, index=True))
label_maps = sorted(n.upper() for n in reader.get("LABEL", None, index=True))
dti = ["DTI", ]
if show_fmri:
fmri_pdgms = sorted(n.upper() for n in reader.get("FMRI", None, index=True))
else:
fmri_pdgms = []
# check for collisions
assert len(grey_images) + len(label_maps) + len(dti) + len(fmri_pdgms) == \
len(set(grey_images + label_maps + fmri_pdgms + dti))
all_images = [("IMAGE", n) for n in grey_images] + \
[("LABEL", n) for n in label_maps] + \
[("DTI", n) for n in dti] + \
[("FMRI", n) for n in fmri_pdgms]
if show_none:
all_images = [(None, "NONE")] + all_images
self.available_images = all_images
self.combo_box = None
self.data_dict = {t: i for i, t in enumerate(all_images)}
def setup(self, combo_box):
combo_box.clear()
for im_class, name in self.available_images:
combo_box.addItem(name.title(), (im_class, name))
combo_box.currentIndexChanged.connect(self._handle_index_change)
self.combo_box = combo_box
def set_image(self, image_class, image_name):
image_class = image_class.upper() if image_class is not None else None
image_name = image_name.upper()
index = self.data_dict[(image_class, image_name)]
self.combo_box.setCurrentIndex(index)
def _handle_index_change(self, index):
new_class, new_name = self.combo_box.itemData(index)
self.image_changed.emit((new_class, new_name))
@property
def current_class(self):
idx = self.combo_box.currentIndex()
data = self.combo_box.itemData(idx)
return data[0]
@property
def current_name(self):
idx = self.combo_box.currentIndex()
data = self.combo_box.itemData(idx)
return data[1]
@property
def current_class_and_name(self):
idx = self.combo_box.currentIndex()
data = self.combo_box.itemData(idx)
return data
class ContrastComboManager(QtCore.QObject):
"""
Reusable component for selecting fMRI constrasts from a braviz reader
"""
contrast_changed = QtCore.pyqtSignal(int)
def __init__(self, reader):
super(ContrastComboManager, self).__init__()
self.valid_paradigms = reader.get("FMRI", None, index=True)
self.__last_contrast = {}
self.combo_box = None
self.reader = reader
self._contrast_dict = None
self._index_dict = {}
self._setting_up = False
self._current_pdgm = None
def setup(self, combo_box, initial_subject=None, initial_pdgm=None):
combo_box.clear()
combo_box.currentIndexChanged.connect(self._handle_index_change)
self.combo_box = combo_box
if initial_pdgm is not None and initial_subject is not None:
self.change_paradigm(initial_subject, initial_pdgm)
def change_paradigm(self, subject, pdgm):
if self._current_pdgm == pdgm and self._contrast_dict is not None:
# no update necessary
return
self._current_pdgm = pdgm
self._setting_up = True
if pdgm is not None:
pdgm = pdgm.upper()
log = logging.getLogger(__name__)
if pdgm not in self.valid_paradigms:
self.combo_box.clear()
self.combo_box.setEnabled(False)
self._setting_up = False
return
try:
contrast_dict = self.reader.get("FMRI", subject, name=pdgm, contrasts_dict=True)
except Exception:
log.warning("Couldn't get contrast dict")
contrast_dict = None
self._contrast_dict = contrast_dict
self._index_dict = {}
if contrast_dict is None:
self.combo_box.setEnabled(False)
else:
items = sorted(contrast_dict.items(), key=lambda x: x[0])
self.combo_box.clear()
for i, (k, n) in enumerate(items):
self.combo_box.addItem(n, k)
self._index_dict[i] = k
last_contrast = self.__last_contrast.get(pdgm)
if last_contrast is not None:
idx = self.combo_box.findData(last_contrast)
self.combo_box.setCurrentIndex(idx)
self.combo_box.setEnabled(True)
self._setting_up = False
def set_contrast(self, contrast):
idx = self._index_dict.get(contrast)
if idx is None:
log = logging.getLogger(__name__)
log.warning("Contrast not currently in box")
return
self.combo_box.setCurrentIndex(idx)
def get_previous_contrast(self, pdgm):
return self.__last_contrast.get(pdgm, 1)
def _handle_index_change(self, index):
if self._setting_up:
return
contrast_n = self.combo_box.itemData(index)
self.__last_contrast[self._current_pdgm] = contrast_n
self.contrast_changed.emit(contrast_n)
if __name__ == "__main__":
app = QtGui.QApplication([])
context = ContextVariablesPanel(None)
context.show()
app.exec_()