class EmulatorPreferencesPane(PreferencesPane): """ The preferences pane for the Framework application. """ #### 'PreferencesPane' interface ########################################## # The factory to use for creating the preferences model object. model_factory = EmulatorPreferences category = Str('Editors') #### 'FrameworkPreferencesPane' interface ################################ # Note the quirk in the RangeEditor: specifying a custom editor is # supposed to take the defaults from the item name specified, but I # can't get it to work with only the "mode" parameter. I have to specify # all the other params, and the low/high values have to be attributes # in EmulatorPreferences, not the values in the trait itself. See # traitsui/editors/range_editor.py view = View( VGroup(HGroup(Item('map_width', editor=RangeEditor(mode="spinner", is_float=False, low_name='map_width_low', high_name='map_width_high')), Label('Default Character Map Width (in bytes)'), show_labels = False), HGroup(Item('bitmap_width', editor=RangeEditor(mode="spinner", is_float=False, low_name='bitmap_width_low', high_name='bitmap_width_high')), Label('Default Bitmap Width (in bytes)'), show_labels = False), HGroup(Item('hex_grid_width', editor=RangeEditor(mode="spinner", is_float=False, low_name='hex_grid_width_low', high_name='hex_grid_width_high')), Label('Default Hex Grid Width (in bytes)'), show_labels = False), HGroup(Item('text_font'), Label('Hex Display Font'), show_labels = False), HGroup(Item('header_font'), Label('Column Header Font'), show_labels = False), HGroup(Item('int_display_format'), Label('Number Display Format'), show_labels=False), HGroup(Item('hex_display_format'), Label('Hex Display Format'), show_labels=False), HGroup(Item('hex_grid_lower_case'), Label('Use Lower Case for Hex Digits'), show_labels = False), HGroup(Item('assembly_lower_case'), Label('Use Lower Case for Assembler Mnemonics'), show_labels = False), HGroup(Item('highlight_background_color', editor=ColorEditor(), style='custom'), Label('Highlight Color'), show_labels = False), label='Hex Editor'), resizable=True)
class Align(HasTraits): # The position of the view position = Array(shape=(3, )) brightness = Range(-2., 2., value=0.) contrast = Range(0., 3., value=1.) opacity = Range(0., 1., value=.1) colormap = Enum(*lut_manager.lut_mode_list()) fliplut = Bool outlines_visible = Bool(default_value=True) outline_rep = Enum(outline_reps) outline_color = Color( default=options.config.get("mayavi_aligner", "outline_color")) line_width = Range(0.5, 10., value=float( options.config.get("mayavi_aligner", "line_width"))) point_size = Range(0.5, 10., value=float( options.config.get("mayavi_aligner", "point_size"))) epi_filter = Enum(None, "median", "gradient") filter_strength = Range(1, 20, value=3) scene_3d = Instance(MlabSceneModel, ()) scene_x = Instance(MlabSceneModel, ()) scene_y = Instance(MlabSceneModel, ()) scene_z = Instance(MlabSceneModel, ()) # The data source epi_src = Instance(Source) surf_src = Instance(Source) xfm = Instance(Filter) surf = Instance(Module) disable_render = Bool flip_fb = Bool flip_lr = Bool flip_ud = Bool save_callback = Instance(types.FunctionType) save_btn = Button(label="Save Transform") legend = Str(legend) #--------------------------------------------------------------------------- # Object interface #--------------------------------------------------------------------------- def __init__(self, pts, polys, epi, xfm=None, xfmtype='magnet', **traits): ''' Parameters ---------- xfm : array_like, optional The initial 4x4 rotation matrix into magnet space (epi with slice affine) ''' self.load_epi(epi, xfm, xfmtype) self.pts, self.polys = pts, polys self._undolist = [] self._redo = None super(Align, self).__init__(**traits) def load_epi(self, epifilename, xfm=None, xfmtype="magnet"): """Loads the EPI image from the specified epifilename. """ nii = nibabel.load(epifilename) self.epi_file = nii epi = nii.get_data().astype(float).squeeze() if epi.ndim > 3: epi = epi[:, :, :, 0] self.affine = nii.get_affine() base = nii.get_header().get_base_affine() self.base = base self.origin = base[:3, -1] self.spacing = np.diag(base)[:3] if xfm is None: self.startxfm = np.dot(base, np.linalg.inv(self.affine)) elif xfmtype == "magnet": self.startxfm = np.dot(np.dot(base, np.linalg.inv(self.affine)), xfm) else: print("using xfmtype %s" % xfmtype) self.startxfm = xfm self.center = self.spacing * nii.get_shape()[:3] / 2 + self.origin self.padshape = 2**(np.ceil(np.log2(np.array(epi.shape)))) epi = np.nan_to_num(epi) self.epi_orig = epi - epi.min() self.epi_orig /= self.epi_orig.max() self.epi_orig *= 2 self.epi_orig -= 1 self.epi = self.epi_orig.copy() #--------------------------------------------------------------------------- # Default values #--------------------------------------------------------------------------- def _position_default(self): return np.abs(self.origin) + ( (np.array(self.epi.shape) + 1) % 2) * np.abs(self.spacing) / 2 def _epi_src_default(self): sf = mlab.pipeline.scalar_field(self.epi, figure=self.scene_3d.mayavi_scene, name='EPI') sf.origin = self.origin sf.spacing = self.spacing return sf def _surf_src_default(self): return mlab.pipeline.triangular_mesh_source( self.pts[:, 0], self.pts[:, 1], self.pts[:, 2], self.polys, figure=self.scene_3d.mayavi_scene, name='Cortex') def _surf_default(self): smooth = mlab.pipeline.poly_data_normals( self.xfm, figure=self.scene_3d.mayavi_scene) smooth.filter.splitting = False surf = mlab.pipeline.surface(smooth, figure=self.scene_3d.mayavi_scene) surf.actor.mapper.scalar_visibility = 0 return surf def _xfm_default(self): xfm = mlab.pipeline.transform_data(self.surf_src, figure=self.scene_3d.mayavi_scene) def savexfm(info, evt): self._undolist.append(xfm.transform.matrix.to_array()) np.save("/tmp/last_xfm.npy", self.get_xfm()) xfm.widget.add_observer("EndInteractionEvent", savexfm) xfm.widget.add_observer("EndInteractionEvent", self.update_slabs) xfm.transform.set_matrix(self.startxfm.ravel()) xfm.widget.set_transform(xfm.transform) return xfm #--------------------------------------------------------------------------- # Scene activation callbacks #--------------------------------------------------------------------------- @on_trait_change('scene_3d.activated') def display_scene_3d(self): self.scene_3d.mlab.view(40, 50) self.scene_3d.scene.renderer.use_depth_peeling = True self.scene_3d.scene.background = (0, 0, 0) # Keep the view always pointing up self.scene_3d.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain( ) self.scene_3d.scene_editor.aligner = self self.opacity = float(options.config.get("mayavi_aligner", "opacity")) self.xfm.widget.enabled = False self.colormap = options.config.get("mayavi_aligner", "colormap") self.disable_render = True for ax in [self.x_axis, self.y_axis, self.z_axis]: ax.update_position() ax.reset_view() self.disable_render = False @on_trait_change('scene_x.activated') def display_scene_x(self): self.x_axis = XAxis(parent=self) @on_trait_change('scene_y.activated') def display_scene_y(self): self.y_axis = YAxis(parent=self) @on_trait_change('scene_z.activated') def display_scene_z(self): self.z_axis = ZAxis(parent=self) #--------------------------------------------------------------------------- # Traits callback #--------------------------------------------------------------------------- def _save_btn_changed(self): if self.save_callback is not None: self.save_callback(self) def _disable_render_changed(self): self.scene_3d.scene.disable_render = self.disable_render def _position_changed(self): self.disable_render = True for ax in [self.x_axis, self.y_axis, self.z_axis]: ax.update_position() self.disable_render = False def _outlines_visible_changed(self): self.disable_render = True for ax in [self.x_axis, self.y_axis, self.z_axis]: ax.toggle_outline() self.disable_render = False @on_trait_change("colormap, fliplut") def update_colormap(self): for ax in [self.x_axis, self.y_axis, self.z_axis]: if ax.ipw_3d and ax.ipw: ax.ipw_3d.parent.scalar_lut_manager.set( lut_mode=self.colormap, reverse_lut=self.fliplut) ax.ipw.parent.scalar_lut_manager.set(lut_mode=self.colormap, reverse_lut=self.fliplut) def _opacity_changed(self): self.surf.actor.property.opacity = self.opacity @on_trait_change("brightness,contrast") def update_brightness(self): self.epi_src.scalar_data = (self.epi * self.contrast) + self.brightness @on_trait_change("flip_ud") def update_flipud(self): #self.epi_src.scalar_data = self.epi_src.scalar_data[:,:,::-1] flip = np.eye(4) flip[2, 2] = -1 mat = self.xfm.transform.matrix.to_array() self.set_xfm(np.dot(mat, flip), "base") @on_trait_change("flip_lr") def update_fliplr(self): #self.epi_src.scalar_data = self.epi_src.scalar_data[::-1] flip = np.eye(4) flip[0, 0] = -1 mat = self.xfm.transform.matrix.to_array() self.set_xfm(np.dot(mat, flip), "base") @on_trait_change("flip_fb") def update_flipfb(self): #self.epi_src.scalar_data = self.epi_src.scalar_data[:,::-1] flip = np.eye(4) flip[1, 1] = -1 mat = self.xfm.transform.matrix.to_array() self.set_xfm(np.dot(mat, flip), "base") @on_trait_change("epi_filter, filter_strength") def update_epifilter(self): if self.epi_filter is None: self.epi = self.epi_orig.copy() elif self.epi_filter == "median": fstr = np.floor(self.filter_strength / 2) * 2 + 1 self.epi = volume.detrend_median(self.epi_orig.T, fstr).T elif self.epi_filter == "gradient": self.epi = volume.detrend_gradient(self.epi_orig.T, self.filter_strength).T self.update_brightness() def update_slabs(self, *args, **kwargs): self.disable_render = True for ax in [self.x_axis, self.y_axis, self.z_axis]: ax.update_slab() self.disable_render = False def get_xfm(self, xfmtype="magnet"): if xfmtype in ["anat->epicoord", "coord"]: ibase = np.linalg.inv(self.base) xfm = self.xfm.transform.matrix.to_array() return np.dot(ibase, xfm) elif xfmtype in ["anat->epibase", "base"]: return self.xfm.transform.matrix.to_array() elif xfmtype in ['anat->magnet', "magnet"]: ibase = np.linalg.inv(self.base) xfm = self.xfm.transform.matrix.to_array() return np.dot(self.affine, np.dot(ibase, xfm)) def set_xfm(self, matrix, xfmtype='magnet'): assert xfmtype in "magnet coord base".split(), "Unknown transform type" if xfmtype == "coord": matrix = np.dot(self.base, matrix) elif xfmtype == "magnet": iaff = np.linalg.inv(self.affine) matrix = np.dot(self.base, np.dot(iaff, matrix)) self.xfm.transform.set_matrix(matrix.ravel()) self.xfm.widget.set_transform(self.xfm.transform) self.xfm.update_pipeline() self.update_slabs() def undo(self): if len(self._undolist) > 0: self.xfm.transform.set_matrix(self._undolist[-1].ravel()) self.xfm.widget.set_transform(self.xfm.transform) self.xfm.update_pipeline() self.update_slabs() self._redo = self._undolist.pop() #--------------------------------------------------------------------------- # The layout of the dialog created #--------------------------------------------------------------------------- view = View(HGroup( Group( Item('scene_y', editor=SceneEditor(scene_class=FlatScene)), Item('scene_z', editor=SceneEditor(scene_class=FlatScene)), show_labels=False, ), Group( Item('scene_x', editor=SceneEditor(scene_class=FlatScene)), Item('scene_3d', editor=SceneEditor(scene_class=ThreeDScene)), show_labels=False, ), Group(Group( Item("save_btn", show_label=False, visible_when="save_callback is not None"), "brightness", "contrast", "epi_filter", Item('filter_strength', visible_when="epi_filter is not None"), "_", "opacity", "_", Item('colormap', editor=ImageEnumEditor(values=lut_manager.lut_mode_list(), cols=6, path=lut_manager.lut_image_dir)), "fliplut", "_", "flip_ud", "flip_lr", "flip_fb", "_", Item('outline_color', editor=ColorEditor()), 'outline_rep', 'line_width', 'point_size', '_', ), Group( Item('legend', editor=TextEditor(), style='readonly', show_label=False, emphasized=True, dock='vertical'), show_labels=False, ), orientation='vertical'), ), resizable=True, title='Aligner')
class Polygon(HasTraits): line_color = Trait(Color((0, 0, 0)), editor=ColorEditor())
class Polygon(HasTraits): line_color = Trait(wx.wxColour(0, 0, 0), editor=ColorEditor())
class multicolorfits_viewer(HasTraits): """The main window. Has instructions for creating and destroying the app. """ panel1 = Instance(ControlPanel) panel2 = Instance(ControlPanel) panel3 = Instance(ControlPanel) panel4 = Instance(ControlPanel) figure_combined = Instance(Figure, ()) image = Array() image_axes = Instance(Axes) image_axesimage = Instance(AxesImage) image_xsize = Int(256) image_ysize = Int(256) gamma = Float(2.2) tickcolor = Str( '0.9' ) #,auto_set=False,enter_set=True) #Apparently need to set to TextEditor explicitly below... tickcolor_picker = ColorTrait((230, 230, 230)) sexdec = Enum('Sexagesimal', 'Decimal') plotbutton_combined = Button(u"Plot Combined") plotbutton_inverted_combined = Button(u"Plot Inverted Combined") clearbutton_combined = Button(u"Clear Combined") save_the_image = Button(u"Save Image") save_the_fits = Button(u"Save RGB Fits") print_params = Button(u"Print Params") status_string_left = Str('') status_string_right = Str('') def _panel1_default(self): return ControlPanel() #figure=self.figure) def _panel2_default(self): return ControlPanel() #figure=self.figure) def _panel3_default(self): return ControlPanel() #figure=self.figure) def _panel4_default(self): return ControlPanel() #figure=self.figure) def __init__(self): super(multicolorfits_viewer, self).__init__() self._init_params( ) #Set placeholder things like the WCS, tick color, map units... self.image = self._fresh_image() #Sets a blank image self.image_axes = self.figure_combined.add_subplot(111, aspect=1) self.image_axesimage = self.image_axes.imshow(self.image, cmap='gist_gray', origin='lower', interpolation='nearest') self.image_axes.set_xlabel(self.xlabel) self.image_axes.set_ylabel(self.ylabel) self.image_axes.tick_params( axis='both', color=self.tickcolor) #colors=... also sets label color try: self.image_axes.coords.frame.set_color( self.tickcolor ) #Updates the frame color. .coords won't exist until WCS set except: [ self.image_axes.spines[s].set_color(self.tickcolor) for s in ['top', 'bottom', 'left', 'right'] ] view = View(Item("gamma",label=u"Gamma",show_label=True), Item('_'), HSplit( Group( Group(Item('panel1', style="custom",show_label=False),label='Image 1'), Group(Item('panel2', style="custom",show_label=False),label='Image 2'), Group(Item('panel3', style="custom",show_label=False),label='Image 3'), Group(Item('panel4', style="custom",show_label=False),label='Image 4'), orientation='horizontal',layout='tabbed',springy=True), VGroup( HGroup( Item('tickcolor',label='Tick Color',show_label=True, \ tooltip='Color of ticks: standard name float[0..1], or #hex', \ editor=TextEditor(auto_set=False, enter_set=True,)), Item('tickcolor_picker',label='Pick',show_label=True,editor=ColorEditor()), Item('sexdec',label='Coordinate Style',tooltip=u'Display coordinates in sexagesimal or decimal', \ show_label=True), ), Item('figure_combined', editor=MPLFigureEditor(),show_label=False, width=900, height=800,resizable=True), HGroup( Item('plotbutton_combined', tooltip=u"Plot the image",show_label=False), Item('plotbutton_inverted_combined', tooltip=u"Plot the inverted image",show_label=False), Item('clearbutton_combined',tooltip=u'Clear the combined figure',show_label=False), Item("save_the_image", tooltip=u"Save current image. Mileage may vary...",show_label=False), Item("save_the_fits", tooltip=u"Save RGB frames as single fits file with header.",show_label=False), Item("print_params", tooltip=u"Print out current settings for use in manual image scripting.",show_label=False), ), #HGroup ), #VGroup show_labels=False,), resizable=True, height=0.75, width=0.75, statusbar = [StatusItem(name = 'status_string_left', width = 0.5), StatusItem(name = 'status_string_right', width = 0.5)], title=u"Fits Multi-Color Combiner",handler=MPLInitHandler ) #View def _init_params(self): plt.rcParams.update({'font.family': 'serif','xtick.major.size':6,'ytick.major.size':6, \ 'xtick.major.width':1.,'ytick.major.width':1., \ 'xtick.direction':'in','ytick.direction':'in'}) try: plt.rcParams.update({ 'xtick.top': True, 'ytick.right': True }) #apparently not in mpl v<2.0... except: pass #Make a workaround for mpl<2.0 later... self.datamin_initial = 0. self.datamax_initial = 1. self.datamin = 0. self.datamax = 1. #This will be the displayed value of the scaling min/max #self.mapunits='Pixel Value' #self.tickcolor='0.5'#'white', 'black', '0.5' self.wcs = WCS() self.xlabel = 'x' self.ylabel = 'y' def _fresh_image(self): #self.norm=ImageNormalize(self.image,stretch=scaling_fns['linear']() ) blankdata = np.zeros([100, 100]) blankdata[-1, -1] = 1 return blankdata def update_radecpars(self): self.rapars = self.image_axes.coords[0] self.decpars = self.image_axes.coords[1] self.rapars.set_ticks(color=self.tickcolor) self.decpars.set_ticks(color=self.tickcolor) self.rapars.set_ticks(number=6) self.decpars.set_ticks(number=6) #self.rapars.set_ticklabel(size=8); self.decpars.set_ticklabel(size=8); #size here means the tick length ##self.rapars.set_ticks(spacing=10*u.arcmin, color='white', exclude_overlapping=True) ##self.decpars.set_ticks(spacing=5*u.arcmin, color='white', exclude_overlapping=True) self.rapars.display_minor_ticks(True) #self.rapars.set_minor_frequency(10) self.decpars.display_minor_ticks(True) if self.sexdec == 'Sexagesimal': self.rapars.set_major_formatter('hh:mm:ss.ss') self.decpars.set_major_formatter('dd:mm:ss.ss') #self.rapars.set_separator(('$^\mathrm{H}$', "'", '"')) self.rapars.set_separator(('H ', "' ", '" ')) #format_xcoord=lambda x,y: '{}i$^\mathrm{H}${}{}{}'.format(x[0],x[1],"'",x[2],'"') #self.image_axes.format_coord=format_xcoord else: self.rapars.set_major_formatter('d.dddddd') self.decpars.set_major_formatter('d.dddddd') ##self.decpars.ticklabels.set_rotation(45) #Rotate ticklabels ##self.decpars.ticklabels.set_color(xkcdrust) #Ticklabel Color @on_trait_change('tickcolor') def update_tickcolor(self): try: #Catch case when you've predefined a color variable in hex string format, e.g., mynewred='#C11B17' #--> Need to do this first, otherwise traits throws a fit up the stack even despite the try/except check globals()[ self.tickcolor] #This check should catch undefined inputs self.image_axes.tick_params(axis='both', color=globals()[self.tickcolor]) self.image_axes.coords.frame.set_color(self.tickcolor) self.tickcolor_picker = hex_to_rgb(globals()[self.tickcolor]) self.status_string_right = 'Tick color changed to ' + self.tickcolor except: try: self.tickcolor = to_hex(self.tickcolor) try: self.update_radecpars() except: self.image_axes.tick_params(axis='both', color=to_hex(self.tickcolor)) self.image_axes.coords.frame.set_color( to_hex(self.tickcolor)) self.status_string_right = 'Tick color changed to ' + self.tickcolor except: self.status_string_right = "Color name %s not recognized. Must be standard mpl.colors string, float[0..1] or #hex string" % ( self.tickcolor) try: self.tickcolor_picker = hex_to_rgb(to_hex( self.tickcolor)) #update the picker color... except: pass self.figure_combined.canvas.draw() @on_trait_change('tickcolor_picker') def update_tickcolorpicker(self): #print self.tickcolor_picker.name() self.tickcolor = self.tickcolor_picker.name() @on_trait_change('sexdec') def update_sexdec(self): self.update_radecpars() self.figure_combined.canvas.draw() self.status_string_right = 'Coordinate style changed to ' + self.sexdec def _plotbutton_combined_fired(self): try: self.panel1.data except: self.status_string_right = "No fits file loaded yet!" return #self.image=self.panel1.data self.wcs = WCS(self.panel1.hdr) self.hdr = self.panel1.hdr self.combined_RGB = combine_multicolor([ pan.image_colorRGB for pan in [self.panel1, self.panel2, self.panel3, self.panel4] if pan.in_use == True ], gamma=self.gamma) ###Using this command is preferable, as long as the projection doesn't need to be updated... # The home zoom button will work, but no WCS labels because projection wasn't set during init. #self.image_axesimage.set_data(self.data) ###Using this set instead properly updates the axes labels to WCS, but the home zoom button won't work self.figure_combined.clf() self.image_axes = self.figure_combined.add_subplot(111, aspect=1, projection=self.wcs) self.image_axesimage = self.image_axes.imshow(self.combined_RGB, origin='lower', interpolation='nearest') self.update_radecpars() self.figure_combined.canvas.draw() self.status_string_right = "Plot updated" def _plotbutton_inverted_combined_fired(self): try: self.panel1.data except: self.status_string_right = "No fits file loaded yet!" return self.wcs = WCS(self.panel1.hdr) self.hdr = self.panel1.hdr self.combined_RGB = combine_multicolor( [ pan.image_colorRGB for pan in [self.panel1, self.panel2, self.panel3, self.panel4] if pan.in_use == True ], inverse=True, gamma=self.gamma, ) self.figure_combined.clf() self.image_axes = self.figure_combined.add_subplot(111, aspect=1, projection=self.wcs) self.image_axesimage = self.image_axes.imshow(self.combined_RGB, origin='lower', interpolation='nearest') self.update_radecpars() self.figure_combined.canvas.draw() self.status_string_right = "Plot updated" def _clearbutton_combined_fired(self): try: del self.combined_RGB #If clear already pressed once, data will already have been deleted... except: pass self.in_use = False self.figure_combined.clf() self.image = self._fresh_image() self.image_axes = self.figure_combined.add_subplot(111, aspect=1) self.image_axesimage = self.image_axes.imshow(self.image, cmap='gist_gray', origin='lower', interpolation='nearest') self.xlabel = 'x' self.ylabel = 'y' self.image_axes.set_xlabel(self.xlabel) self.image_axes.set_ylabel(self.ylabel) self.image_axes.tick_params(axis='both', color=self.tickcolor) try: self.image_axes.coords.frame.set_color(self.tickcolor) except: self.tickcolor_picker = hex_to_rgb(to_hex(self.tickcolor)) self.figure_combined.canvas.draw() self.status_string_right = "Plot cleared" def setup_mpl_events(self): self.image_axeswidget = AxesWidget(self.image_axes) self.image_axeswidget.connect_event('motion_notify_event', self.image_on_motion) self.image_axeswidget.connect_event('figure_leave_event', self.on_cursor_leave) self.image_axeswidget.connect_event('figure_enter_event', self.on_cursor_enter) self.image_axeswidget.connect_event('button_press_event', self.image_on_click) def image_on_motion(self, event): if event.xdata is None or event.ydata is None: return x = int(np.round(event.xdata)) y = int(np.round(event.ydata)) if ((x >= 0) and (x < self.image.shape[1]) and (y >= 0) and (y < self.image.shape[0])): imval = self.image[y, x] self.status_string_left = "x,y={},{} {:.5g}".format(x, y, imval) else: self.status_string_left = "" def image_on_click(self, event): if event.xdata is None or event.ydata is None or event.button is not 1: return #Covers when click outside of main plot #print event x = int( np.round(event.xdata) ) #xdata is the actual pixel position. xy is in 'display space', i.e. pixels in the canvas y = int(np.round(event.ydata)) xwcs, ywcs = self.wcs.wcs_pix2world([[x, y]], 0)[0] #print xwcs,ywcs if ((x >= 0) and (x < self.image.shape[1]) and (y >= 0) and (y < self.image.shape[0])): imval = self.image[y, x] self.status_string_right = "x,y=[{},{}], RA,DEC=[{}, {}], value = {:.5g}".format( x, y, xwcs, ywcs, imval) #self.status_string_right = "x,y[{},{}] = {:.3f},{:.3f} {:.5g}".format(x, y,event.xdata,event.ydata, imval) else: self.status_string_right = "" ## left-click: event.button = 1, middle-click: event.button=2, right-click: event.button=3. ## For double-click, event.dblclick = False for first click, True on second #print event.button, event.dblclick def on_cursor_leave(self, event): QApplication.restoreOverrideCursor() self.status_string_left = '' def on_cursor_enter(self, event): QApplication.setOverrideCursor(Qt.CrossCursor) def _save_the_image_fired(self): dlg = FileDialog(action='save as') if dlg.open() == OK: plt.savefig(dlg.path, size=(800, 800), dpi=300) def _save_the_fits_fired(self): #Generate a generic header with correct WCS and comments about the colors that made it #... come back and finish this later... dlg = FileDialog(action='save as') if dlg.open() == OK: pyfits.writeto( dlg.path, np.swapaxes(np.swapaxes(self.combined_RGB, 0, 2), 2, 1), self.hdr) def _print_params_fired(self): print('\n\nRGB Image plot params:') pan_i = 0 for pan in [self.panel1, self.panel2, self.panel3, self.panel4]: pan_i += 1 if pan.in_use == True: print('image%i: ' % (pan_i)) print(' vmin = %.3e , vmax = %.3e, scale = %s' % (pan.datamin, pan.datamax, pan.image_scale)) print(" image color = '%s'" % (pan.imagecolor)) print("gamma = %.1f , tick color = '%s'\n" % (self.gamma, self.tickcolor))
class ControlPanel(HasTraits): """This is the control panel where the various parameters for the images are specified """ gamma = 2.2 fitsfile = File(filter=[u"*.fits"]) image_figure = Instance(Figure, ()) image = Array() image_axes = Instance(Axes) image_axesimage = Instance(AxesImage) image_xsize = Int(256) image_ysize = Int(256) datamin = Float(0.0, auto_set=False, enter_set=True) #Say, in mJy datamax = Float( 1.0, auto_set=False, enter_set=True ) #auto_set=input set on each keystroke, enter_set=set after Enter percent_min = Range(value=0.0, low=0.0, high=100.) percent_max = Range(value=100.0, low=0.0, high=100.) #Percentile of data values for rescaling minmaxbutton = Button('Min/Max') zscalebutton = Button('Zscale') image_scale = Str('linear') scale_dropdown = Enum( ['linear', 'sqrt', 'squared', 'log', 'power', 'sinh', 'asinh']) imagecolor = Str('#FFFFFF') imagecolor_picker = ColorTrait((255, 255, 255)) #plotbeam_button=Button('Add Beam (FWHM)') plotbutton_individual = Button(u"Plot Single") plotbutton_inverted_individual = Button(u"Plot Inverted Single") clearbutton_individual = Button(u"Clear Single") status_string_left = Str('') status_string_right = Str('') def __init__(self): self._init_params( ) #Set placeholder things like the WCS, tick color, map units... self.image = self._fresh_image() #Sets a blank image self.image_axes = self.image_figure.add_subplot(111, aspect=1) self.image_axesimage = self.image_axes.imshow(self.image, cmap='gist_gray', origin='lower', interpolation='nearest') self.image_axes.axis('off') view = View( HSplit( VGroup( Item("fitsfile", label=u"Select 2D FITS file", show_label=True), #,height=100), HGroup( VGroup( Item('plotbutton_individual', tooltip=u"Plot the single image",show_label=False), Item('plotbutton_inverted_individual', tooltip=u"Plot the single inverted image",show_label=False), Item('clearbutton_individual', tooltip=u"Clear the single image",show_label=False), Item('_'), Item('imagecolor',label='Image Color',show_label=True, \ tooltip='Color of ticks: standard name float[0..1], or #hex', \ editor=TextEditor(auto_set=False, enter_set=True,)), Item('imagecolor_picker',label='Pick',show_label=True,editor=ColorEditor()), ), Item('image_figure', editor=MPLFigureEditor(), show_label=False, width=300, height=300,resizable=True), ), HGroup(Item('datamin', tooltip=u"Minimum data val for scaling", show_label=True), Item('datamax', tooltip=u"Maximum data val for scaling", show_label=True)), Item('percent_min', tooltip=u"Min. percentile for scaling", show_label=True), Item('percent_max', tooltip=u"Max. percentile for scaling", show_label=True), HGroup(Item('minmaxbutton', tooltip=u"Reset to data min/max", show_label=False), Item('zscalebutton', tooltip=u"Compute scale min/max from zscale algorithm", show_label=False)), Item('scale_dropdown',label='Scale',show_label=True), ), #End of Left column ), #End of HSplit resizable=True, #height=0.75, width=0.75, #title=u"Multi-Color Image Combiner", handler=MPLInitHandler, statusbar = [StatusItem(name = 'status_string_left', width = 0.5), StatusItem(name = 'status_string_right', width = 0.5)] ) #End of View def _init_params(self): self.in_use = False plt.rcParams.update({'font.family': 'serif','xtick.major.size':6,'ytick.major.size':6, \ 'xtick.major.width':1.,'ytick.major.width':1., \ 'xtick.direction':'in','ytick.direction':'in'}) try: plt.rcParams.update({ 'xtick.top': True, 'ytick.right': True }) #apparently not in mpl v<2.0... except: pass #Make a workaround for mpl<2.0 later... self.datamin_initial = 0. self.datamax_initial = 1. self.datamin = 0. self.datamax = 1. #This will be the displayed value of the scaling min/max def _fresh_image(self): blankdata = np.zeros([100, 100]) blankdata[-1, -1] = 1 return blankdata def _fitsfile_changed(self): self.data, self.hdr = pyfits.getdata(self.fitsfile, header=True) force_hdr_floats( self.hdr ) #Ensure that WCS cards such as CDELT are floats instead of strings naxis = int(self.hdr['NAXIS']) if naxis > 2: #print('Dropping Extra axes') self.hdr = force_hdr_to_2D(self.hdr) try: self.data = self.data[0, 0, :, :] except: self.data = self.data[0, :, :] self.status_string_right = 'Dropped extra axes' self.datamax_initial = np.asscalar(np.nanmax(self.data)) self.datamin_initial = np.asscalar(np.nanmin(self.data)) self.datamax = np.asscalar(np.nanmax(self.data)) self.datamin = np.asscalar(np.nanmin(self.data)) self.in_use = True #@on_trait_change('imagecolor') #def update_imagecolor(self): def _imagecolor_changed(self): try: #Catch case when you've predefined a color variable in hex string format, e.g., mynewred='#C11B17' #--> Need to do this first, otherwise traits throws a fit up the stack even despite the try/except check globals()[ self.imagecolor] #This check should catch undefined inputs self.imagecolor_picker = hex_to_rgb(globals()[self.imagecolor]) self.status_string_right = 'Image color changed to ' + self.imagecolor except: try: self.imagecolor = to_hex(self.imagecolor) self.status_string_right = 'Image color changed to ' + self.imagecolor except: self.status_string_right = "Color name %s not recognized. Must be standard mpl.colors string, float[0..1] or #hex string" % ( self.imagecolor) try: self.imagecolor_picker = hex_to_rgb(to_hex( self.imagecolor)) #update the picker color... except: pass ### self.image_greyRGB and self.image_colorRGB may not yet be instantiated if the color is changed before clicking 'plot' try: self.image_colorRGB = colorize_image(self.image_greyRGB, self.imagecolor, colorintype='hex', gammacorr_color=self.gamma) except: pass try: self.image_axesimage.set_data(self.image_colorRGB**(1. / self.gamma)) except: pass self.in_use = True self.image_figure.canvas.draw() #@on_trait_change('imagecolor_picker') #def update_imagecolorpicker(self): def _imagecolor_picker_changed(self): #print self.tickcolor_picker.name() self.imagecolor = self.imagecolor_picker.name() #@on_trait_change('percent_min') #def update_scalepercmin(self): def _percent_min_changed(self): self.datamin = np.nanpercentile(self.data, self.percent_min) self.data_scaled = ( scaling_fns[self.image_scale]() + ManualInterval(vmin=self.datamin, vmax=self.datamax))(self.data) self.image_greyRGB = ski_color.gray2rgb( adjust_gamma(self.data_scaled, self.gamma)) self.image_colorRGB = colorize_image(self.image_greyRGB, self.imagecolor, colorintype='hex', gammacorr_color=self.gamma) self.image_axesimage.set_data(self.image_colorRGB**(1. / self.gamma)) self.image_figure.canvas.draw() self.status_string_right = "Updated scale using percentiles" #@on_trait_change('percent_max') #def update_scalepercmax(self): def _percent_max_changed(self): self.datamax = np.nanpercentile(self.data, self.percent_max) self.data_scaled = ( scaling_fns[self.image_scale]() + ManualInterval(vmin=self.datamin, vmax=self.datamax))(self.data) self.image_greyRGB = ski_color.gray2rgb( adjust_gamma(self.data_scaled, self.gamma)) self.image_colorRGB = colorize_image(self.image_greyRGB, self.imagecolor, colorintype='hex', gammacorr_color=self.gamma) self.image_axesimage.set_data(self.image_colorRGB**(1. / self.gamma)) self.image_figure.canvas.draw() self.status_string_right = "Updated scale using percentiles" ### Very slow to update datamin and datamax as well as percs... Can comment these if desired and just hit plot after datamin #@on_trait_change('datamin') #def update_datamin(self): self.percent_min=np.round(percentileofscore(self.data.ravel(),self.datamin,kind='strict'),2) def _datamin_changed(self): self.percent_min = np.round( percentileofscore(self.data.ravel(), self.datamin, kind='strict'), 2) #@on_trait_change('datamax') #def update_datamax(self): self.percent_max=np.round(percentileofscore(self.data.ravel(),self.datamax,kind='strict'),2) def _datamax_changed(self): self.percent_max = np.round( percentileofscore(self.data.ravel(), self.datamax, kind='strict'), 2) #@on_trait_change('scale_dropdown') #def update_image_scale(self): def _scale_dropdown_changed(self): self.image_scale = self.scale_dropdown #self.norm=ImageNormalize(self.sregion,stretch=scaling_fns[self.image_scale]() ) self.data_scaled = ( scaling_fns[self.image_scale]() + ManualInterval(vmin=self.datamin, vmax=self.datamax))(self.data) #*** Instead, should I just integrate my imscale class here instead of astropy? ... self.image_greyRGB = ski_color.gray2rgb( adjust_gamma(self.data_scaled, self.gamma)) self.image_colorRGB = colorize_image(self.image_greyRGB, self.imagecolor, colorintype='hex', gammacorr_color=self.gamma) self.image_axesimage.set_data(self.image_colorRGB**(1. / self.gamma)) self.in_use = True self.image_figure.canvas.draw() self.status_string_right = 'Image scale function changed to ' + self.image_scale def _minmaxbutton_fired(self): self.datamin = self.datamin_initial self.datamax = self.datamax_initial #self.image_axesimage.norm.vmin=self.datamin #self.image_axesimage.norm.vmax=self.datamax self.percent_min = np.round( percentileofscore(self.data.ravel(), self.datamin, kind='strict'), 2) self.percent_max = np.round( percentileofscore(self.data.ravel(), self.datamax, kind='strict'), 2) #self.image_figure.canvas.draw() self.status_string_right = "Scale reset to min/max" def _zscalebutton_fired(self): tmpZscale = ZScaleInterval().get_limits(self.data) self.datamin = float(tmpZscale[0]) self.datamax = float(tmpZscale[1]) self.percent_min = np.round( percentileofscore(self.data.ravel(), self.datamin, kind='strict'), 2) self.percent_max = np.round( percentileofscore(self.data.ravel(), self.datamax, kind='strict'), 2) #self.image_figure.canvas.draw() self.status_string_right = "Min/max determined by zscale" def _plotbutton_individual_fired(self): try: self.data except: self.status_string_right = "No fits file loaded yet!" return #self.image=self.data ###Using this command is preferable, as long as the projection doesn't need to be updated... # The home zoom button will work, but no WCS labels because projection wasn't set during init. #Scale the data to [0,1] range self.data_scaled = ( scaling_fns[self.image_scale]() + ManualInterval(vmin=self.datamin, vmax=self.datamax))(self.data) #Convert scale[0,1] image to greyscale RGB image self.image_greyRGB = ski_color.gray2rgb( adjust_gamma(self.data_scaled, self.gamma)) self.image_colorRGB = colorize_image(self.image_greyRGB, self.imagecolor, colorintype='hex', gammacorr_color=self.gamma) self.image_axesimage.set_data(self.image_colorRGB**(1. / self.gamma)) ###Using this set instead properly updates the axes labels to WCS, but the home zoom button won't work #self.image_figure.clf() #self.image_axes = self.image_figure.add_subplot(111,aspect=1)#,projection=self.wcs) #self.image_axesimage = self.image_axes.imshow(self.image, cmap=self.image_cmap,origin='lower',interpolation='nearest', norm=self.norm) self.percent_min = np.round( percentileofscore(self.data.ravel(), self.datamin, kind='strict'), 2) self.percent_max = np.round( percentileofscore(self.data.ravel(), self.datamax, kind='strict'), 2) self.in_use = True #self.update_radecpars() self.image_figure.canvas.draw() self.status_string_right = "Plot updated" def _plotbutton_inverted_individual_fired(self): try: self.data except: self.status_string_right = "No fits file loaded yet!" return self.data_scaled = ( scaling_fns[self.image_scale]() + ManualInterval(vmin=self.datamin, vmax=self.datamax))(self.data) self.image_greyRGB = ski_color.gray2rgb( adjust_gamma(self.data_scaled, self.gamma)) self.image_colorRGB = colorize_image(self.image_greyRGB, hexinv(self.imagecolor), colorintype='hex', gammacorr_color=self.gamma) #self.image_axesimage.set_data(1.-self.image_colorRGB**(1./self.gamma)) self.image_axesimage.set_data( combine_multicolor([ self.image_colorRGB, ], gamma=self.gamma, inverse=True)) self.percent_min = np.round( percentileofscore(self.data.ravel(), self.datamin, kind='strict'), 2) self.percent_max = np.round( percentileofscore(self.data.ravel(), self.datamax, kind='strict'), 2) self.in_use = True self.image_figure.canvas.draw() self.status_string_right = "Plot updated" def _clearbutton_individual_fired(self): try: del self.data, self.data_scaled, self.image_greyRGB self.image_colorRGB #In case clear already pressed once except: pass self.in_use = False self.image_figure.clf() self.image = self._fresh_image() self.image_axes = self.image_figure.add_subplot(111, aspect=1) self.image_axesimage = self.image_axes.imshow(self.image, cmap='gist_gray', origin='lower', interpolation='nearest') self.image_axes.axis('off') self.image_figure.canvas.draw() self.status_string_right = "Plot cleared" def setup_mpl_events(self): self.image_axeswidget = AxesWidget(self.image_axes) self.image_axeswidget.connect_event('motion_notify_event', self.image_on_motion) self.image_axeswidget.connect_event('figure_leave_event', self.on_cursor_leave) self.image_axeswidget.connect_event('figure_enter_event', self.on_cursor_enter) self.image_axeswidget.connect_event('button_press_event', self.image_on_click) def image_on_motion(self, event): if event.xdata is None or event.ydata is None: return x = int(np.round(event.xdata)) y = int(np.round(event.ydata)) if ((x >= 0) and (x < self.image.shape[1]) and (y >= 0) and (y < self.image.shape[0])): imval = self.image[y, x] self.status_string_left = "x,y={},{} {:.5g}".format(x, y, imval) else: self.status_string_left = "" def image_on_click(self, event): if event.xdata is None or event.ydata is None or event.button is not 1: return #Covers when click outside of main plot #print event x = int( np.round(event.xdata) ) #xdata is the actual pixel position. xy is in 'display space', i.e. pixels in the canvas y = int(np.round(event.ydata)) #xwcs,ywcs=self.wcs.wcs_pix2world([[x,y]],0)[0]; #print xwcs,ywcs if ((x >= 0) and (x < self.image.shape[1]) and (y >= 0) and (y < self.image.shape[0])): imval = self.image[y, x] #self.status_string_right = "x,y=[{},{}], RA,DEC=[{}, {}], value = {:.5g}".format(x, y,xwcs,ywcs, imval) self.status_string_right = "x,y[{},{}] = {:.3f},{:.3f} {:.5g}".format( x, y, event.xdata, event.ydata, imval) else: self.status_string_right = "" ## left-click: event.button = 1, middle-click: event.button=2, right-click: event.button=3. ## For double-click, event.dblclick = False for first click, True on second #print event.button, event.dblclick def on_cursor_leave(self, event): QApplication.restoreOverrideCursor() self.status_string_left = '' def on_cursor_enter(self, event): QApplication.setOverrideCursor(Qt.CrossCursor)