Example #1
0
 def create_3d_preview(self):
     if self.volume is None:
         if self.imagedata is None:
             self.imagedata = self.as_vtkimagedata()
         self.volume = VolumeMask(self)
         self.volume.create_volume()
Example #2
0
class Mask():
    general_index = -1

    def __init__(self):
        Mask.general_index += 1
        self.index = Mask.general_index
        self.matrix = None
        self.spacing = (1.0, 1.0, 1.0)
        self.imagedata = None
        self.colour = random.choice(const.MASK_COLOUR)
        self.opacity = const.MASK_OPACITY
        self.threshold_range = const.THRESHOLD_RANGE
        self.name = const.MASK_NAME_PATTERN % (Mask.general_index + 1)
        self.edition_threshold_range = [
            const.THRESHOLD_OUTVALUE, const.THRESHOLD_INVALUE
        ]
        self.is_shown = 1
        self.edited_points = {}
        self.was_edited = False
        self.volume = None
        self.auto_update_mask = True
        self.modified_time = 0
        self.__bind_events()
        self._modified_callbacks = []

        self.history = EditionHistory()

    def __bind_events(self):
        Publisher.subscribe(self.OnFlipVolume, 'Flip volume')
        Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes')

    def as_vtkimagedata(self):
        print("Converting to VTK")
        vimg = converters.to_vtk_mask(self.matrix, self.spacing)
        print("Converted")
        return vimg

    def set_colour(self, colour):
        self.colour = colour
        if self.volume is not None:
            self.volume.set_colour(colour)
            Publisher.sendMessage("Render volume viewer")

    def save_history(self, index, orientation, array, p_array, clean=False):
        self.history.new_node(index, orientation, array, p_array, clean)

    def undo_history(self, actual_slices):
        self.history.undo(self.matrix, actual_slices)
        self.modified()

        # Marking the project as changed
        session = ses.Session()
        session.ChangeProject()

    def redo_history(self, actual_slices):
        self.history.redo(self.matrix, actual_slices)
        self.modified()

        # Marking the project as changed
        session = ses.Session()
        session.ChangeProject()

    def on_show(self):
        self.history._config_undo_redo(self.is_shown)

    def create_3d_preview(self):
        if self.volume is None:
            if self.imagedata is None:
                self.imagedata = self.as_vtkimagedata()
            self.volume = VolumeMask(self)
            self.volume.create_volume()

    def _update_imagedata(self, update_volume_viewer=True):
        if self.imagedata is not None:
            dz, dy, dx = self.matrix.shape
            #  np_image = numpy_support.vtk_to_numpy(self.imagedata.GetPointData().GetScalars())
            #  np_image[:] = self.matrix.reshape(-1)
            self.imagedata.SetDimensions(dx - 1, dy - 1, dz - 1)
            self.imagedata.SetSpacing(self.spacing)
            self.imagedata.SetExtent(0, dx - 1, 0, dy - 1, 0, dz - 1)
            self.imagedata.Modified()
            self.volume._actor.Update()

            if update_volume_viewer:
                Publisher.sendMessage("Render volume viewer")

    def SavePlist(self, dir_temp, filelist):
        mask = {}
        filename = u'mask_%d' % self.index
        mask_filename = u'%s.dat' % filename
        mask_filepath = os.path.join(dir_temp, mask_filename)
        filelist[self.temp_file] = mask_filename
        #self._save_mask(mask_filepath)

        mask['index'] = self.index
        mask['name'] = self.name
        mask['colour'] = self.colour[:3]
        mask['opacity'] = self.opacity
        mask['threshold_range'] = self.threshold_range
        mask['edition_threshold_range'] = self.edition_threshold_range
        mask['visible'] = self.is_shown
        mask['mask_file'] = mask_filename
        mask['mask_shape'] = self.matrix.shape
        mask['edited'] = self.was_edited

        plist_filename = filename + u'.plist'
        #plist_filepath = os.path.join(dir_temp, plist_filename)

        temp_plist = tempfile.mktemp()
        plistlib.writePlist(mask, temp_plist)

        filelist[temp_plist] = plist_filename

        return plist_filename

    def OpenPList(self, filename):
        mask = plistlib.readPlist(filename)

        self.index = mask['index']
        self.name = mask['name']
        self.colour = mask['colour']
        self.opacity = mask['opacity']
        self.threshold_range = mask['threshold_range']
        self.edition_threshold_range = mask['edition_threshold_range']
        self.is_shown = mask['visible']
        mask_file = mask['mask_file']
        shape = mask['mask_shape']
        self.was_edited = mask.get('edited', False)

        dirpath = os.path.abspath(os.path.split(filename)[0])
        path = os.path.join(dirpath, mask_file)
        self._open_mask(path, tuple(shape))

    def OnFlipVolume(self, axis):
        submatrix = self.matrix[1:, 1:, 1:]
        if axis == 0:
            submatrix[:] = submatrix[::-1]
            self.matrix[1::, 0, 0] = self.matrix[:0:-1, 0, 0]
        elif axis == 1:
            submatrix[:] = submatrix[:, ::-1]
            self.matrix[0, 1::, 0] = self.matrix[0, :0:-1, 0]
        elif axis == 2:
            submatrix[:] = submatrix[:, :, ::-1]
            self.matrix[0, 0, 1::] = self.matrix[0, 0, :0:-1]
        self.modified()

    def OnSwapVolumeAxes(self, axes):
        axis0, axis1 = axes
        self.matrix = self.matrix.swapaxes(axis0, axis1)
        if self.volume:
            self.imagedata = self.as_vtkimagedata()
            self.volume.change_imagedata()
        self.modified()

    def _save_mask(self, filename):
        shutil.copyfile(self.temp_file, filename)

    def _open_mask(self, filename, shape, dtype='uint8'):
        print(">>", filename, shape)
        self.temp_file = filename
        self.matrix = np.memmap(filename, shape=shape, dtype=dtype, mode="r+")

    def _set_class_index(self, index):
        Mask.general_index = index

    def add_modified_callback(self, callback):
        ref = weakref.WeakMethod(callback)
        self._modified_callbacks.append(ref)

    def remove_modified_callback(self, callback):
        callbacks = []
        removed = False
        for cb in self._modified_callbacks:
            if cb() is not None:
                if cb() != callback:
                    callbacks.append(cb)
                else:
                    removed = True
        self._modified_callbacks = callbacks
        return removed

    def create_mask(self, shape):
        """
        Creates a new mask object. This method do not append this new mask into the project.

        Parameters:
            shape(int, int, int): The shape of the new mask.
        """
        self.temp_file = tempfile.mktemp()
        shape = shape[0] + 1, shape[1] + 1, shape[2] + 1
        self.matrix = np.memmap(self.temp_file,
                                mode='w+',
                                dtype='uint8',
                                shape=shape)

    def modified(self, all_volume=False):
        if all_volume:
            self.matrix[0] = 1
            self.matrix[:, 0, :] = 1
            self.matrix[:, :, 0] = 1
        if ses.Session().auto_reload_preview:
            self._update_imagedata()
        self.modified_time = time.monotonic()
        callbacks = []
        print(self._modified_callbacks)
        for callback in self._modified_callbacks:
            if callback() is not None:
                callback()()
                callbacks.append(callback)
        self._modified_callbacks = callbacks

    def clean(self):
        self.matrix[1:, 1:, 1:] = 0
        self.modified(all_volume=True)

    def cleanup(self):
        if self.is_shown:
            self.history._config_undo_redo(False)
        if self.volume:
            Publisher.sendMessage("Unload volume", volume=self.volume._actor)
            Publisher.sendMessage("Render volume viewer")
            self.imagedata = None
            self.volume = None
        del self.matrix

    def copy(self, copy_name):
        """
        creates and return a copy from the mask instance.

        params:
            copy_name: the name from the copy
        """
        new_mask = Mask()
        new_mask.name = copy_name
        new_mask.colour = self.colour
        new_mask.opacity = self.opacity
        new_mask.threshold_range = self.threshold_range
        new_mask.edition_threshold_range = self.edition_threshold_range
        new_mask.is_shown = self.is_shown

        new_mask.create_mask(shape=[i - 1 for i in self.matrix.shape])
        new_mask.matrix[:] = self.matrix[:]
        new_mask.spacing = self.spacing

        return new_mask

    def clear_history(self):
        self.history.clear_history()

    def fill_holes_auto(self, target, conn, orientation, index, size):
        CON2D = {4: 1, 8: 2}
        CON3D = {6: 1, 18: 2, 26: 3}

        if target == '3D':
            cp_mask = self.matrix.copy()
            matrix = self.matrix[1:, 1:, 1:]
            bstruct = ndimage.generate_binary_structure(3, CON3D[conn])

            imask = (~(matrix > 127))
            labels, nlabels = ndimage.label(imask, bstruct, output=np.uint16)

            if nlabels == 0:
                return

            ret = floodfill.fill_holes_automatically(matrix, labels, nlabels,
                                                     size)
            if ret:
                self.save_history(index, orientation, self.matrix.copy(),
                                  cp_mask)
        else:
            bstruct = ndimage.generate_binary_structure(2, CON2D[conn])

            if orientation == 'AXIAL':
                matrix = self.matrix[index + 1, 1:, 1:]
            elif orientation == 'CORONAL':
                matrix = self.matrix[1:, index + 1, 1:]
            elif orientation == 'SAGITAL':
                matrix = self.matrix[1:, 1:, index + 1]

            cp_mask = matrix.copy()

            imask = (~(matrix > 127))
            labels, nlabels = ndimage.label(imask, bstruct, output=np.uint16)

            if nlabels == 0:
                return

            labels = labels.reshape(1, labels.shape[0], labels.shape[1])
            matrix = matrix.reshape(1, matrix.shape[0], matrix.shape[1])

            ret = floodfill.fill_holes_automatically(matrix, labels, nlabels,
                                                     size)
            if ret:
                self.save_history(index, orientation, matrix.copy(), cp_mask)

    def __del__(self):
        os.remove(self.temp_file)