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: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())
Example #5
0
 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())
Example #6
0
 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())
Example #7
0
 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())
Example #8
0
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
Example #9
0
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
Example #10
0
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)
Example #11
0
    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