def DoCreateResource(self): assert self.GetInstance() is None control_image = Image(parent=self.GetParentAsWindow(), choices=ObservableList(), id=self.GetID(), pos=self.GetPosition(), size=self.GetSize(), style=self.GetStyle(), name=self.GetName()) self.SetupWindow(control_image) self.CreateChildren(control_image) return control_image
def OnOpenClicked(self, event): """ Load the spectrum with either a reference spectrum or a computed histogram """ # Create the image image = medipy.io.load( self._image_path) #, 0, loader_class= nmr2D.Nmr2D) # Insert a reference spectrum into the image if one has been specified if self._reference_path is not None: spectrum = numpy.fromfile(self._reference_path, numpy.int32) image.metadata["header"]["proton_spectrum"] = spectrum # Load a list of annotations if an annotation file has been specified if self._annotations_path is not None: image.metadata["Data"] = image.data dom = md.parse(self._annotations_path) peaks = dom.getElementsByTagName("Peak2D") image.annotations = ObservableList() for peak in peaks: annotation = ImageAnnotation() ppm = (float(peak.getAttribute("F1")), float(peak.getAttribute("F2"))) point = rbnmr.ppm_to_point(ppm, image.metadata["Procs"], image.metadata["Proc2s"]) annotation.position = [0, point[-2], point[-1]] annotation.label = peak.getAttribute("annotation") annotation.shape = ImageAnnotation.Shape.cross annotation.size = 10 annotation.color = [0, 1., 0.] annotation.filled = False annotation.depth = 10 image.annotations.append(annotation) self.GetParent().append_image([{"image": image}]) # Close the window self.Destroy()
def __call__(self): # Save the current value of the parameters # self._save_parameters() # Build the expression namespace namespace = {"function": self._function} for parameter in self._parameters: if parameter.get("role", "") == "return": namespace[parameter["name"]] = None elif parameter.get("role", "") == "output": # Special case for images and 3D viewers if parameter["type"] == "Image" and self._controls[ parameter["name"]].output_checked: dummy = Image(shape=(1, 1, 1), value=0, dtype=numpy.single) namespace[parameter["name"]] = dummy elif parameter["type"] == "Object3D": dummy = Object3D(name="New object") namespace[parameter["name"]] = dummy else: namespace[parameter["name"]] = self._controls[ parameter["name"]].value else: # Parameter will not be modified namespace[parameter["name"]] = self._controls[ parameter["name"]].value # Build the expression args = [] return_values = [] for parameter in self._parameters: if parameter.get("role", "") == "return": return_values.append(parameter["name"]) else: args.append("{0} = {0}".format(parameter["name"])) expression = "function({0})".format(", ".join(args)) if return_values: return_expression = ", ".join(return_values) expression = "{0} = {1}".format(return_expression, expression) # Execute it def f(): exec expression in namespace periodic_progress_dialog = PeriodicProgressDialog( 0.2, "Running %s ..." % self._function.func_name, "Running %s ..." % self._function.func_name) worker_thread = WorkerThread(periodic_progress_dialog, target=f) worker_thread.start() periodic_progress_dialog.start() worker_thread.join() periodic_progress_dialog.Destroy() if worker_thread.exception: wx.MessageBox( "Could not run function : {0}".format(worker_thread.exception), "Could not run function") else: # Update controls and application for parameter in self._parameters: if parameter.get("role", "") not in ["return", "output"]: # Parameter has not been modified continue else: name = parameter["name"] value = namespace[name] control = self._controls[name] # Special case for Image and Object3D if parameter["type"] == "Image": if control.output_checked: self.notify_observers("new_image", image=value) else: self.notify_observers("replace_image", old=control.value, new=value) elif parameter["type"] == "Object3D": if control.output_checked: viewer = Viewer3DFrame(parent=None, objects_3d=ObservableList()) viewer.Show() self.notify_observers("new_viewer_3d", viewer=viewer) control.value = self._viewer_3ds[-1] viewer_3d = control.value viewer_3d.objects_3d.append(value) # If object has an associated image, set the GUI image image = value.image if image is not None: self.notify_observers("set_image_to_object_3d", image=image, object_3d=value) if len(viewer_3d.objects_3d) == 1: viewer_3d.view_all() viewer_3d.update_object_editor() # In any case but Object3D (whose value is a Viewer3D), # update the control if parameter["type"] != "Object3D": control.value = value
def __init__(self, *args, **kwargs): wx.Panel.__init__(self, *args, **kwargs) Observable.__init__(self, ["active_object"]) self._objects_3d = ObservableList() self._active_object = None # Currently pressed button, None if no button is pressed self._button = None # Bindings from mouse button to tool self.tools = { "LeftButton": viewer_3d_tools.Rotate(self), "MiddleButton": viewer_3d_tools.Pan(self), "RightButton": viewer_3d_tools.Dolly(self), "MouseWheelForward": viewer_3d_tools.WheelDolly(self, "forward"), "MouseWheelBackward": viewer_3d_tools.WheelDolly(self, "backward") } # Bindings from key to tool self.key_bindings = { # Nothing by default } # VTK objects for rendering self._renderer = vtkRenderer() self._renderer.SetBackground(82. / 255., 87. / 255., 110. / 255.) self._renderer.GetActiveCamera().ParallelProjectionOff() self._rwi = wxVTKRenderWindowInteractor(self, wx.ID_ANY) self._rwi.GetRenderWindow().AddRenderer(self._renderer) self._sizer = wx.BoxSizer() self._sizer.Add(self._rwi, 1, wx.EXPAND) self.SetSizer(self._sizer) # Interaction styles : customize events self._rwi.SetInteractorStyle(None) self._rwi.AddObserver("LeftButtonPressEvent", self._start_interaction) self._rwi.AddObserver("MiddleButtonPressEvent", self._start_interaction) self._rwi.AddObserver("RightButtonPressEvent", self._start_interaction) self._rwi.AddObserver("LeftButtonReleaseEvent", self._stop_interaction) self._rwi.AddObserver("MiddleButtonReleaseEvent", self._stop_interaction) self._rwi.AddObserver("RightButtonReleaseEvent", self._stop_interaction) self._rwi.AddObserver("MouseWheelForwardEvent", self._start_interaction) self._rwi.AddObserver("MouseWheelBackwardEvent", self._start_interaction) self._rwi.AddObserver("MouseMoveEvent", self._dispatch_interaction) self._rwi.AddObserver("KeyPressEvent", self._keypress) self._rwi.AddObserver("KeyReleaseEvent", self._keyrelease) # Clean up when the window is closed self.Bind(wx.EVT_CLOSE, self.OnClose) self.objects_3d.add_observer("any", self._on_objects_3d_modified)
class Viewer3D(wx.Panel, Observable): def __init__(self, *args, **kwargs): wx.Panel.__init__(self, *args, **kwargs) Observable.__init__(self, ["active_object"]) self._objects_3d = ObservableList() self._active_object = None # Currently pressed button, None if no button is pressed self._button = None # Bindings from mouse button to tool self.tools = { "LeftButton": viewer_3d_tools.Rotate(self), "MiddleButton": viewer_3d_tools.Pan(self), "RightButton": viewer_3d_tools.Dolly(self), "MouseWheelForward": viewer_3d_tools.WheelDolly(self, "forward"), "MouseWheelBackward": viewer_3d_tools.WheelDolly(self, "backward") } # Bindings from key to tool self.key_bindings = { # Nothing by default } # VTK objects for rendering self._renderer = vtkRenderer() self._renderer.SetBackground(82. / 255., 87. / 255., 110. / 255.) self._renderer.GetActiveCamera().ParallelProjectionOff() self._rwi = wxVTKRenderWindowInteractor(self, wx.ID_ANY) self._rwi.GetRenderWindow().AddRenderer(self._renderer) self._sizer = wx.BoxSizer() self._sizer.Add(self._rwi, 1, wx.EXPAND) self.SetSizer(self._sizer) # Interaction styles : customize events self._rwi.SetInteractorStyle(None) self._rwi.AddObserver("LeftButtonPressEvent", self._start_interaction) self._rwi.AddObserver("MiddleButtonPressEvent", self._start_interaction) self._rwi.AddObserver("RightButtonPressEvent", self._start_interaction) self._rwi.AddObserver("LeftButtonReleaseEvent", self._stop_interaction) self._rwi.AddObserver("MiddleButtonReleaseEvent", self._stop_interaction) self._rwi.AddObserver("RightButtonReleaseEvent", self._stop_interaction) self._rwi.AddObserver("MouseWheelForwardEvent", self._start_interaction) self._rwi.AddObserver("MouseWheelBackwardEvent", self._start_interaction) self._rwi.AddObserver("MouseMoveEvent", self._dispatch_interaction) self._rwi.AddObserver("KeyPressEvent", self._keypress) self._rwi.AddObserver("KeyReleaseEvent", self._keyrelease) # Clean up when the window is closed self.Bind(wx.EVT_CLOSE, self.OnClose) self.objects_3d.add_observer("any", self._on_objects_3d_modified) def view_all(self): self.reset_camera() self._rwi.Render() def get_bounds(self): # get the bounding box of all of the objects of the current scene (visible or not) bounds = [] for _object in self._objects_3d: actor = _object.actor bounds.append(actor.GetBounds()) Xmin = 0 Xmax = 0 Ymin = 0 Ymax = 0 Zmin = 0 Zmax = 0 if len(bounds) > 0: Xmin = min(map(lambda x: x[0], bounds)) Xmax = max(map(lambda x: x[1], bounds)) Ymin = min(map(lambda x: x[2], bounds)) Ymax = max(map(lambda x: x[3], bounds)) Zmin = min(map(lambda x: x[4], bounds)) Zmax = max(map(lambda x: x[5], bounds)) return ((Xmin, Ymin, Zmin), (Xmax, Ymax, Zmax)) def _get_distance(self): """get the greatest dimension of the enclosing bounding box of all the element in the scene\ (visible or not) """ Pmin, Pmax = self.get_bounds() w1 = Pmax[0] - Pmin[0] w2 = Pmax[1] - Pmin[1] w3 = Pmax[2] - Pmin[2] return max(w1, w2, w3) def reset_camera(self): # view angle view_angle = 53.13 self._renderer.GetActiveCamera().SetViewAngle(view_angle) Pmin, Pmax = self.get_bounds() # target : focal point focal_point = numpy.add(Pmin, Pmax) / 2. distance = self._get_distance() position = self._renderer.GetActiveCamera().GetPosition() v = numpy.subtract(position, focal_point) norme = numpy.linalg.norm(v) remoteness = 1.5 factor = (distance * remoteness) / norme new_pos = numpy.add(numpy.multiply(v, factor), focal_point) # update the camera ... self._renderer.GetActiveCamera().SetFocalPoint(*focal_point) self._renderer.GetActiveCamera().SetPosition(new_pos) self._renderer.ResetCameraClippingRange(Pmin[0], Pmax[0], Pmin[1], Pmax[1], Pmin[2], Pmax[2]) ############## # Properties # ############## def _set_objects_3d(self, objects_3d): collection = self._renderer.GetViewProps() collection.InitTraversal() for i in range(collection.GetNumberOfItems()): actor = collection.GetNextProp() self._renderer.RemoveActor(actor) self._objects_3d = objects_3d for objet in self.objects_3d: self._renderer.AddActor(objet.actor) self._objects_3d.remove_observer("any", self._on_objects_3d_modified) self._objects_3d = objects_3d self.objects_3d.add_observer("any", self._on_objects_3d_modified) def _set_active_object(self, object): if object not in self.objects_3d: raise medipy.base.Exception("Object is not in viewer's objects") self._active_object = object self.notify_observers("active_object") objects_3d = property(lambda x: x._objects_3d, _set_objects_3d) active_object = property(lambda x: x._active_object, _set_active_object) renderer = property(lambda x: x._renderer) render_window_interactor = property(lambda x: x._rwi) ################## # Event handlers # ################## def OnClose(self, event): self._renderer.RemoveAllViewProps() del self._renderer self._rwi.GetRenderWindow().Finalize() self._rwi.SetRenderWindow(None) self._rwi.Close() del self._rwi self.Destroy() def _on_objects_3d_modified(self, event): if event.event == "append": for objet in self.objects_3d: if not self._renderer.HasViewProp(objet.actor): self._renderer.AddActor(objet.actor) elif event.event == "delete_item": self._renderer.RemoveActor(event.old_value.actor) else: raise medipy.base.Exception("Unmanaged event : %s" % event.event) self._rwi.Render() def _start_interaction(self, object, event): if event.startswith("MouseWheel"): wheel_event = event[:-len("Event")] if wheel_event in self.tools: self.tools[wheel_event].start_interaction() self.tools[wheel_event].stop_interaction() elif event.endswith("PressEvent"): button = event[:-len("PressEvent")] event = button if object.GetControlKey(): event += "Control" if object.GetShiftKey(): event += "Shift" # In case no modifier is configured, fall back to the basic behavior if event not in self.tools: event = button if event in self.tools: self._button = button self.tools[event].start_interaction() def _stop_interaction(self, object, event): button = event[:-len("ReleaseEvent")] event = self._button if object.GetControlKey(): event += "Control" if object.GetShiftKey(): event += "Shift" for key, tool in self.tools.items(): if key.startswith(button): tool.stop_interaction() self._button = None def _dispatch_interaction(self, object, event): event = self._button if event is None: return if object.GetControlKey(): event += "Control" if object.GetShiftKey(): event += "Shift" # In case no modifier is configured, fall back to the basic behavior if event not in self.tools: event = self._button if event in self.tools: self.tools[event].dispatch_interaction() def _keypress(self, object, event): ordinal = ord(object.GetKeyCode()) if ordinal in self.key_bindings: self.key_bindings[ordinal].press() def _keyrelease(self, object, event): ordinal = ord(object.GetKeyCode()) if ordinal in self.key_bindings: self.key_bindings[ordinal].release() ##################### # Private interface # ##################### def _update_actors(self): actors_from_renderer = [] collection = self._renderer.GetViewProps() collection.InitTraversal() for i in range(collection.GetNumberOfItems()): actor = collection.GetNextProp() actors_from_renderer.append(actor) actors_from_objects = [object.actor for object in self.objects_3d] for actor in actors_from_renderer: if actor not in actors_from_objects: self._renderer.RemoveActor(actor) for object in self.objects_3d: if not self._renderer.HasViewProp(object.actor): self._renderer.AddActor(object.actor)
def __init__(self, parent, slice_mode="multiplanar", layers=None, annotations=None, interpolation=False, display_coordinates="physical", scalar_bar_visibility=False, orientation_visibility=True, corner_annotations_visibility=False, convention="radiological", crosshair="full", *args, **kwargs): if annotations is None: annotations = ObservableList() ############## # Properties # ############## self._slice_mode = None self._interpolation = None self._display_coordinates = None self._scalar_bar_visibility = None self._orientation_visibility = None self._convention = None self._crosshair = None self._corner_annotations_visibility = None self._annotations = None self._cursor_physical_position = None self._cursor_index_position = None self._center_physical_position = None self._center_index_position = None self._zoom = None self._mouse_tools = {} self._keyboard_tools = {} ################### # Private members # ################### self._rwi = None self._layers = ObservableList() self._slices = [] self._slices_names = [] self._informations_renderer = vtkRenderer() self._informations_renderer.SetViewport( *self._viewport["informations"]) self._informations_corner_annotation = vtkCornerAnnotation() self._informations_corner_annotation.SetNonlinearFontScaleFactor(0.3) self._informations_renderer.AddActor( self._informations_corner_annotation) ################## # Initialization # ################## wx.Panel.__init__(self, parent, *args, **kwargs) # Explicitely pass size to RWI to propagate it correctly self._rwi = wxVTKRenderWindowInteractor(self, wx.ID_ANY, size=self.GetSize()) self._rwi.Enable(1) sizer = wx.BoxSizer() sizer.Add(self._rwi, 1, wx.EXPAND | wx.ALL, 3) self.SetSizer(sizer) self._rwi.Bind(wx.EVT_LEFT_DOWN, self._on_button_down) self._rwi.Bind(wx.EVT_MIDDLE_DOWN, self._on_button_down) self._rwi.Bind(wx.EVT_RIGHT_DOWN, self._on_button_down) self._rwi.Bind(wx.EVT_MOUSEWHEEL, self._on_button_down) self.Bind(wx.EVT_CLOSE, self._on_close) PropertySynchronized.__init__(self, [ "interpolation", "display_coordinates", "scalar_bar_visibility", "orientation_visibility", "zoom" ]) self.add_allowed_event("cursor_position") self.add_allowed_event("image_position") self.add_allowed_event("center") self.add_allowed_event("layer_visibility") for event in [ "data", "display_range", "cut_low", "cut_high", "zero_transparency" ]: self.add_allowed_event("colormap_{0}".format(event)) # self.add_allowed_event("layer_modified") # self.add_allowed_event("activated") self._set_interpolation(interpolation) self._set_display_coordinates(display_coordinates) self._set_scalar_bar_visibility(scalar_bar_visibility) self._set_orientation_visibility(orientation_visibility) self._set_convention(convention) self._set_crosshair(crosshair) self._set_corner_annotations_visibility(corner_annotations_visibility) for layer in layers or []: self.append_layer(**layer) self._set_slice_mode(slice_mode) self._set_annotations(annotations) # Position image at middle of layer 0 self.reset_view()
class Image(wx.Panel, PropertySynchronized): """ Synchronized representation of several slices with several layers. When passed to the constructor, layers are specified by a dictionary containing the following keys : * `"image"` : a :class:`medipy.base.Image`, mandatory * `"colormap"` : a :class:`medipy.gui.Colormap`, defaults to None * `"opacity"` : a scalar between `0.0` and `1.0`, defaults to `1.0` For example an image with two layers, the bottom one in grayscale and the top one in rainbow can be created as follows : :: import medipy.io import medipy.gui import medipy.gui.image anatomical = medipy.io.load("/usr/share/data/fsl-mni152-templates/MNI152_T1_1mm.nii.gz") atlas = medipy.io.load("/usr/share/fsl/data/atlases/MNI/MNI-maxprob-thr0-1mm.nii.gz") colormap = medipy.gui.Colormap(medipy.gui.colormaps["rainbow"], None, zero_transparency=True) layers = [ {"image": anatomical}, {"image": atlas, "colormap": colormap, "opacity": 0.5} ] image = medipy.gui.image.Image(parent, layers=layers) """ _viewport = { "axial": (0.0, 0.0, 0.5, 0.5), "coronal": (0.0, 0.5, 0.5, 1.0), "sagittal": (0.5, 0.5, 1.0, 1.0), "informations": (0.5, 0.0, 1.0, 0.5) } # Index of location for vtkCornerAnnotation _corner_annotation_index = { "up_left": 2, "up_right": 3, "down_left": 0, "down_right": 1 } def __init__(self, parent, slice_mode="multiplanar", layers=None, annotations=None, interpolation=False, display_coordinates="physical", scalar_bar_visibility=False, orientation_visibility=True, corner_annotations_visibility=False, convention="radiological", crosshair="full", *args, **kwargs): if annotations is None: annotations = ObservableList() ############## # Properties # ############## self._slice_mode = None self._interpolation = None self._display_coordinates = None self._scalar_bar_visibility = None self._orientation_visibility = None self._convention = None self._crosshair = None self._corner_annotations_visibility = None self._annotations = None self._cursor_physical_position = None self._cursor_index_position = None self._center_physical_position = None self._center_index_position = None self._zoom = None self._mouse_tools = {} self._keyboard_tools = {} ################### # Private members # ################### self._rwi = None self._layers = ObservableList() self._slices = [] self._slices_names = [] self._informations_renderer = vtkRenderer() self._informations_renderer.SetViewport( *self._viewport["informations"]) self._informations_corner_annotation = vtkCornerAnnotation() self._informations_corner_annotation.SetNonlinearFontScaleFactor(0.3) self._informations_renderer.AddActor( self._informations_corner_annotation) ################## # Initialization # ################## wx.Panel.__init__(self, parent, *args, **kwargs) # Explicitely pass size to RWI to propagate it correctly self._rwi = wxVTKRenderWindowInteractor(self, wx.ID_ANY, size=self.GetSize()) self._rwi.Enable(1) sizer = wx.BoxSizer() sizer.Add(self._rwi, 1, wx.EXPAND | wx.ALL, 3) self.SetSizer(sizer) self._rwi.Bind(wx.EVT_LEFT_DOWN, self._on_button_down) self._rwi.Bind(wx.EVT_MIDDLE_DOWN, self._on_button_down) self._rwi.Bind(wx.EVT_RIGHT_DOWN, self._on_button_down) self._rwi.Bind(wx.EVT_MOUSEWHEEL, self._on_button_down) self.Bind(wx.EVT_CLOSE, self._on_close) PropertySynchronized.__init__(self, [ "interpolation", "display_coordinates", "scalar_bar_visibility", "orientation_visibility", "zoom" ]) self.add_allowed_event("cursor_position") self.add_allowed_event("image_position") self.add_allowed_event("center") self.add_allowed_event("layer_visibility") for event in [ "data", "display_range", "cut_low", "cut_high", "zero_transparency" ]: self.add_allowed_event("colormap_{0}".format(event)) # self.add_allowed_event("layer_modified") # self.add_allowed_event("activated") self._set_interpolation(interpolation) self._set_display_coordinates(display_coordinates) self._set_scalar_bar_visibility(scalar_bar_visibility) self._set_orientation_visibility(orientation_visibility) self._set_convention(convention) self._set_crosshair(crosshair) self._set_corner_annotations_visibility(corner_annotations_visibility) for layer in layers or []: self.append_layer(**layer) self._set_slice_mode(slice_mode) self._set_annotations(annotations) # Position image at middle of layer 0 self.reset_view() def append_layer(self, *args, **kwargs): """ Append a new layer. """ self.insert_layer(len(self._layers), *args, **kwargs) def insert_layer(self, index, image, colormap=None, opacity=1.0): """ Insert a new layer at specified position. The colormap defaults to a gray colormap. If the colormap's display range is None, it defaults to the image range. """ if colormap is None: colormap = Colormap(colormaps["gray"], None, False, False, False) if colormap.display_range is None: colormap.display_range = (image.data.min(), image.data.max()) for slice in self._slices: slice.insert_layer(index, image, colormap, opacity) if len(self._slices) > 1: for event in [ "data", "display_range", "cut_low", "cut_high", "zero_transparency" ]: self._slices[0].layers[index].colormap.add_observer( event, self._on_colormap_event) for slice_index, slice in enumerate(self._slices): layer = slice.layers[index] next_slice_layer = self._slices[(slice_index + 1) % len( self._slices)].layers[index] layer.colormap.append_child(next_slice_layer.colormap) # Update self._layers late to be sure that slices are correctly set-up # before observers get notified self._layers.insert(index, { "image": image, "colormap": colormap, "opacity": opacity }) def delete_layer(self, index): """ Remove a layer from the list. """ for event in [ "data", "display_range", "cut_low", "cut_high", "zero_transparency" ]: self._slices[0].layers[index].colormap.remove_observer( event, self._on_colormap_event) del self._layers[index] for slice in self._slices: slice.delete_layer(index) def get_layer_visibility(self, index): return self._slices[0].get_layer_visibility(index) def set_layer_visibility(self, index, visibility): for slice in self._slices: slice.set_layer_visibility(index, visibility) def get_layer_image(self, index): return self._layers[index]["image"] def set_layer_image(self, index, image): self._layers[index]["image"] = image for slice in self._slices: slice.layers[index].image = image def get_layer_colormap(self, index): return self._layers[index]["colormap"] def set_layer_colormap(self, index, colormap): self._layers[index]["colormap"] = colormap for slice in self._slices: slice.layers[index].colormap = colormap def get_layer_opacity(self, index): return self._layers[index]["opacity"] def set_layer_opacity(self, index, opacity): self._layers[index]["opacity"] = opacity for slice in self._slices: slice.layers[index].opacity = opacity def get_layer_class(self, index): return self._slices[0].layers[index].__class__ def get_layer_property(self, index, name): return getattr(self._slices[0].layers[index], name) def set_layer_property(self, index, name, value): for slice in self._slices: setattr(slice.layers[index], name, value) def render(self): self._rwi.Render() def reset_view(self): for slice in self._slices: slice.reset_view() self.notify_observers("center") def get_mouse_button_tool(self, button): """ Return a triplet (ToolClass, args, kwargs) """ return self._mouse_tools[button] def set_mouse_button_tool(self, button, tool_class, *args, **kwargs): """ Set a tool associated with given button (Left, Middle, or Right), with an optional modifier (Shift or Control). Example : Right, ShiftLeft. Set tool to None to have no tool connected to the button. """ self._mouse_tools[button] = (tool_class, args, kwargs) for slice in self._slices: if tool_class: tool = tool_class(*args, **kwargs) else: tool = None slice.set_mouse_button_tool(button, tool) def get_wheel_tool(self, direction): """ Return the tool associated with a mouse wheel direction (Forward or Backward) """ event_name = "MouseWheel%s" % direction return self._mouse_tools[event_name] def set_wheel_tool(self, direction, tool_class, *args, **kwargs): """ Set a tool associated with a mouse wheel direction (Forward or Backward) """ self._mouse_tools[direction] = (tool_class, args, kwargs) for slice in self._slices: if tool_class: tool = tool_class(*args, **kwargs) else: tool = None slice.set_wheel_tool(direction, tool) def get_keyboard_tool(self, key): return self._keyboard_tools[key] def set_keyboard_tool(self, key, tool_class, *args, **kwargs): self._keyboard_tools[key] = (tool_class, args, kwargs) for slice in self._slices: if tool_class: tool = tool_class(*args, **kwargs) else: tool = None slice.set_keyboard_tool(key, tool) def set_next_window_info(self, info): """ Remap the VTK (and underlying) windows. This has to be called when reparenting. """ self._rwi.GetRenderWindow().SetNextWindowInfo(info) self._rwi.GetRenderWindow().WindowRemap() ############## # Properties # ############## def _get_layers(self): return self._layers def _get_slice_mode(self): return self._slice_mode def _set_slice_mode(self, slice_mode): if slice_mode not in ["axial", "coronal", "sagittal", "multiplanar"]: raise medipy.base.Exception("Unknown slice mode : %s" % (slice_mode, )) old_cursor_position = (numpy.copy(self.cursor_index_position) if self.cursor_index_position is not None else None) old_slice_mode = self._slice_mode self._slice_mode = slice_mode for slice in self._slices: self._rwi.GetRenderWindow().RemoveRenderer(slice.renderer) slice.unset_rwi(self._rwi) if old_slice_mode == "multiplanar" and slice_mode != "multiplanar": self._rwi.GetRenderWindow().RemoveRenderer( self._informations_renderer) self._slices = [] self._slices_names = [] names = (["axial", "coronal", "sagittal"] if slice_mode == "multiplanar" else [slice_mode]) # Build and configure the Slice objects for name in names: if self._display_coordinates == "index": # Do not use radiological or neurological slices convention = "index" else: convention = self._convention world_to_slice = medipy.base.coordinate_system.slices[convention][ name] slice = Slice(world_to_slice, self._layers, self._annotations, self._interpolation, self._display_coordinates, self._scalar_bar_visibility, self._orientation_visibility, slice_mode != "multiplanar", self.crosshair) if slice_mode == "multiplanar": slice.renderer.SetViewport(*self._viewport[name]) else: slice.renderer.SetViewport(0., 0., 1., 1.) self._rwi.GetRenderWindow().AddRenderer(slice.renderer) slice.setup_rwi(self._rwi) self._slices.append(slice) self._slices_names.append(name) for index, slice in enumerate(self._slices): for event in slice.allowed_events: if event in [ "any", "cursor_position", "image_position", "center", "corner_annotations_visibility", "world_to_slice", "layer_visibility" ]: continue slice.add_observer(event, self._on_slice_event) slice.add_observer("cursor_position", self._on_cursor_position) slice.add_observer("center", self._on_center) slice.add_observer("layer_visibility", self._on_layer_visibility) # Synchronize the Slices' colormaps # Do not use PropertySynchronized API for better event handling # and thus better performances if len(self._slices) > 1: next_slice = self._slices[(index + 1) % len(self._slices)] for layer_index, layer in enumerate(slice.layers): for event in [ "data", "display_range", "cut_low", "cut_high", "zero_transparency" ]: layer.colormap.add_observer(event, self._on_colormap_event) next_slice_layer = next_slice.layers[layer_index] layer.colormap.append_child(next_slice_layer.colormap) # Add a 4th renderer with image info in multiplanar mode, otherwise # display the corner annotations if slice_mode == "multiplanar": self._rwi.GetRenderWindow().AddRenderer( self._informations_renderer) self._informations_corner_annotation.SetVisibility( self.corner_annotations_visibility) else: self._slices[ 0].corner_annotations_visibility = self.corner_annotations_visibility for button, (class_, args, kwargs) in self._mouse_tools.items(): self.set_mouse_button_tool(button, class_, *args, **kwargs) for key, (class_, args, kwargs) in self._keyboard_tools.items(): self.set_keyboard_tool(key, class_, *args, **kwargs) # Keep the same pixel under the cursor and centered in the view self._locked = True if old_cursor_position is not None: self._set_cursor_index_position(old_cursor_position) self._locked = False self._update_informations() def _get_number_of_layers(self): return len(self._layers) def _get_interpolation(self): return self._interpolation def _set_interpolation(self, interpolation): self._set_slice_property("interpolation", interpolation) def _get_display_coordinates(self): return self._display_coordinates def _set_display_coordinates(self, display_coordinates): if display_coordinates not in [ "physical", "nearest_axis_aligned", "index" ]: raise medipy.base.Exception("Unknown display coordinates : %s" % (display_coordinates, )) self._set_slice_property("display_coordinates", display_coordinates) # Update world_to_slice, necessary when moving to/from index if self._display_coordinates == "index": # Do not use radiological or neurological slices convention = "index" else: convention = self._convention for index, slice in enumerate(self._slices): name = self._slices_names[index] world_to_slice = medipy.base.coordinate_system.slices[convention][ name] slice.world_to_slice = world_to_slice def _get_scalar_bar_visibility(self): return self._scalar_bar_visibility def _set_scalar_bar_visibility(self, scalar_bar_visibility): self._set_slice_property("scalar_bar_visibility", scalar_bar_visibility) def _get_orientation_visibility(self): return self._orientation_visibility def _set_orientation_visibility(self, orientation_visibility): self._set_slice_property("orientation_visibility", orientation_visibility) def _get_convention(self): """ Image viewing convention, can be either : * radiological (left-is-right) * neurological (left-is-left) """ return self._convention def _set_convention(self, convention): if convention not in ["radiological", "neurological"]: raise medipy.base.Exception( "Unknown viewing convention : {0}".format(convention)) self._convention = convention if self._display_coordinates == "index": # Do not use radiological or neurological slices convention = "index" for index, slice in enumerate(self._slices): name = self._slices_names[index] world_to_slice = medipy.base.coordinate_system.slices[convention][ name] slice.world_to_slice = world_to_slice def _get_crosshair(self): """ Display mode of the crosshair. """ return self._crosshair def _set_crosshair(self, value): if value not in ["full", "partial", "none"]: raise medipy.base.Exception( "Unknown crosshair mode: {0!r}".format(value)) self._set_slice_property("crosshair", value) def _get_corner_annotations_visibility(self): """ Visibility of the corner annotations. """ return self._corner_annotations_visibility def _set_corner_annotations_visibility(self, value): self._corner_annotations_visibility = value if self._slice_mode == "multiplanar": self._informations_corner_annotation.SetVisibility(value) else: self._set_slice_property("corner_annotations_visibility", value) def _get_annotations(self): return self._annotations def _set_annotations(self, annotations): if self._annotations is not None: self._annotations.remove_observer("any", self._on_annotations_changed) self._annotations = annotations self._annotations.add_observer("any", self._on_annotations_changed) for slice in self._slices: slice.annotations = annotations def _get_cursor_physical_position(self): return self._cursor_physical_position def _set_cursor_physical_position(self, cursor_physical_position): self._cursor_physical_position = cursor_physical_position if self._layers: image = self._layers[0]["image"] self._cursor_index_position = image.physical_to_index( cursor_physical_position) else: self._cursor_index_position = cursor_physical_position if self._slices: # All slices are synchronized self._slices[0].cursor_physical_position = cursor_physical_position self._rwi.Render() self.notify_observers("cursor_position") def _get_cursor_index_position(self): return self._cursor_index_position def _set_cursor_index_position(self, cursor_index_position): if self._layers: image = self._layers[0]["image"] cursor_physical_position = image.index_to_physical( cursor_index_position) else: cursor_physical_position = cursor_index_position self._set_cursor_physical_position(cursor_physical_position) def _get_center_physical_position(self): if self._center_physical_position is not None: return self._center_physical_position elif self._slices: self._center_physical_position = self._slices[ 0].image_physical_position return self._center_physical_position else: return None def _set_center_physical_position(self, position): self._center_physical_position = position if self._layers: image = self._layers[0]["image"] self._center_index_position = image.physical_to_index(position) else: self._center_index_position = cursor_physical_position if self._slices: # All slices are synchronized self._slices[0].center_on_physical_position(position) self._rwi.Render() self.notify_observers("cursor_position") def _get_center_index_position(self): if self._center_index_position is not None: return self._center_index_position elif self._slices: return self._slices[0].image_index_position else: return None def _set_center_index_position(self, position): if self._layers: image = self._layers[0]["image"] center_physical_position = image.index_to_physical(position) else: center_physical_position = position self._set_center_physical_position(center_physical_position) def _get_zoom(self): return self._zoom def _set_zoom(self, zoom): self._set_slice_property("zoom", zoom) layers = property(_get_layers) slice_mode = property(_get_slice_mode, _set_slice_mode) number_of_layers = property(_get_number_of_layers) interpolation = property(_get_interpolation, _set_interpolation) display_coordinates = property(_get_display_coordinates, _set_display_coordinates) scalar_bar_visibility = property(_get_scalar_bar_visibility, _set_scalar_bar_visibility) orientation_visibility = property(_get_orientation_visibility, _set_orientation_visibility) convention = property(_get_convention, _set_convention) crosshair = property(_get_crosshair, _set_crosshair) corner_annotations_visibility = property( _get_corner_annotations_visibility, _set_corner_annotations_visibility) annotations = property(_get_annotations, _set_annotations) cursor_physical_position = property(_get_cursor_physical_position, _set_cursor_physical_position) cursor_index_position = property(_get_cursor_index_position, _set_cursor_index_position) center_physical_position = property(_get_center_physical_position, _set_center_physical_position) center_index_position = property(_get_center_index_position, _set_center_index_position) zoom = property(_get_zoom, _set_zoom) ##################### # Private interface # ##################### def _set_slice_property(self, name, value): """ Set a property across all slices, and notify observers. """ setattr(self, "_{0}".format(name), value) for slice in self._slices: slice.remove_observer(name, self._on_slice_event) for slice in self._slices: setattr(slice, name, value) for slice in self._slices: slice.add_observer(name, self._on_slice_event) self._rwi.Render() self.notify_observers(name) def _on_slice_event(self, event): # Don't use _set_xxx to avoid spurious events value = getattr(event.object, event.event) setattr(self, "_%s" % event.event, value) for slice in self._slices: slice.remove_observer(event.event, self._on_slice_event) for slice in self._slices: if slice != event.object: setattr(slice, event.event, value) self.notify_observers(event.event) for slice in self._slices: slice.add_observer(event.event, self._on_slice_event) if event.event == "zoom": self._update_informations() def _on_colormap_event(self, event): if self._slices: try: layers_colormaps = [x["colormap"] for x in self._layers] layer_index = layers_colormaps.index(event.object) except ValueError: logging.warning("No such colormap in layers {0}".format(self)) else: self.notify_observers("colormap_{0}".format(event.event), layer_index=layer_index) def _on_cursor_position(self, event): self._cursor_index_position = event.object.cursor_index_position self._cursor_physical_position = event.object.cursor_physical_position for slice in self._slices: slice.remove_observer(event.event, self._on_cursor_position) for slice in self._slices: if slice != event.object: slice.cursor_physical_position = self._cursor_physical_position self.notify_observers(event.event) for slice in self._slices: slice.add_observer(event.event, self._on_cursor_position) self._update_informations() def _on_annotations_changed(self, event): self.render() def _on_center(self, event): self._center_index_position = event.object.image_index_position self._center_physical_position = event.object.image_physical_position for slice in self._slices: slice.remove_observer(event.event, self._on_center) for slice in self._slices: if slice != event.object: slice.center_on_physical_position( self._cursor_physical_position) self.notify_observers(event.event) for slice in self._slices: slice.add_observer(event.event, self._on_center) self._update_informations() def _on_layer_visibility(self, event): self.notify_observers("layer_visibility", index=event.index) def _on_button_down(self, event): """ Propagate mouse clicks to parent. """ event.Skip() new_event = event.Clone() new_event.SetEventObject(self) self.AddPendingEvent(new_event) def _on_close(self, event): for slice in self._slices: slice.close() slice.unset_rwi(self._rwi) self._rwi.Disable() self._rwi.Close() self.Destroy() def _update_informations(self): informations = get_informations(self) if self._slice_mode == "multiplanar": for where, label in informations.items(): index = self._corner_annotation_index[where] self._informations_corner_annotation.SetText(index, label) else: for where, label in informations.items(): self._slices[0].set_label(where, label)
app = wx.App() frame = wx.Frame(None) app.SetTopWindow(frame) ok_button = wx.Button(frame, id=wx.ID_OK, label="OK") reset_button = wx.Button(frame, id=wx.ID_RESET, label="Reset") ok_button.Bind( wx.EVT_BUTTON, lambda event: sys.stdout.write("Value is %s (%s)\n" % (control.value, "valid" if control.validate() else "invalid"))) reset_button.Bind(wx.EVT_BUTTON, lambda event: control.reset()) scenes = ObservableList() for i in range(0): scenes.append(Viewer3DFrame(None, ObservableList())) control = Object3D( frame, scenes, # value="machin", output_checked=False) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(control, flag=wx.EXPAND) button_sizer = wx.BoxSizer(wx.HORIZONTAL) button_sizer.Add(ok_button)
def load_metadata(self, index=0): format = NiftiFormat(self._filename) metadata = {} # Get the number of dimensions in the image from the extent nb_dimensions = len(format.extent) for i in range(max(len(format.extent) - 3, 0)): if format.extent[-1] == 1: nb_dimensions -= 1 else: break # Get spacing and origin for those dimensions. Reverse to keep the order # of the numpy array metadata["spacing"] = format.asDict()["pixdim"][1:nb_dimensions + 1] metadata["spacing"].reverse() metadata["spacing"] = numpy.asarray(metadata["spacing"]) if format.asDict()["scl_slope"] != 0: metadata["slope"] = format.asDict()["scl_slope"] metadata["shift"] = format.asDict()["scl_inter"] metadata["header"] = format.asDict() metadata["annotations"] = ObservableList() ######################### # Diffusion information # ######################### # Load gradient direction file base_name = os.path.splitext(self._filename)[0] if base_name.endswith(".nii"): base_name = os.path.splitext(base_name)[0] gradient_candidates = [ base_name + ".bvecs", # /foo/bar/image.bvecs base_name + ".bvec", # /foo/bar/image.bvec os.path.join(os.path.dirname(self._filename), "bvecs"), # /foo/bar/bvecs os.path.join(os.path.dirname(self._filename), "bvec") # /foo/bar/bvec ] gradient_file = None for candidate in gradient_candidates: if os.path.isfile(candidate): gradient_file = candidate break # Load b-values file bvalue_candidates = [ base_name + ".bvals", # /foo/bar/image.bvals base_name + ".bval", # /foo/bar/image.bval os.path.join(os.path.dirname(self._filename), "bval"), # /foo/bar/bvals os.path.join(os.path.dirname(self._filename), "bvals") # /foo/bar/bval ] bvalue_file = None for candidate in bvalue_candidates: if os.path.isfile(candidate): bvalue_file = candidate break if None not in [gradient_file, bvalue_file]: gradients = numpy.loadtxt(gradient_file, dtype=numpy.single) bvalues = numpy.loadtxt(bvalue_file, dtype=numpy.single) gradients = gradients.T mr_diffusion_sequence = [] for index, gradient in enumerate(gradients): dataset = medipy.io.dicom.DataSet() dataset.diffusion_directionality = "DIRECTIONAL" dataset.diffusion_bvalue = bvalues[index] gradient_dataset = medipy.io.dicom.DataSet() gradient_dataset.diffusion_gradient_orientation = gradient dataset.diffusion_gradient_direction_sequence = [ gradient_dataset ] mr_diffusion_sequence.append(dataset) metadata["mr_diffusion_sequence"] = mr_diffusion_sequence return metadata
def __init__(self, world_to_slice, layers=None, annotations=None, interpolation=False, display_coordinates="physical", scalar_bar_visibility=False, orientation_visibility=True, corner_annotations_visibility=False, crosshair="full"): layers = layers or [] annotations = annotations or ObservableList() ############################ # Property-related members # ############################ self._interpolation = None self._display_coordinates = None self._scalar_bar_visibility = True self._orientation_visibility = None self._corner_annotations_visibility = None self._crosshair = None self._world_to_slice = None self._slice_to_world = None self._layers = [] self._annotations = None self._gui_annotations = {} self._image_physical_position = None self._image_index_position = None self._cursor_physical_position = None self._cursor_index_position = None self._zoom = None self._mouse_tools = {} self._keyboard_tools = {} self._renderer = vtkRenderer() ################### # Private members # ################### # World-to-slice matrix, with rows and columns added or removed so that # it is 3x3. self._3d_world_to_slice = None self._3d_slice_to_world = None # Slice extent is the physical extent of all layers, # given as (x_min, x_max, y_min, y_max) self._slice_extent = (-100, 100, -100, 100) # VTK objects self._scalar_bar_actor = vtkScalarBarActor() self._corner_annotation = vtkCornerAnnotation() self._orientation_annotation = vtkOrientationAnnotation() self._crosshair = Crosshair() # Tools and interactions self._observer_tags = [] self._active_source = None ################## # Initialization # ################## super(Slice, self).__init__([ "world_to_slice", "interpolation", "display_coordinates", "scalar_bar_visibility", "orientation_visibility", "corner_annotations_visibility", "crosshair", "zoom" ]) self.add_allowed_event("cursor_position") self.add_allowed_event("image_position") self.add_allowed_event("center") self.add_allowed_event("layer_visibility") # Configure camera camera = self._renderer.GetActiveCamera() camera.ParallelProjectionOn() camera.SetPosition(0, 0, self._actors_altitudes["camera"]) camera.SetFocalPoint(0, 0, 0) # Create cursor self._crosshair.altitude = self._actors_altitudes["cursor"] self._crosshair.hole_size = 5 self._renderer.AddActor(self._crosshair.actor) # Create scalar bar (from vtkInria3D) self._scalar_bar_actor.GetLabelTextProperty().SetColor(1.0, 1.0, 1.0) self._scalar_bar_actor.GetTitleTextProperty().SetColor(1.0, 1.0, 1.0) self._scalar_bar_actor.GetLabelTextProperty().BoldOff() self._scalar_bar_actor.GetLabelTextProperty().ShadowOff() self._scalar_bar_actor.GetLabelTextProperty().ItalicOff() self._scalar_bar_actor.SetNumberOfLabels(3) self._scalar_bar_actor.GetLabelTextProperty().SetFontSize(8) self._scalar_bar_actor.GetPositionCoordinate( ).SetCoordinateSystemToNormalizedViewport() self._scalar_bar_actor.SetWidth(0.1) self._scalar_bar_actor.SetHeight(0.5) self._scalar_bar_actor.SetPosition(0.8, 0.3) self._scalar_bar_actor.PickableOff() self._renderer.AddActor(self._scalar_bar_actor) # Setup text-annotation actors self._corner_annotation.SetNonlinearFontScaleFactor(0.3) self._renderer.AddActor(self._corner_annotation) self._orientation_annotation.SetNonlinearFontScaleFactor(0.25) self._renderer.AddActor(self._orientation_annotation) self._set_interpolation(interpolation) self._set_display_coordinates(display_coordinates) self._set_scalar_bar_visibility(scalar_bar_visibility) self._set_orientation_visibility(orientation_visibility) self._set_corner_annotations_visibility(corner_annotations_visibility) self._set_crosshair(crosshair) self._set_world_to_slice(world_to_slice) for layer in layers: self.append_layer(**layer) if annotations is not None: self._set_annotations(annotations) # Position slice at middle of layer 0 self.reset_view() # Configure default tools self.set_mouse_button_tool("Left", mouse_tools.Select()) self.set_mouse_button_tool("Middle", mouse_tools.Pan()) self.set_mouse_button_tool("Right", mouse_tools.WindowLevel()) self.set_wheel_tool("Forward", mouse_tools.Zoom(1.1)) self.set_wheel_tool("Backward", mouse_tools.Zoom(1. / 1.1)) self.set_keyboard_tool("Left", keyboard_tools.MoveCursor()) self.set_keyboard_tool("Right", keyboard_tools.MoveCursor()) self.set_keyboard_tool("Up", keyboard_tools.MoveCursor()) self.set_keyboard_tool("Down", keyboard_tools.MoveCursor()) self.set_keyboard_tool("Prior", keyboard_tools.MoveCursor()) self.set_keyboard_tool("Next", keyboard_tools.MoveCursor()) self.set_keyboard_tool("PageUp", keyboard_tools.MoveCursor()) self.set_keyboard_tool("PageDown", keyboard_tools.MoveCursor()) self.set_keyboard_tool("+", keyboard_tools.Zoom(1.1)) self.set_keyboard_tool("-", keyboard_tools.Zoom(1. / 1.1)) self.set_keyboard_tool("i", keyboard_tools.ToggleInterpolation()) self.set_keyboard_tool("b", keyboard_tools.ToggleScalarBarVisibility()) self.set_keyboard_tool( "c", keyboard_tools.ToggleCornerAnnotationsVisibility()) self.set_keyboard_tool("o", keyboard_tools.ToggleOrientationVisibility())