def update_legend(self, vmax=17.): ''' Update the legend with the electrode devices. ''' self.legend_handles = [] for i in self.devices: QtCore.pyqtRemoveInputHook() cmap = self.elec_colors num = self.devices.index(i) c = cmap(num / vmax) color_patch = mpatches.Patch(color=c, label=i) self.legend_handles.append(color_patch) plt.legend(handles=self.legend_handles, loc='upper right', fontsize='x-small')
def update_legend(self, vmax=17.): ''' Update the legend with the electrode devices. Parameters ---------- vmax : int Maximum number of devices (is used to set the color scale) ''' self.legend_handles = [] for i in self.devices: QtCore.pyqtRemoveInputHook() cmap = self.elec_colors num = self.devices.index(i) c = cmap(num / vmax) color_patch = mpatches.Patch(color=c, label=i) self.legend_handles.append(color_patch) plt.legend(handles=self.legend_handles, fontsize='x-small', borderaxespad=3, bbox_to_anchor=self.ax[3].get_position())
def __init__(self, subj_dir, img): ''' Initialize the electrode picker with the user-defined MRI and co-registered CT scan in [subj_dir]. Images will be displayed using orientation information obtained from the image header. Images will be resampled to dimensions [256,256,256] for display. We will also listen for keyboard and mouse events so the user can interact with each of the subplot panels (zoom/pan) and add/remove electrodes with a keystroke. ''' QtCore.pyqtRemoveInputHook() self.subj_dir = subj_dir self.img = nib.load(img) # Get affine transform self.affine = self.img.affine self.fsVox2RAS = np.array([[-1., 0., 0., 128.], [0., 0., 1., -128.], [0., -1., 0., 128.], [0., 0., 0., 1.]]) # Apply orientation to the MRI so that the order of the dimensions will be # sagittal, coronal, axial self.codes = nib.orientations.axcodes2ornt( nib.orientations.aff2axcodes(self.affine)) img_data = nib.orientations.apply_orientation(self.img.get_data(), self.codes) self.voxel_sizes = nib.affines.voxel_sizes(self.affine) nx, ny, nz = np.array(img_data.shape, dtype='float') self.inv_affine = np.linalg.inv(self.affine) self.img_clim = np.percentile(img_data, (1., 99.)) self.img_data = img_data self.elec_data = np.nan + np.zeros((img_data.shape)) self.bin_mat = '' # binary mask for electrodes self.device_num = 0 # Start with device 0, increment when we add a new electrode name type self.device_name = '' self.devices = [ ] # This will be a list of the devices (grids, strips, depths) self.elec_num = dict() self.elecmatrix = dict() # This will be the electrode coordinates self.legend_handles = [] # This will hold legend entries self.elec_added = False # Whether we're in an electrode added state self.imsz = [256, 256, 256] self.current_slice = np.array( [self.imsz[0] / 2, self.imsz[1] / 2, self.imsz[2] / 2], dtype=np.float) self.fig = plt.figure(figsize=(12, 10)) self.fig.canvas.set_window_title('Electrode Picker') thismanager = plt.get_current_fig_manager() thismanager.window.setWindowIcon( QtGui.QIcon((os.path.join('icons', 'leftbrain_blackbg.png')))) self.im = [] self.elec_im = [] self.pial_im = [] self.cursor = [] self.cursor2 = [] im_ranges = [[0, self.imsz[1], 0, self.imsz[2]], [0, self.imsz[0], 0, self.imsz[2]], [0, self.imsz[0], 0, self.imsz[1]]] im_labels = [['Inferior', 'Posterior'], ['Inferior', 'Left'], ['Posterior', 'Left']] self.ax = [] self.contour = [False, False, False] self.pial_surf_on = True # Whether pial surface is visible or not self.T1_on = True # Whether T1 is visible or not # This is the current slice for indexing (as integers so python doesnt complain) cs = np.round(self.current_slice).astype(np.int) # Plot sagittal, coronal, and axial views for i in np.arange(3): self.ax.append(self.fig.add_subplot(2, 2, i + 1)) self.ax[i].set_axis_bgcolor('k') if i == 0: imdata = img_data[cs[0], :, :].T ctdat = ct_data[cs[0], :, :].T edat = self.elec_data[cs[0], :, :].T pdat = self.pial_data[cs[0], :, :].T elif i == 1: imdata = img_data[:, cs[1], :].T ctdat = ct_data[:, cs[1], :].T edat = self.elec_data[:, cs[1], :].T pdat = self.pial_data[:, cs[1], :].T elif i == 2: imdata = img_data[:, :, cs[2]].T ctdat = ct_data[:, :, cs[2]].T edat = self.elec_data[:, :, cs[2]].T pdat = self.pial_data[:, :, cs[2]].T # Show the MRI data in grayscale self.im.append(plt.imshow(imdata, cmap=cm.gray, aspect='auto')) # Overlay the CT on top in "hot" colormap, slightly transparent self.ct_im.append( plt.imshow(ctdat, cmap=cm.hot, aspect='auto', alpha=0.5, vmin=1000, vmax=3000)) # Overlay the electrodes image on top (starts as NaNs, is eventually filled in) self.elec_colors = mcolors.LinearSegmentedColormap.from_list( 'elec_colors', np.vstack( (cm.Set1(np.linspace(0., 1, 9)), cm.Set2(np.linspace(0., 1, 8))))) self.elec_im.append( plt.imshow(edat, cmap=self.elec_colors, aspect='auto', alpha=1, vmin=0, vmax=17)) # Overlay the pial surface self.pial_im.append(self.ax[i].contour(pdat, linewidths=0.5, colors='y')) self.contour[i] = True # Plot a green cursor self.cursor.append( plt.plot([cs[1], cs[1]], [ self.ax[i].get_ylim()[0] + 1, self.ax[i].get_ylim()[1] - 1 ], color=[0, 1, 0])) self.cursor2.append( plt.plot([ self.ax[i].get_xlim()[0] + 1, self.ax[i].get_xlim()[1] - 1 ], [cs[2], cs[2]], color=[0, 1, 0])) # Flip the y axis so brains are the correct side up plt.gca().invert_yaxis() # Get rid of tick labels self.ax[i].set_xticks([]) self.ax[i].set_yticks([]) # Label the axes self.ax[i].set_xlabel(im_labels[i][0]) self.ax[i].set_ylabel(im_labels[i][1]) self.ax[i].axis(im_ranges[i]) # Plot the maximum intensity projection self.ct_slice = 's' # Show sagittal MIP to start self.ax.append(self.fig.add_subplot(2, 2, 4)) self.ax[3].set_axis_bgcolor('k') self.im.append( plt.imshow(np.nanmax(ct_data[cs[0] - 15:cs[0] + 15, :, :], axis=0).T, cmap=cm.gray, aspect='auto')) self.cursor.append( plt.plot( [cs[1], cs[1]], [self.ax[3].get_ylim()[0] + 1, self.ax[3].get_ylim()[1] - 1], color=[0, 1, 0])) self.cursor2.append( plt.plot( [self.ax[3].get_xlim()[0] + 1, self.ax[3].get_xlim()[1] - 1], [cs[2], cs[2]], color=[0, 1, 0])) self.ax[3].set_xticks([]) self.ax[3].set_yticks([]) plt.gca().invert_yaxis() self.ax[3].axis([0, self.imsz[1], 0, self.imsz[2]]) self.elec_im.append( plt.imshow(self.elec_data[cs[0], :, :].T, cmap=self.elec_colors, aspect='auto', alpha=1, vmin=0, vmax=17)) plt.gcf().suptitle( "Press 'n' to enter device name in console, press 'e' to add an electrode at crosshair, press 'h' for more options", fontsize=14) plt.tight_layout() plt.subplots_adjust(top=0.9) cid2 = self.fig.canvas.mpl_connect('scroll_event', self.on_scroll) cid3 = self.fig.canvas.mpl_connect('button_press_event', self.on_click) cid = self.fig.canvas.mpl_connect('key_press_event', self.on_key) #cid4 = self.fig.canvas.mpl_connect('key_release_event', self.on_key) plt.show() self.fig.canvas.draw()
def __init__(self, subj_dir, hem): ''' Initialize the electrode picker with the user-defined MRI and co-registered CT scan in [subj_dir]. Images will be displayed using orientation information obtained from the image header. Images will be resampled to dimensions [256,256,256] for display. We will also listen for keyboard and mouse events so the user can interact with each of the subplot panels (zoom/pan) and add/remove electrodes with a keystroke. Parameters ---------- subj_dir : str Path to freesurfer subjects hem : {'lh', 'rh', 'stereo'} Hemisphere of implantation. Attributes ---------- subj_dir : str Path to freesurfer subjects hem : {'lh','rh','stereo'} Hemisphere of implantation img : nibabel image Data from brain.mgz T1 MRI scan ct : nibabel image Data from rCT.nii registered CT scan pial_img : nibabel image Filled pial image affine : array-like Affine transform for img fsVox2RAS : array-like Freesurfer voxel to RAS coordinate affine transform codes : nibabel orientation codes voxel_sizes : array-like nibabel voxel size inv_affine : array-like Inverse of self.affine transform img_clim : array-like 1st and 99th percentile of the image data (for color scaling) pial_codes : orientation codes for pial ct_codes : orientation codes for CT elec_data : array-like Mask for the electrodes bin_mat : array-like Temporary mask for populating elec_data device_num : int Number of current device that has been added device_name : str Name of current device devices : list List of devices (grids, strips, depths) elec_num : dict Indexed by device_name, which number electrode we are on for that particular device elecmatrix : dict Dictionary of electrode coordinates legend_handles : list Holds legend entries elec_added : bool Whether we're in an electrode added state imsz : array-like image size (brain.mgz) ctsz : array-like CT size (rCT.nii) current_slice : array-like Which 3D slice coordinate the user clicked fig : figure window The current figure window im : Contains data for each axis with MRI data values. ct_im : Contains CT data for each axis elec_im : Contains electrode data for each axis pial_im : Contains data for the pial surface on each axis cursor : array-like Cross hair cursor2 : array-like Cross hair ax : which of the axes we're on contour : list of bool Whether pial surface contour is displayed in each view pial_surf_on : bool Whether pial surface is visible or not T1_on : bool Whether T1 is visible or not ct_slice : {'s','c','a'} How to slice CT maximum intensity projection (sagittal, coronal, or axial) ''' QtCore.pyqtRemoveInputHook() self.subj_dir = subj_dir if hem == 'stereo': hem = 'lh' # For now, set to lh because hemisphere isn't used in stereo case self.hem = hem self.img = nib.load(os.path.join(subj_dir, 'mri', 'brain.mgz')) self.ct = nib.load(os.path.join(subj_dir, 'CT', 'rCT.nii')) pial_fill = os.path.join(subj_dir, 'surf', '%s.pial.filled.mgz' % (self.hem)) if not os.path.isfile(pial_fill): pial_surf = os.path.join(subj_dir, 'surf', '%s.pial' % (self.hem)) mris_fill = os.path.join(os.environ['FREESURFER_HOME'], 'bin', 'mris_fill') os.system('%s -c -r 1 %s %s' % (mris_fill, pial_surf, pial_fill)) self.pial_img = nib.load(pial_fill) #self.slider = QSlider(Qt.Horizontal) # Get affine transform self.affine = self.img.affine self.fsVox2RAS = np.array([[-1., 0., 0., 128.], [0., 0., 1., -128.], [0., -1., 0., 128.], [0., 0., 0., 1.]]) # Apply orientation to the MRI so that the order of the dimensions will be # sagittal, coronal, axial self.codes = nib.orientations.axcodes2ornt( nib.orientations.aff2axcodes(self.affine)) img_data = nib.orientations.apply_orientation(self.img.get_data(), self.codes) self.voxel_sizes = nib.affines.voxel_sizes(self.affine) nx, ny, nz = np.array(img_data.shape, dtype='float') self.inv_affine = np.linalg.inv(self.affine) self.img_clim = np.percentile(img_data, (1., 99.)) # Apply orientation to pial surface fill self.pial_codes = nib.orientations.axcodes2ornt( nib.orientations.aff2axcodes(self.pial_img.affine)) pial_data = nib.orientations.apply_orientation( self.pial_img.get_data(), self.pial_codes) pial_data = scipy.ndimage.binary_closing(pial_data) # Apply orientation to the CT so that the order of the dimensions will be # sagittal, coronal, axial self.ct_codes = nib.orientations.axcodes2ornt( nib.orientations.aff2axcodes(self.ct.affine)) ct_data = nib.orientations.apply_orientation(self.ct.get_data(), self.ct_codes) cx, cy, cz = np.array(ct_data.shape, dtype='float') # Resample both images to the highest resolution voxsz = (256, 256, 256) if ct_data.shape != voxsz: print("Resizing voxels in CT") ct_data = scipy.ndimage.zoom( ct_data, [voxsz[0] / cx, voxsz[1] / cy, voxsz[2] / cz]) print(ct_data.shape) if self.img.shape != voxsz: print("Resizing voxels in MRI") img_data = scipy.ndimage.zoom( img_data, [voxsz[0] / nx, voxsz[1] / ny, voxsz[2] / nz]) print(img_data.shape) # Threshold the CT so only bright objects (electrodes) are visible ct_data[ct_data < 1000] = np.nan self.ct_data = ct_data self.img_data = img_data self.pial_data = pial_data self.elec_data = np.nan + np.zeros((img_data.shape)) self.bin_mat = '' # binary mask for electrodes self.device_num = 0 # Start with device 0, increment when we add a new electrode name type self.device_name = '' self.devices = [ ] # This will be a list of the devices (grids, strips, depths) self.elec_num = dict() self.elecmatrix = dict() # This will be the electrode coordinates self.legend_handles = [] # This will hold legend entries self.elec_added = False # Whether we're in an electrode added state self.imsz = [256, 256, 256] self.ctsz = [256, 256, 256] self.current_slice = np.array( [self.imsz[0] / 2, self.imsz[1] / 2, self.imsz[2] / 2], dtype=np.float) self.fig = plt.figure(figsize=(12, 10)) self.fig.canvas.set_window_title('Electrode Picker') thismanager = plt.get_current_fig_manager() thismanager.window.setWindowIcon( QtGui.QIcon((os.path.join('icons', 'leftbrain_blackbg.png')))) self.im = [] self.ct_im = [] self.elec_im = [] self.pial_im = [] self.cursor = [] self.cursor2 = [] im_ranges = [[0, self.imsz[1], 0, self.imsz[2]], [0, self.imsz[0], 0, self.imsz[2]], [0, self.imsz[0], 0, self.imsz[1]]] im_labels = [['Inferior', 'Posterior'], ['Inferior', 'Left'], ['Posterior', 'Left']] self.ax = [] self.contour = [False, False, False] self.pial_surf_on = True # Whether pial surface is visible or not self.T1_on = True # Whether T1 is visible or not # This is the current slice for indexing (as integers so python doesnt complain) cs = np.round(self.current_slice).astype(np.int) # Plot sagittal, coronal, and axial views for i in np.arange(3): self.ax.append(self.fig.add_subplot(2, 2, i + 1)) self.ax[i].set_axis_bgcolor('k') if i == 0: imdata = img_data[cs[0], :, :].T ctdat = ct_data[cs[0], :, :].T edat = self.elec_data[cs[0], :, :].T pdat = self.pial_data[cs[0], :, :].T elif i == 1: imdata = img_data[:, cs[1], :].T ctdat = ct_data[:, cs[1], :].T edat = self.elec_data[:, cs[1], :].T pdat = self.pial_data[:, cs[1], :].T elif i == 2: imdata = img_data[:, :, cs[2]].T ctdat = ct_data[:, :, cs[2]].T edat = self.elec_data[:, :, cs[2]].T pdat = self.pial_data[:, :, cs[2]].T # Show the MRI data in grayscale self.im.append(plt.imshow(imdata, cmap=cm.gray, aspect='auto')) # Overlay the CT on top in "hot" colormap, slightly transparent self.ct_im.append( plt.imshow(ctdat, cmap=cm.hot, aspect='auto', alpha=0.5, vmin=1000, vmax=3000)) # Overlay the electrodes image on top (starts as NaNs, is eventually filled in) self.elec_colors = mcolors.LinearSegmentedColormap.from_list( 'elec_colors', np.vstack( (cm.Set1(np.linspace(0., 1, 9)), cm.Set2(np.linspace(0., 1, 8))))) self.elec_im.append( plt.imshow(edat, cmap=self.elec_colors, aspect='auto', alpha=1, vmin=0, vmax=17)) # Overlay the pial surface self.pial_im.append(self.ax[i].contour(pdat, linewidths=0.5, colors='y')) self.contour[i] = True # Plot a green cursor self.cursor.append( plt.plot([cs[1], cs[1]], [ self.ax[i].get_ylim()[0] + 1, self.ax[i].get_ylim()[1] - 1 ], color=[0, 1, 0])) self.cursor2.append( plt.plot([ self.ax[i].get_xlim()[0] + 1, self.ax[i].get_xlim()[1] - 1 ], [cs[2], cs[2]], color=[0, 1, 0])) # Flip the y axis so brains are the correct side up plt.gca().invert_yaxis() # Get rid of tick labels self.ax[i].set_xticks([]) self.ax[i].set_yticks([]) # Label the axes self.ax[i].set_xlabel(im_labels[i][0]) self.ax[i].set_ylabel(im_labels[i][1]) self.ax[i].axis(im_ranges[i]) # Plot the maximum intensity projection self.ct_slice = 's' # Show sagittal MIP to start self.ax.append(self.fig.add_subplot(2, 2, 4)) self.ax[3].set_axis_bgcolor('k') self.im.append( plt.imshow(np.nanmax(ct_data[cs[0] - 15:cs[0] + 15, :, :], axis=0).T, cmap=cm.gray, aspect='auto')) self.cursor.append( plt.plot( [cs[1], cs[1]], [self.ax[3].get_ylim()[0] + 1, self.ax[3].get_ylim()[1] - 1], color=[0, 1, 0])) self.cursor2.append( plt.plot( [self.ax[3].get_xlim()[0] + 1, self.ax[3].get_xlim()[1] - 1], [cs[2], cs[2]], color=[0, 1, 0])) self.ax[3].set_xticks([]) self.ax[3].set_yticks([]) plt.gca().invert_yaxis() self.ax[3].axis([0, self.imsz[1], 0, self.imsz[2]]) self.elec_im.append( plt.imshow(self.elec_data[cs[0], :, :].T, cmap=self.elec_colors, aspect='auto', alpha=1, vmin=0, vmax=17)) plt.gcf().suptitle( "Press 'n' to enter device name, press 'e' to add an electrode at crosshair, press 'h' for more options", fontsize=14) plt.tight_layout() plt.subplots_adjust(top=0.9) cid2 = self.fig.canvas.mpl_connect('scroll_event', self.on_scroll) cid3 = self.fig.canvas.mpl_connect('button_press_event', self.on_click) cid = self.fig.canvas.mpl_connect('key_press_event', self.on_key) #cid4 = self.fig.canvas.mpl_connect('key_release_event', self.on_key) slider_ax = plt.axes([ self.ax[0].get_position().bounds[0] + 0.06, self.ax[0].get_position().bounds[1] + 0.42, self.ax[0].get_position().bounds[2] - 0.1, 0.02 ]) self.mri_slider = Slider(slider_ax, 'MRI max', img_data.min(), img_data.max(), facecolor=[0.3, 0.3, 0.3]) self.mri_slider.on_changed(self.update_mri) ct_slider_ax = plt.axes([ self.ax[1].get_position().bounds[0] + 0.06, self.ax[1].get_position().bounds[1] + 0.42, self.ax[1].get_position().bounds[2] - 0.1, 0.02 ]) print(np.nanmax(ct_data)) self.ct_slider = Slider(ct_slider_ax, 'CT max', 1000, np.nanmax(ct_data), facecolor=[0.8, 0.1, 0.1]) self.ct_slider.on_changed(self.update_ct) plt.show() self.fig.canvas.draw()