def __init__(self): HasTraits.__init__(self) x, y, z = ogrid[-10:10, -10:10, -10:10] scalars = x**2 + y**2 + z**2 src = ArraySource(scalar_data=scalars) self.scene.mayavi_scene.add_child(src) src.add_module(IsoSurface())
def __init__(self): HasTraits.__init__(self) x, y, z = ogrid[-10:10:100j, -10:10:100j, -10:10:100j] scalars = sin(x*y*z)/(x*y*z) src = ArraySource(scalar_data=scalars) self.scene.mayavi_scene.add_child(src) src.add_module(IsoSurface())
def __init__(self): HasTraits.__init__(self) x, y, z = ogrid[-10:10, -10:10, -10:10] scalars = x ** 2 + y ** 2 + z ** 2 src = ArraySource(scalar_data=scalars) self.scene.mayavi_scene.add_child(src) src.add_module(IsoSurface())
def on_patient_loaded(self, msg): self.data = msg.data ct_data = self.data.voxelplan_images x, y, z = ogrid[0:1:ct_data.dimx, 0:1:ct_data.dimy, 0:1:ct_data.dimz] src = ArraySource(scalar_data=ct_data.cube) self.scene.engine.add_source(src) src.add_module(IsoSurface())
def __init__(self): HasTraits.__init__(self) # Create some data, and plot it using the embedded scene's engine x, y, z = ogrid[-10:10:100j, -10:10:100j, -10:10:100j] scalars = sin(x * y * z) / (x * y * z) src = ArraySource(scalar_data=scalars) self.scene.engine.add_source(src) src.add_module(IsoSurface())
def __init__(self): HasTraits.__init__(self) # Create some data, and plot it using the embedded scene's engine x, y, z = ogrid[-10:10:100j, -10:10:100j, -10:10:100j] scalars = sin(x*y*z)/(x*y*z) src = ArraySource(scalar_data=scalars) self.scene.engine.add_source(src) src.add_module(IsoSurface())
def visStack(v, opacity=.5, color=(1, 0, 0), mode=''): if mode != 'same': mlab.figure(bgcolor=(1, 1, 1)) s = mlab.get_engine() # Returns the running mayavi engine. scene = s.current_scene # Returns the current scene. scene.scene.disable_render = True # for speed origion = [0, 0, 0] label = 'Segmentation' A = ArraySource(scalar_data=v) A.origin = np.array(origion) D = s.add_source(A) # add the data to the Mayavi engine #Apply gaussain 3d filter to smooth visualization # F=mlab.pipeline.user_defined(D, filter='ImageGaussianSmooth') # F.filter.set_standard_deviation(0,0,0) contour = Contour() s.add_filter(contour) # smooth = mlab.pipeline.user_defined(contour, filter='SmoothPolyDataFilter') # smooth.filter.number_of_iterations = 1 # smooth.filter.relaxation_factor = 0 surface = Surface() s.add_module(surface) surface.module_manager.scalar_lut_manager.lut_mode = u'coolwarm' surface.module_manager.scalar_lut_manager.reverse_lut = True surface.actor.property.opacity = opacity surface.actor.mapper.scalar_visibility = False surface.actor.property.color = color #color return surface
def affine_img_src(data, affine, scale=1, name='AffineImage', reverse_x=False): """ Make a Mayavi source defined by a 3D array and an affine, for wich the voxel of the 3D array are mapped by the affine. Parameters ----------- data: 3D ndarray The data arrays affine: (4 x 4) ndarray The (4 x 4) affine matrix relating voxels to world coordinates. scale: float, optional An optional addition scaling factor. name: string, optional The name of the Mayavi source created. reverse_x: boolean, optional Reverse the x (lateral) axis. Useful to compared with images in radiologic convention. Notes ------ The affine should be diagonal. """ # Late import to avoid triggering wx imports before needed. try: from mayavi.sources.api import ArraySource except ImportError: # Try out old install of Mayavi, with namespace packages from enthought.mayavi.sources.api import ArraySource center = np.r_[0, 0, 0, 1] spacing = np.diag(affine)[:3] origin = np.dot(affine, center)[:3] if reverse_x: # Radiologic convention spacing[0] *= -1 origin[0] *= -1 src = ArraySource(scalar_data=np.asarray(data, dtype=np.float), name=name, spacing=scale * spacing, origin=scale * origin) return src
class QspaceViewController(HasTraits): """MayaVi Scene View, for showing the q-space visualization.""" #Make an engine just for this frame engine = Instance(Engine, args=()) #Create the scene scene = Instance(MlabSceneModel, ()) def _scene_default(self): """Default initializer for scene""" self.engine.start() return MlabSceneModel(engine=self.engine) #3D view of the scene. view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene), resizable=True, show_label=False), resizable=True) #The FrameQspaceView that is calling this. parent_frame = None #Text to show when mouse is not over a reflection MOUSE_TEXT_WITH_NO_REFLECTION = "hkl: N/A" #Scaling applied to sphere from the given size parameter SPHERE_SCALING = 0.05 #Reflections shown as pixels pixel_view = False #----------------------------------------------------------------------------------------------- def __init__(self, parent_frame): #Initialize the parent class HasTraits.__init__(self) #Record the frame calling this self.parent_frame = parent_frame #And more initialization self.warning_text_visible = False self.form_is_closing = False self.init_view_objects() #Subscribe to messages model.messages.subscribe(self.update_stats_panel, model.messages.MSG_EXPERIMENT_QSPACE_CHANGED) model.messages.subscribe(self.update_data_volume, model.messages.MSG_EXPERIMENT_QSPACE_CHANGED) model.messages.subscribe( self.update_data_points, model.messages.MSG_EXPERIMENT_REFLECTIONS_CHANGED) model.messages.subscribe( self.update_stats_panel, model.messages.MSG_EXPERIMENT_REFLECTIONS_CHANGED) model.messages.subscribe( self.init_view_objects, model.messages.MSG_EXPERIMENT_QSPACE_SETTINGS_CHANGED) #Do an initial data update self.update_stats_panel() self.update_data_volume() self.update_data_points() self.view_mode_changed() #Don't open a child frame at first self.reflection_info_child_frame = None #----------------------------------------------------------------------------------------------- def __del__(self): print "QspaceViewController.__del__" self.cleanup() #----------------------------------------------------------------------------------------------- def cleanup(self): """Clean-up routine for closing the view.""" #Marker to avoid a bug self.form_is_closing = True #Remove the scene from engine self.engine.remove_scene(self.scene) self.engine.stop() model.messages.unsubscribe(self.update_stats_panel) model.messages.unsubscribe(self.update_data_volume) model.messages.unsubscribe(self.update_data_points) model.messages.unsubscribe(self.init_view_objects) #Also close child windows if not self.reflection_info_child_frame is None: self.reflection_info_child_frame.Destroy() #----------------------------------------------------------------------------------------------- def init_pickers(self): """Initialize the pickers objects.""" #This vtk picker object will be used later self.pointpicker = tvtk.PointPicker() interactor = self.scene.interactor if interactor is None: print "Bad interactor! : ", interactor return interactor.add_observer('RightButtonPressEvent', self.on_button_press) interactor.add_observer('MouseMoveEvent', self.on_mouse_move) interactor.add_observer('RightButtonReleaseEvent', self.on_button_release) #----------------------------------------------------------------------------------------------- def get_reflection_under_mouse(self, obj, verbose=False): """Return the Reflection object under the mouse cursor. Parameters: obj: the object passed to the mouse observer (contains the mouse coordinates).""" #Directly create a VTK point picker. This avoids the mouse cursor going "wait" # when simply requesting a point. pp = self.pointpicker x, y = obj.GetEventPosition() pp.pick((float(x), float(y), 0.0), self.scene.renderer) #These are the closest coordinates found coordinates = pp.pick_position #Lets go through all the actors found for (i, act) in enumerate(pp.actors): if id(act) == id(self.points_module_surface.actor.actor): #Yay, we found a point on that actor - the points surface #That way, we ignore any elements in front coordinates = pp.picked_positions[i] break if id(act) == id(self.points_module_glyph.actor.actor): #If in glyph mode, we can use that one instead. # But the points surface is better. coordinates = pp.picked_positions[i] #Look for the closest reflection to those coordinates return model.experiment.exp.get_reflection_closest_to_q( column(coordinates)) #----------------------------------------------------------------------------------------------- def on_button_press(self, obj, evt): """Event handler for the 3d Scene, called when right-mouse-button is clicked.""" self.mouse_mvt = False refl = self.get_reflection_under_mouse(obj, True) self.select_reflection(refl) #----------------------------------------------------------------------------------------------- def select_reflection(self, refl): """Select the Reflection refl, open the frame_reflection_info, and highlight the selected reflection""" #Open the frame_reflection_info (if needed) and set it to the reflection we clicked frm = self.open_frame_reflection_info() frm.Show() frm.panel.set_reflection(refl, update_textboxes=True) #This'll move the cube self.on_user_changing_reflection_selected(refl) #Return the form that was opened return frm #----------------------------------------------------------------------------------------------- def open_frame_reflection_info(self): """Open the frame_reflection_info frame if not already open. Makes sure that it is initialized correctly.""" if self.reflection_info_child_frame is None: #Make a new one frm = frame_reflection_info.FrameReflectionInfo(self.parent_frame, can_follow=True, do_follow=True) #Also make sure we observe changes frm.panel.add_observer(self.on_user_changing_reflection_selected) frm.Bind(wx.EVT_CLOSE, self.on_reflection_info_child_frame_close) #Save the frame for when we close the frame self.reflection_info_child_frame = frm else: #Get an existing instance frm = self.reflection_info_child_frame return frm #----------------------------------------------------------------------------------------------- def on_reflection_info_child_frame_close(self, event): """Called when a child FrameReflectionInfo is closed.""" #Clear the instance self.reflection_info_child_frame = None event.Skip() #----------------------------------------------------------------------------------------------- def on_user_changing_reflection_selected(self, refl): """Called when the user changes the HKL of the highlighted peak in this or another window. Parameter: refl: the newly-selected reflection """ self.refl = refl if not refl is None: #Move the little cube self.mouse_point_data_src.data = self.make_single_point_data( refl.q_vector) self.mouse_cube.visible = True else: #hide the cube self.mouse_cube.visible = False #----------------------------------------------------------------------------------------------- def on_mouse_move(self, obj, evt): """Event handler for the 3d Scene.""" self.mouse_mvt = True ref = self.get_reflection_under_mouse(obj) if ref is None: text = self.MOUSE_TEXT_WITH_NO_REFLECTION else: text = "hkl: %d,%d,%d" % ref.hkl #Show it in the little status bar. self.parent_frame.staticTextMouseInfo.SetLabel("Mouse is over: " + text) # If you change a 3D view element with every movement, the # mouse switches to an hourglass each time - annoying! # self.mouse_text.text = text #----------------------------------------------------------------------------------------------- def on_button_release(self, obj, evt): """Event handler for the 3d Scene.""" pass #----------------------------------------------------------------------------------------------- def init_view_objects(self, *args): """Initialize (or re-initialize) all the view elements in the scene. This needs to be called when q-space modeled area changes (changing the outline) or q-resolution, etc. """ #Prevent drawing all the intermediate steps self.scene.disable_render = True #First, we need to remove any data sources and modules from a previous run, # if they exist. # (this is only needed when chaning q-space parameters) for x in [ 'data_src', 'point_data_src', 'iso', 'points_module_surface', 'points_module_glyph', 'outline', 'mouse_text', 'mouse_cube' ]: if hasattr(self, x): getattr(self, x).remove() #Now the corner labels if hasattr(self, 'corner_text'): for txt in self.corner_text: txt.remove() engine = self.engine #We get the qspace_displayed array from experiment and make a copy of it. # This object will REMAIN here and just have its data updated. self.data_src = ArraySource( scalar_data=model.experiment.exp.get_qspace_displayed().copy()) self.data_src.scalar_name = "coverage" self.data_src.visible = False engine.add_source(self.data_src) # --- Text overlay for warnings ---- txt = Text(text="(Points were thinned down)", position_in_3d=False) txt.x_position = 0.02 txt.y_position = 0.98 txt.actor.text_scale_mode = "none" txt.actor.text_property.font_size = 14 txt.actor.text_property.vertical_justification = "top" self.warning_text = txt engine.add_module(self.warning_text) self.warning_text.visible = self.warning_text_visible #---- Make the isosurface object that goes with the volume coverage data ----- iso = IsoSurface() self.iso = iso #Scale the isosurface so that the size is actually q-vector iso.actor.actor.scale = tuple( np.array([1.0, 1.0, 1.0]) * model.experiment.exp.inst.q_resolution) #And we offset the position so that the center is at q-space (0,0,0) iso.actor.actor.position = tuple( np.array([1.0, 1.0, 1.0]) * -model.experiment.exp.inst.qlim) # When set to 1, This helps them to show up right in partially transparent mode. iso.actor.property.backface_culling = 0 iso.actor.property.frontface_culling = 0 #Add the module to the data source, to make it plot that data self.data_src.add_module(iso) # ---- Now we make a point data source, for the individual reflection plot --- self.point_data_src = VTKDataSource(name="Reflection point positions") self.point_data_src.visible = False engine.add_source(self.point_data_src) self.point_data_src.data = self.make_point_data( ) #still needs to get set. # ---- Make a module of simple points, using the Surface module ---- self.points_module_surface = Surface() self.points_module_surface.name = "Single reflections as pixels (Surface)" self.point_data_src.add_module(self.points_module_surface) # points representation = just plot a pixel for each vertex. self.points_module_surface.actor.property.set(representation='points', point_size=3) # ---- Make a module of glyphs, making spheres for each point ---- self.points_module_glyph = Glyph() self.points_module_glyph.name = "Single reflections as spheres (Glyph)" #No scaling of glyph size with scalar data self.points_module_glyph.glyph.scale_mode = 'data_scaling_off' gs = self.points_module_glyph.glyph.glyph_source gs.glyph_source = gs.glyph_dict['sphere_source'] #How many vertices does each sphere have? 3 = fastest. gs.glyph_source.phi_resolution = 3 gs.glyph_source.theta_resolution = 3 #And how big does each sphere end up? gs.glyph_source.radius = 0.1 #Add the module to the same point data source, to show it self.point_data_src.add_module(self.points_module_glyph) # Hide points initially self.points_module_surface.visible = False self.points_module_glyph.visible = False #Get the color look-up table self.points_lut = self.points_module_glyph.module_manager.scalar_lut_manager.lut.table.to_array( ) self.points_lut_original = 1 * self.points_lut # ---- Simple outline for all of the data. ----- self.outline = Outline() #Manually make the outline = the modeled volume, which is +- qlim = 1/d_min self.outline.manual_bounds = True self.outline.bounds = tuple( np.array([-1., 1., -1., 1., -1., 1.]) * model.experiment.exp.inst.qlim) #Add it to the scene directly. engine.add_module(self.outline) #--- Label the HKL of the corners --- if config_gui.cfg.label_corners: q = model.instrument.inst.qlim #This the reciprocal lattice vectors rec = model.experiment.exp.crystal.reciprocal_lattice self.corner_text = [] for x in [-q, q]: for y in [-q, q]: for z in [-q, q]: (h, k, l) = model.crystal_calc.get_hkl_from_q( column([x, y, z]), rec) label = "%d,%d,%d" % (h, k, l) txt = Text(text=label, position_in_3d=False) txt.position_in_3d = True txt.x_position = x txt.y_position = y txt.z_position = z txt.actor.text_scale_mode = "none" txt.actor.text_property.font_size = 14 txt.actor.text_property.vertical_justification = "top" txt.actor.text_property.justification = "left" txt.actor.property.color = (0.75, 1.0, 1.0) engine.add_module(txt) self.corner_text.append(txt) # ---- A text overlay, to show what is under the mouse ----- # self.mouse_text = Text(text=self.MOUSE_TEXT_WITH_NO_REFLECTION, position_in_3d=False) # self.mouse_text.x_position=0.02 # self.mouse_text.y_position=0.98 # self.mouse_text.actor.text_scale_mode = "none" # self.mouse_text.actor.text_property.font_size = 20 # self.mouse_text.actor.text_property.vertical_justification = "top" # engine.add_module(self.mouse_text) # ---- A cube highlighting where the mouse is ---- self.mouse_cube = Glyph() self.mouse_cube.name = "Cube highlighting mouse position (Glyph)" #No scaling of glyph size with scalar data self.mouse_cube.glyph.scale_mode = 'data_scaling_off' self.mouse_cube.glyph.color_mode = 'no_coloring' gs = self.mouse_cube.glyph.glyph_source gs.glyph_source = gs.glyph_dict['cube_source'] self.mouse_cube.actor.property.representation = "wireframe" self.mouse_cube.actor.property.specular = 1.0 # to make edge always white self.mouse_point_data_src = VTKDataSource() self.mouse_point_data_src.name = "Mouse position (VTK Data)" self.mouse_point_data_src.visible = True engine.add_source(self.mouse_point_data_src) self.mouse_point_data_src.data = self.make_single_point_data((0, 0, 0)) self.mouse_point_data_src.add_module(self.mouse_cube) self.mouse_cube.visible = False # #--- Reciprocal axes 3D view ---- # c = model.experiment.exp.crystal #@type c Crystal # (a,b,c) = (c.recip_a, c.recip_b, c.recip_c) # offset = model.experiment.exp.inst.qlim # self.scene.mlab.plot3d([offset, offset+a[0]], [offset, offset+a[1]], [offset, offset+a[2]], color=(1,0,0)) # self.scene.mlab.plot3d([offset, offset+b[0]], [offset, offset+b[1]], [offset, offset+b[2]], color=(0,1,0)) # self.scene.mlab.plot3d([offset, offset+c[0]], [offset, offset+c[1]], [offset, offset+c[2]], color=(0,0,1)) #Re-enable drawing self.scene.disable_render = False #----------------------------------------------------------------------------------------------- def set_points_lut(self, predicted): """Set the look-up table (colormap). Parameters: predicted: bool, True for the "predicted" color map; measured otherwise""" if not hasattr(self, 'points_lut'): print "No LUT!" return # print "setting lut ", predicted if predicted or True: #Predicted style; copy the original self.points_lut[:, :] = self.points_lut_original[:, :] else: #Measured style self.points_lut[:, 0] = 255 - self.points_lut_original[:, 0] self.points_lut[:, 1] = self.points_lut_original[:, 1] self.points_lut[:, 2] = self.points_lut_original[:, 2] # self.points_lut[:,0] = np.arange(0,256,1) # self.points_lut[:,1] = 0 #G # self.points_lut[:,2] = 255 #B self.points_lut[:, 3] = 255 #alpha #----------------------------------------------------------------------------------------------- def show_pipeline(self): """Show the Mayavi pipeline editor window.""" mlab.show_pipeline() #----------------------------------------------------------------------------------------------- def make_point_data(self): """Generate the point data object (type tvtk.PolyData) that is needed by the VTKDataSource to display the points (reflections).""" pd = tvtk.PolyData() ref_q = model.experiment.exp.reflections_q_vector mask = model.experiment.exp.reflections_mask if not isinstance(ref_q, np.ndarray): #No array = return an empty data set return pd assert len( model.experiment.exp.reflections_times_measured_with_equivalents ) == len( mask ), "Reflection mask and times measured should be the same length." #How many of the points are kept? num = np.sum(mask) #Are there more points than the configuration allows? num_wanted = config_gui.cfg.max_3d_points if num > num_wanted: #Adjust the mask to show only so many points indices = np.nonzero(mask)[0] #Randomize list of indices np.random.shuffle(indices) #Keep only the # wanted indices = indices[0:num_wanted] #Re-generate a smaller mask mask = mask.copy( ) #Must copy the array, otherwise we are modifying the source one mask &= 0 mask[indices] = True #Display a warning self.warning_text.text = "(Points thinned down from %d to %d)" % ( num, num_wanted) self.warning_text_visible = True #Make sure the number is correct num = num_wanted else: #No problem self.warning_text_visible = False self.warning_text.visible = self.warning_text_visible if num <= 0: #Nothing to show! return pd else: #Positions = our array of q-vectors. # .T = transpose pd.points = ref_q[:, mask].T #Create the vertices verts = np.arange(0, num, 1) verts.shape = (num, 1) pd.verts = verts #Put the # of times measured here as the scalar data param = display_thread.get_reflection_masking_params( ) #@type param ParamReflectionMasking param_display = display_thread.get_reflection_display_params( ) #@type param_display ParamReflectionDisplay if param_display.color_map == param_display.COLOR_BY_PREDICTED: #--- Color using the predicted measurements --- if param.show_equivalent_reflections: #SHow times measured COUNTING equivalent (symmetrical) reflections. pd.point_data.scalars = model.experiment.exp.reflections_times_measured_with_equivalents[ mask, :] else: # Show only the times measured for the exact reflection pd.point_data.scalars = model.experiment.exp.reflections_times_measured[ mask, :] else: #--- Color using the real measurements ---- if param.show_equivalent_reflections: #SHow times measured COUNTING equivalent (symmetrical) reflections. pd.point_data.scalars = model.experiment.exp.reflections_times_real_measured_with_equivalents[ mask, :] else: # Show only the times measured for the exact reflection pd.point_data.scalars = model.experiment.exp.reflections_times_real_measured[ mask, :] pd.point_data.scalars.name = 'scalars' return pd #----------------------------------------------------------------------------------------------- def make_single_point_data(self, coordinates): """Generate a pointdata object with a single point""" pd = tvtk.PolyData() #Create the vertices num = 1 pd.points = np.array(coordinates).reshape(1, 3) verts = np.arange(0, num, 1) verts.shape = (num, 1) pd.verts = verts pd.point_data.scalars = np.array([1]) pd.point_data.scalars.name = 'scalars' return pd #----------------------------------------------------------------------------------------------- def fix_colorscale(self, module): """Fix the color scale of the given module, to make sure all visual components have matching colors. """ lutm = module.module_manager.scalar_lut_manager lutm.data_range = np.array([0.0, 4.5]) lutm.number_of_labels = 5 lutm.use_default_range = False lutm.shadow = True lutm.scalar_bar.title = "Redundancy" #----------------------------------------------------------------------------------------------- def auto_adjust_pixel_size(self): """Change the size of the pixels drawn to make them look good on screen. Call this when the window is resized or when the # of points plotted changes.""" #Figure out a good pixel size (w, h) = self.scene.render_window.size diag = np.sqrt(w * w + h * h) display = display_thread.get_reflection_display_params() pixel_size = diag / 300. # * (display.size / 5.) self.points_module_surface.actor.property.set(representation='points', point_size=pixel_size) #Update the GUI self.parent_frame.tabReflections.change_displayed_size(pixel_size) #----------------------------------------------------------------------------------------------- def get_best_sphere_size(self): """Calculate the best size a sphere should be drawn to be spaced out okay.""" #Calculate the spacing between hkls in reciprocal space a = vector_length(model.experiment.exp.crystal.recip_a) b = vector_length(model.experiment.exp.crystal.recip_b) c = vector_length(model.experiment.exp.crystal.recip_c) #Take a fraction of the smallest distance return np.min([a, b, c]) / 6 #----------------------------------------------------------------------------------------------- def auto_adjust_sphere_size(self): """Change the size of the sphere glyphs drawn to make them spaced out okay. Call this when the density of points plotted changes.""" sphere_size = self.get_best_sphere_size() #Actually change the size self.points_module_glyph.glyph.glyph_source.glyph_source.radius = sphere_size #Resize the highlighting self.resize_highlighting_cubes(sphere_size) #Update the GUI too self.parent_frame.tabReflections.change_displayed_size( sphere_size / self.SPHERE_SCALING) #----------------------------------------------------------------------------------------------- def resize_highlighting_cubes(self, sphere_size): """Resize the highlighting cubes to be proportional to the the specified sphere size.""" #Also resize the highlighting cubes cube_size = 4. * sphere_size self.mouse_cube.glyph.glyph_source.glyph_source.x_length = cube_size self.mouse_cube.glyph.glyph_source.glyph_source.y_length = cube_size self.mouse_cube.glyph.glyph_source.glyph_source.z_length = cube_size #----------------------------------------------------------------------------------------------- def update_data_points(self, *args): """Called when a message is received saying that the q-space calculation has changed. Will update the graphical display. TO SHOW POINTS!! """ #Do we need to adjust which elements are visible? change_visibility = not self.in_volume_mode() #No GUI updates while we work self.scene.disable_render = True if change_visibility: #Hide the iso surface self.iso.visible = False #Ensure visibility of warning text self.warning_text.visible = self.warning_text_visible #Get the display parameters last saved. # @type display ParamReflectionDisplay display = display_thread.get_reflection_display_params() #Generate a new data object to make self.point_data_src.data = self.make_point_data() #Set the color map self.set_points_lut(display.color_map == display.COLOR_BY_PREDICTED) if self.in_pixel_mode(): # ------------ Pixel View ----------------------- self.pixel_view = True if change_visibility: #Hide the sphere view, show the points self.points_module_glyph.visible = False self.points_module_surface.visible = True #Change the pixel size if display.automatic_size: self.auto_adjust_pixel_size() else: #Directly use the value pixel_size = round(display.size) self.points_module_surface.actor.property.set( representation='points', point_size=pixel_size) #Make sure the highlight cubes are okay sized self.resize_highlighting_cubes(self.get_best_sphere_size()) else: # ------------ Sphere View ----------------------- self.pixel_view = False if change_visibility: #Hide the points view, show the spheres self.points_module_glyph.visible = True self.points_module_surface.visible = False #Change the radius of the spheres if display.automatic_size: self.auto_adjust_sphere_size() else: #Use the number given sphere_size = display.size * self.SPHERE_SCALING self.points_module_glyph.glyph.glyph_source.glyph_source.radius = sphere_size self.resize_highlighting_cubes(sphere_size) # Doc says: Call this function when you change the array data in-place. # This call should update the MayaVi views. # NOTE! Calling this from a different thread seems to crash. self.point_data_src.update() #Make sure the color scale is good self.fix_colorscale(self.points_module_surface) #This will redraw now. self.scene.disable_render = False self.point_data_needs_updating = False #The slice panel updates separately since it is subscribed to the messages. #----------------------------------------------------------------------------------------------- def update_data_volume(self, *args): """Called when a message is received saying that the q-space calculation has changed. Will update the graphical display.""" self.scene.disable_render = True #Get the display parameters last saved. display = display_thread.get_display_params() #We make a copy of the data into the existing object. self.data_src.scalar_data = model.experiment.exp.get_qspace_displayed( ).copy() #The max coverage is necessary to avoid going too high in the isosurfaces if display.show_redundancy: max_coverage = np.max(model.experiment.exp.get_qspace_displayed()) if max_coverage > 4: max_coverage = 4 #Update the contours iso = self.iso if display.show_redundancy: iso.contour.contours = list(np.arange(1, max_coverage + 1)) iso.actor.property.opacity = 0.6 # When set to 1, This helps them to show up right in partially transparent mode. iso.actor.property.backface_culling = 1 iso.actor.property.frontface_culling = 1 else: iso.contour.contours = [1.0] iso.actor.property.opacity = 1.0 # When set to 1, this does not look good in solid ISO mode iso.actor.property.backface_culling = 0 iso.actor.property.frontface_culling = 0 #Don't use opacity in inverted mode if display_thread.is_inverted(): iso.actor.property.opacity = 1.0 iso.actor.mapper.scalar_visibility = 1 #1 means use the look-up table #Make sure the color scale is good self.fix_colorscale(self.iso) # Doc says: Call this function when you change the array data in-place. # This call should update the MayaVi views. # NOTE! Calling this from a different thread seems to crash. self.data_src.update() self.scene.disable_render = False self.volume_data_needs_updating = False #The slice panel updates separately since it is subscribed to the messages. #----------------------------------------------------------------------------------------------- def in_volume_mode(self): """Return True if the "volume coverage" view mode selected. """ tab_selected = self.parent_frame.notebookView.GetPage( self.parent_frame.notebookView.GetSelection()) return (tab_selected is self.parent_frame.tabVolume) #----------------------------------------------------------------------------------------------- def in_pixel_mode(self): """Return True if we are in pixel point view mode.""" # @type display ParamReflectionDisplay display = display_thread.get_reflection_display_params() return display.display_as == display.DISPLAY_AS_PIXELS #----------------------------------------------------------------------------------------------- def view_mode_changed(self): """Called when the view mode switches from volume to points, or vice-versa.""" #Can be called while the form is closing, throwing errors. if self.form_is_closing: return if self.in_volume_mode(): #Volume view self.iso.visible = True self.warning_text.visible = False #No need to show this warning self.points_module_surface.visible = False self.points_module_glyph.visible = False else: #Just switch the view, this goes quick! self.iso.visible = False self.warning_text.visible = self.warning_text_visible self.points_module_surface.visible = self.in_pixel_mode() self.points_module_glyph.visible = not self.in_pixel_mode() #And the stats need to update self.update_stats_panel() #----------------------------------------------------------------------------------------------- def update_stats_panel(self, *args): """Update the information displayed on the statistics panel.""" exp = model.experiment.exp if self.in_volume_mode(): #Volume coverage self.parent_frame.stats_panel.show_stats( \ exp.use_symmetry(), exp.overall_coverage, exp.overall_redundancy ) else: #Point mode use_symmetry = display_thread.get_reflection_masking_params( ).primary_reflections_only if use_symmetry: self.parent_frame.stats_panel.show_reflection_stats( use_symmetry, exp.reflection_stats_with_symmetry) else: self.parent_frame.stats_panel.show_reflection_stats( use_symmetry, exp.reflection_stats)
def init_view_objects(self, *args): """Initialize (or re-initialize) all the view elements in the scene. This needs to be called when q-space modeled area changes (changing the outline) or q-resolution, etc. """ #Prevent drawing all the intermediate steps self.scene.disable_render = True #First, we need to remove any data sources and modules from a previous run, # if they exist. # (this is only needed when chaning q-space parameters) for x in [ 'data_src', 'point_data_src', 'iso', 'points_module_surface', 'points_module_glyph', 'outline', 'mouse_text', 'mouse_cube' ]: if hasattr(self, x): getattr(self, x).remove() #Now the corner labels if hasattr(self, 'corner_text'): for txt in self.corner_text: txt.remove() engine = self.engine #We get the qspace_displayed array from experiment and make a copy of it. # This object will REMAIN here and just have its data updated. self.data_src = ArraySource( scalar_data=model.experiment.exp.get_qspace_displayed().copy()) self.data_src.scalar_name = "coverage" self.data_src.visible = False engine.add_source(self.data_src) # --- Text overlay for warnings ---- txt = Text(text="(Points were thinned down)", position_in_3d=False) txt.x_position = 0.02 txt.y_position = 0.98 txt.actor.text_scale_mode = "none" txt.actor.text_property.font_size = 14 txt.actor.text_property.vertical_justification = "top" self.warning_text = txt engine.add_module(self.warning_text) self.warning_text.visible = self.warning_text_visible #---- Make the isosurface object that goes with the volume coverage data ----- iso = IsoSurface() self.iso = iso #Scale the isosurface so that the size is actually q-vector iso.actor.actor.scale = tuple( np.array([1.0, 1.0, 1.0]) * model.experiment.exp.inst.q_resolution) #And we offset the position so that the center is at q-space (0,0,0) iso.actor.actor.position = tuple( np.array([1.0, 1.0, 1.0]) * -model.experiment.exp.inst.qlim) # When set to 1, This helps them to show up right in partially transparent mode. iso.actor.property.backface_culling = 0 iso.actor.property.frontface_culling = 0 #Add the module to the data source, to make it plot that data self.data_src.add_module(iso) # ---- Now we make a point data source, for the individual reflection plot --- self.point_data_src = VTKDataSource(name="Reflection point positions") self.point_data_src.visible = False engine.add_source(self.point_data_src) self.point_data_src.data = self.make_point_data( ) #still needs to get set. # ---- Make a module of simple points, using the Surface module ---- self.points_module_surface = Surface() self.points_module_surface.name = "Single reflections as pixels (Surface)" self.point_data_src.add_module(self.points_module_surface) # points representation = just plot a pixel for each vertex. self.points_module_surface.actor.property.set(representation='points', point_size=3) # ---- Make a module of glyphs, making spheres for each point ---- self.points_module_glyph = Glyph() self.points_module_glyph.name = "Single reflections as spheres (Glyph)" #No scaling of glyph size with scalar data self.points_module_glyph.glyph.scale_mode = 'data_scaling_off' gs = self.points_module_glyph.glyph.glyph_source gs.glyph_source = gs.glyph_dict['sphere_source'] #How many vertices does each sphere have? 3 = fastest. gs.glyph_source.phi_resolution = 3 gs.glyph_source.theta_resolution = 3 #And how big does each sphere end up? gs.glyph_source.radius = 0.1 #Add the module to the same point data source, to show it self.point_data_src.add_module(self.points_module_glyph) # Hide points initially self.points_module_surface.visible = False self.points_module_glyph.visible = False #Get the color look-up table self.points_lut = self.points_module_glyph.module_manager.scalar_lut_manager.lut.table.to_array( ) self.points_lut_original = 1 * self.points_lut # ---- Simple outline for all of the data. ----- self.outline = Outline() #Manually make the outline = the modeled volume, which is +- qlim = 1/d_min self.outline.manual_bounds = True self.outline.bounds = tuple( np.array([-1., 1., -1., 1., -1., 1.]) * model.experiment.exp.inst.qlim) #Add it to the scene directly. engine.add_module(self.outline) #--- Label the HKL of the corners --- if config_gui.cfg.label_corners: q = model.instrument.inst.qlim #This the reciprocal lattice vectors rec = model.experiment.exp.crystal.reciprocal_lattice self.corner_text = [] for x in [-q, q]: for y in [-q, q]: for z in [-q, q]: (h, k, l) = model.crystal_calc.get_hkl_from_q( column([x, y, z]), rec) label = "%d,%d,%d" % (h, k, l) txt = Text(text=label, position_in_3d=False) txt.position_in_3d = True txt.x_position = x txt.y_position = y txt.z_position = z txt.actor.text_scale_mode = "none" txt.actor.text_property.font_size = 14 txt.actor.text_property.vertical_justification = "top" txt.actor.text_property.justification = "left" txt.actor.property.color = (0.75, 1.0, 1.0) engine.add_module(txt) self.corner_text.append(txt) # ---- A text overlay, to show what is under the mouse ----- # self.mouse_text = Text(text=self.MOUSE_TEXT_WITH_NO_REFLECTION, position_in_3d=False) # self.mouse_text.x_position=0.02 # self.mouse_text.y_position=0.98 # self.mouse_text.actor.text_scale_mode = "none" # self.mouse_text.actor.text_property.font_size = 20 # self.mouse_text.actor.text_property.vertical_justification = "top" # engine.add_module(self.mouse_text) # ---- A cube highlighting where the mouse is ---- self.mouse_cube = Glyph() self.mouse_cube.name = "Cube highlighting mouse position (Glyph)" #No scaling of glyph size with scalar data self.mouse_cube.glyph.scale_mode = 'data_scaling_off' self.mouse_cube.glyph.color_mode = 'no_coloring' gs = self.mouse_cube.glyph.glyph_source gs.glyph_source = gs.glyph_dict['cube_source'] self.mouse_cube.actor.property.representation = "wireframe" self.mouse_cube.actor.property.specular = 1.0 # to make edge always white self.mouse_point_data_src = VTKDataSource() self.mouse_point_data_src.name = "Mouse position (VTK Data)" self.mouse_point_data_src.visible = True engine.add_source(self.mouse_point_data_src) self.mouse_point_data_src.data = self.make_single_point_data((0, 0, 0)) self.mouse_point_data_src.add_module(self.mouse_cube) self.mouse_cube.visible = False # #--- Reciprocal axes 3D view ---- # c = model.experiment.exp.crystal #@type c Crystal # (a,b,c) = (c.recip_a, c.recip_b, c.recip_c) # offset = model.experiment.exp.inst.qlim # self.scene.mlab.plot3d([offset, offset+a[0]], [offset, offset+a[1]], [offset, offset+a[2]], color=(1,0,0)) # self.scene.mlab.plot3d([offset, offset+b[0]], [offset, offset+b[1]], [offset, offset+b[2]], color=(0,1,0)) # self.scene.mlab.plot3d([offset, offset+c[0]], [offset, offset+c[1]], [offset, offset+c[2]], color=(0,0,1)) #Re-enable drawing self.scene.disable_render = False