From 615280da8db5b1488db76d00450f29f21186cc38 Mon Sep 17 00:00:00 2001 From: Victor Hugo Souza Date: Thu, 14 Oct 2021 21:25:39 +0300 Subject: [PATCH] FIX: Save affine in project and refactor instances of affine creation in OtherProjects (#386) --- invesalius/control.py | 56 +++++++++++++++++++++++++++----------------------------- invesalius/data/surface.py | 24 +++++++++--------------- invesalius/gui/dialogs.py | 18 ++---------------- invesalius/gui/frame.py | 14 ++++++-------- invesalius/project.py | 2 -- 5 files changed, 44 insertions(+), 70 deletions(-) diff --git a/invesalius/control.py b/invesalius/control.py index e0e9834..025c5a9 100644 --- a/invesalius/control.py +++ b/invesalius/control.py @@ -122,8 +122,6 @@ class Controller(): Publisher.subscribe(self.OnSaveProject, 'Save project') - Publisher.subscribe(self.Send_affine, 'Get affine matrix') - Publisher.subscribe(self.create_project_from_matrix, 'Create project from matrix') Publisher.subscribe(self.show_mask_preview, 'Show mask preview') @@ -506,8 +504,9 @@ class Controller(): utils.debug("No medical images found on given directory") return - matrix, matrix_filename = self.OpenOtherFiles(group) - self.CreateOtherProject(str(name[0]), matrix, matrix_filename) + if group: + matrix, matrix_filename = self.OpenOtherFiles(group) + self.CreateOtherProject(str(name[0]), matrix, matrix_filename) # OPTION 4: Nothing... self.LoadProject() @@ -623,6 +622,11 @@ class Controller(): Publisher.sendMessage(('Set scroll position', 'AXIAL'), index=proj.matrix_shape[0]/2) Publisher.sendMessage(('Set scroll position', 'SAGITAL'),index=proj.matrix_shape[1]/2) Publisher.sendMessage(('Set scroll position', 'CORONAL'),index=proj.matrix_shape[2]/2) + + if self.Slice.affine is not None: + Publisher.sendMessage('Enable Go-to-Coord', status=True) + else: + Publisher.sendMessage('Enable Go-to-Coord', status=False) Publisher.sendMessage('End busy cursor') @@ -715,9 +719,6 @@ class Controller(): proj.matrix_dtype = matrix.dtype.name proj.matrix_filename = matrix_filename - if self.affine is not None: - proj.affine = self.affine.tolist() - # Orientation must be CORONAL in order to as_closes_canonical and # swap axis in img2memmap to work in a standardized way. # TODO: Create standard import image for all acquisition orientations @@ -730,6 +731,8 @@ class Controller(): proj.level = self.Slice.window_level proj.threshold_range = int(matrix.min()), int(matrix.max()) proj.spacing = self.Slice.spacing + if self.Slice.affine is not None: + proj.affine = self.Slice.affine.tolist() ###### session = ses.Session() @@ -915,16 +918,7 @@ class Controller(): matrix, matrix_filename = self.OpenOtherFiles(group) self.CreateOtherProject(name, matrix, matrix_filename) self.LoadProject() - if group.affine.any(): - # TODO: replace the inverse of the affine by the actual affine in the whole code - # remove scaling factor for non-unitary voxel dimensions - # self.affine = image_utils.world2invspace(affine=group.affine) - scale, shear, angs, trans, persp = tr.decompose_matrix(group.affine) - self.affine = np.linalg.inv(tr.compose_matrix(scale=None, shear=shear, - angles=angs, translate=trans, perspective=persp)) - # print("repos_img: {}".format(repos_img)) - self.Slice.affine = self.affine - Publisher.sendMessage('Update affine matrix', affine=self.affine) + Publisher.sendMessage("Enable state project", state=True) else: dialog.ImportInvalidFiles(ftype="Others") @@ -1034,13 +1028,7 @@ class Controller(): self.matrix, scalar_range, self.filename = image_utils.img2memmap(group) hdr = group.header - # if group.affine.any(): - # self.affine = group.affine - # Publisher.sendMessage('Update affine matrix', - # affine=self.affine, status=True) hdr.set_data_dtype('int16') - dims = hdr.get_zooms() - dimsf = tuple([float(s) for s in dims]) wl = float((scalar_range[0] + scalar_range[1]) * 0.5) ww = float((scalar_range[1] - scalar_range[0])) @@ -1048,19 +1036,29 @@ class Controller(): self.Slice = sl.Slice() self.Slice.matrix = self.matrix self.Slice.matrix_filename = self.filename - - self.Slice.spacing = dimsf + # even though the axes 0 and 2 are swapped when creating self.matrix + # the spacing should be kept the original, as it is modified somewhere later + # otherwise generate wrong results + # also need to convert to float because original get_zooms return numpy.float32 + # which is unsupported by the plist for saving the project + self.Slice.spacing = tuple([float(s) for s in hdr.get_zooms()]) self.Slice.window_level = wl self.Slice.window_width = ww + if group.affine.any(): + # remove scaling factor for non-unitary voxel dimensions + scale, shear, angs, trans, persp = tr.decompose_matrix(group.affine) + self.Slice.affine = np.linalg.inv(tr.compose_matrix(scale=None, shear=shear, + angles=angs, translate=trans, perspective=persp)) + else: + self.Slice.affine = None + scalar_range = int(scalar_range[0]), int(scalar_range[1]) + Publisher.sendMessage('Update threshold limits list', threshold_range=scalar_range) - return self.matrix, self.filename - def Send_affine(self): - if self.affine is not None: - Publisher.sendMessage('Update affine matrix', affine=self.affine) + return self.matrix, self.filename def LoadImagedataInfo(self): proj = prj.Project() diff --git a/invesalius/data/surface.py b/invesalius/data/surface.py index c9bcd65..955f5b8 100644 --- a/invesalius/data/surface.py +++ b/invesalius/data/surface.py @@ -51,6 +51,7 @@ else: import invesalius.constants as const import invesalius.data.imagedata_utils as iu +import invesalius.data.slice_ as sl import invesalius.data.polydata_utils as pu import invesalius.project as prj import invesalius.session as ses @@ -159,7 +160,6 @@ class SurfaceManager(): def __init__(self): self.actors_dict = {} self.last_surface_index = 0 - self.affine_vtk = None self.convert2inv = None self.__bind_events() @@ -207,7 +207,6 @@ class SurfaceManager(): Publisher.subscribe(self.OnImportSurfaceFile, 'Import surface file') - Publisher.subscribe(self.UpdateAffineMatrix, 'Update affine matrix') Publisher.subscribe(self.UpdateConvert2InvFlag, 'Update convert2inv flag') Publisher.subscribe(self.CreateSurfaceFromPolydata, 'Create surface from polydata') @@ -338,16 +337,6 @@ class SurfaceManager(): name = os.path.splitext(os.path.split(filename)[-1])[0] self.CreateSurfaceFromPolydata(polydata, name=name, scalar=scalar) - def UpdateAffineMatrix(self, affine): - if affine is not None: - prj_data = prj.Project() - matrix_shape = tuple(prj_data.matrix_shape) - affine = affine.copy() - affine[1, -1] -= matrix_shape[1] - self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) - else: - self.affine_vtk = None - def UpdateConvert2InvFlag(self, convert2inv=False): self.convert2inv = convert2inv @@ -373,12 +362,17 @@ class SurfaceManager(): actor.SetMapper(mapper) actor.GetProperty().SetBackfaceCulling(1) - if self.convert2inv and (self.affine_vtk is not None): - actor.SetUserMatrix(self.affine_vtk) + if self.convert2inv: + # convert between invesalius and world space with shift in the Y coordinate + affine = sl.Slice().affine + if affine is not None: + affine[1, -1] -= sl.Slice().spacing[1] * (sl.Slice().matrix.shape[1] - 1) + affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) + actor.SetUserMatrix(affine_vtk) if overwrite: if index is None: - index = self.last_surface_index + index = self.last_surface_index surface = Surface(index=index) else: surface = Surface() diff --git a/invesalius/gui/dialogs.py b/invesalius/gui/dialogs.py index 86e191e..8639116 100644 --- a/invesalius/gui/dialogs.py +++ b/invesalius/gui/dialogs.py @@ -4200,37 +4200,23 @@ class GoToDialogScannerCoord(wx.Dialog): self.__bind_events() btn_ok.Bind(wx.EVT_BUTTON, self.OnOk) - Publisher.sendMessage('Get affine matrix') def __bind_events(self): Publisher.subscribe(self.SetNewFocalPoint, 'Cross focal point') - Publisher.subscribe(self.UpdateAffineMatrix, 'Update affine matrix') - - def UpdateAffineMatrix(self, affine): - self.affine = affine def SetNewFocalPoint(self, coord, spacing): Publisher.sendMessage('Update cross pos', coord=self.result*spacing) def OnOk(self, evt): - from numpy.linalg import inv import invesalius.data.slice_ as slc try: - #get affine from image import - if self.affine is not None: - affine = self.affine - #get affine from project - else: - from invesalius.project import Project - affine = Project().affine - point = [float(self.goto_sagital.GetValue()), float(self.goto_coronal.GetValue()), float(self.goto_axial.GetValue())] # transformation from scanner coordinates to inv coord system - affine = inv(affine) - self.result = np.dot(affine[:3, :3], np.transpose(point[0:3])) + affine[:3, 3] + affine_inverse = np.linalg.inv(slc.Slice().affine) + self.result = np.dot(affine_inverse[:3, :3], np.transpose(point[0:3])) + affine_inverse[:3, 3] self.result[1] = slc.Slice().GetMaxSliceNumber(const.CORONAL_STR) - self.result[1] Publisher.sendMessage('Update status text in GUI', label=_("Calculating the transformation ...")) diff --git a/invesalius/gui/frame.py b/invesalius/gui/frame.py index adbf036..7402b5f 100644 --- a/invesalius/gui/frame.py +++ b/invesalius/gui/frame.py @@ -892,7 +892,7 @@ class MenuBar(wx.MenuBar): sub(self.OnEnableState, "Enable state project") sub(self.OnEnableUndo, "Enable undo") sub(self.OnEnableRedo, "Enable redo") - sub(self.OnEnableGotoCoord, "Update affine matrix") + sub(self.OnEnableGotoCoord, "Enable Go-to-Coord") sub(self.OnEnableNavigation, "Navigation status") sub(self.OnAddMask, "Add mask") @@ -1242,15 +1242,13 @@ class MenuBar(wx.MenuBar): else: self.FindItemById(wx.ID_REDO).Enable(False) - def OnEnableGotoCoord(self, affine): + def OnEnableGotoCoord(self, status=True): """ - Disable goto coord either if there is no affine matrix or affine is wrongly imported. - :param status: Affine matrix status + Enable or disable goto coord depending on the imported affine matrix. + :param status: True for enabling and False for disabling the Go-To-Coord """ - if affine is not None: - self.FindItemById(const.ID_GOTO_COORD).Enable(True) - else: - self.FindItemById(const.ID_GOTO_COORD).Enable(False) + + self.FindItemById(const.ID_GOTO_COORD).Enable(status) def OnEnableNavigation(self, nav_status, vis_status): """ diff --git a/invesalius/project.py b/invesalius/project.py index 7e638a9..3ad37e0 100644 --- a/invesalius/project.py +++ b/invesalius/project.py @@ -329,8 +329,6 @@ class Project(metaclass=Singleton): if project.get("affine", ""): self.affine = project["affine"] - Publisher.sendMessage('Update affine matrix', - affine=np.asarray(self.affine).reshape(4, 4)) # Opening the masks self.mask_dict = TwoWaysDictionary() -- libgit2 0.21.2