Commit 615280da8db5b1488db76d00450f29f21186cc38

Authored by Victor Hugo Souza
Committed by GitHub
1 parent 1239b41c
Exists in master

FIX: Save affine in project and refactor instances of affine creation in OtherProjects (#386)

invesalius/control.py
... ... @@ -122,8 +122,6 @@ class Controller():
122 122  
123 123 Publisher.subscribe(self.OnSaveProject, 'Save project')
124 124  
125   - Publisher.subscribe(self.Send_affine, 'Get affine matrix')
126   -
127 125 Publisher.subscribe(self.create_project_from_matrix, 'Create project from matrix')
128 126  
129 127 Publisher.subscribe(self.show_mask_preview, 'Show mask preview')
... ... @@ -506,8 +504,9 @@ class Controller():
506 504 utils.debug("No medical images found on given directory")
507 505 return
508 506  
509   - matrix, matrix_filename = self.OpenOtherFiles(group)
510   - self.CreateOtherProject(str(name[0]), matrix, matrix_filename)
  507 + if group:
  508 + matrix, matrix_filename = self.OpenOtherFiles(group)
  509 + self.CreateOtherProject(str(name[0]), matrix, matrix_filename)
511 510 # OPTION 4: Nothing...
512 511  
513 512 self.LoadProject()
... ... @@ -623,6 +622,11 @@ class Controller():
623 622 Publisher.sendMessage(('Set scroll position', 'AXIAL'), index=proj.matrix_shape[0]/2)
624 623 Publisher.sendMessage(('Set scroll position', 'SAGITAL'),index=proj.matrix_shape[1]/2)
625 624 Publisher.sendMessage(('Set scroll position', 'CORONAL'),index=proj.matrix_shape[2]/2)
  625 +
  626 + if self.Slice.affine is not None:
  627 + Publisher.sendMessage('Enable Go-to-Coord', status=True)
  628 + else:
  629 + Publisher.sendMessage('Enable Go-to-Coord', status=False)
626 630  
627 631 Publisher.sendMessage('End busy cursor')
628 632  
... ... @@ -715,9 +719,6 @@ class Controller():
715 719 proj.matrix_dtype = matrix.dtype.name
716 720 proj.matrix_filename = matrix_filename
717 721  
718   - if self.affine is not None:
719   - proj.affine = self.affine.tolist()
720   -
721 722 # Orientation must be CORONAL in order to as_closes_canonical and
722 723 # swap axis in img2memmap to work in a standardized way.
723 724 # TODO: Create standard import image for all acquisition orientations
... ... @@ -730,6 +731,8 @@ class Controller():
730 731 proj.level = self.Slice.window_level
731 732 proj.threshold_range = int(matrix.min()), int(matrix.max())
732 733 proj.spacing = self.Slice.spacing
  734 + if self.Slice.affine is not None:
  735 + proj.affine = self.Slice.affine.tolist()
733 736  
734 737 ######
735 738 session = ses.Session()
... ... @@ -915,16 +918,7 @@ class Controller():
915 918 matrix, matrix_filename = self.OpenOtherFiles(group)
916 919 self.CreateOtherProject(name, matrix, matrix_filename)
917 920 self.LoadProject()
918   - if group.affine.any():
919   - # TODO: replace the inverse of the affine by the actual affine in the whole code
920   - # remove scaling factor for non-unitary voxel dimensions
921   - # self.affine = image_utils.world2invspace(affine=group.affine)
922   - scale, shear, angs, trans, persp = tr.decompose_matrix(group.affine)
923   - self.affine = np.linalg.inv(tr.compose_matrix(scale=None, shear=shear,
924   - angles=angs, translate=trans, perspective=persp))
925   - # print("repos_img: {}".format(repos_img))
926   - self.Slice.affine = self.affine
927   - Publisher.sendMessage('Update affine matrix', affine=self.affine)
  921 +
928 922 Publisher.sendMessage("Enable state project", state=True)
929 923 else:
930 924 dialog.ImportInvalidFiles(ftype="Others")
... ... @@ -1034,13 +1028,7 @@ class Controller():
1034 1028 self.matrix, scalar_range, self.filename = image_utils.img2memmap(group)
1035 1029  
1036 1030 hdr = group.header
1037   - # if group.affine.any():
1038   - # self.affine = group.affine
1039   - # Publisher.sendMessage('Update affine matrix',
1040   - # affine=self.affine, status=True)
1041 1031 hdr.set_data_dtype('int16')
1042   - dims = hdr.get_zooms()
1043   - dimsf = tuple([float(s) for s in dims])
1044 1032  
1045 1033 wl = float((scalar_range[0] + scalar_range[1]) * 0.5)
1046 1034 ww = float((scalar_range[1] - scalar_range[0]))
... ... @@ -1048,19 +1036,29 @@ class Controller():
1048 1036 self.Slice = sl.Slice()
1049 1037 self.Slice.matrix = self.matrix
1050 1038 self.Slice.matrix_filename = self.filename
1051   -
1052   - self.Slice.spacing = dimsf
  1039 + # even though the axes 0 and 2 are swapped when creating self.matrix
  1040 + # the spacing should be kept the original, as it is modified somewhere later
  1041 + # otherwise generate wrong results
  1042 + # also need to convert to float because original get_zooms return numpy.float32
  1043 + # which is unsupported by the plist for saving the project
  1044 + self.Slice.spacing = tuple([float(s) for s in hdr.get_zooms()])
1053 1045 self.Slice.window_level = wl
1054 1046 self.Slice.window_width = ww
1055 1047  
  1048 + if group.affine.any():
  1049 + # remove scaling factor for non-unitary voxel dimensions
  1050 + scale, shear, angs, trans, persp = tr.decompose_matrix(group.affine)
  1051 + self.Slice.affine = np.linalg.inv(tr.compose_matrix(scale=None, shear=shear,
  1052 + angles=angs, translate=trans, perspective=persp))
  1053 + else:
  1054 + self.Slice.affine = None
  1055 +
1056 1056 scalar_range = int(scalar_range[0]), int(scalar_range[1])
  1057 +
1057 1058 Publisher.sendMessage('Update threshold limits list',
1058 1059 threshold_range=scalar_range)
1059   - return self.matrix, self.filename
1060 1060  
1061   - def Send_affine(self):
1062   - if self.affine is not None:
1063   - Publisher.sendMessage('Update affine matrix', affine=self.affine)
  1061 + return self.matrix, self.filename
1064 1062  
1065 1063 def LoadImagedataInfo(self):
1066 1064 proj = prj.Project()
... ...
invesalius/data/surface.py
... ... @@ -51,6 +51,7 @@ else:
51 51  
52 52 import invesalius.constants as const
53 53 import invesalius.data.imagedata_utils as iu
  54 +import invesalius.data.slice_ as sl
54 55 import invesalius.data.polydata_utils as pu
55 56 import invesalius.project as prj
56 57 import invesalius.session as ses
... ... @@ -159,7 +160,6 @@ class SurfaceManager():
159 160 def __init__(self):
160 161 self.actors_dict = {}
161 162 self.last_surface_index = 0
162   - self.affine_vtk = None
163 163 self.convert2inv = None
164 164 self.__bind_events()
165 165  
... ... @@ -207,7 +207,6 @@ class SurfaceManager():
207 207  
208 208 Publisher.subscribe(self.OnImportSurfaceFile, 'Import surface file')
209 209  
210   - Publisher.subscribe(self.UpdateAffineMatrix, 'Update affine matrix')
211 210 Publisher.subscribe(self.UpdateConvert2InvFlag, 'Update convert2inv flag')
212 211  
213 212 Publisher.subscribe(self.CreateSurfaceFromPolydata, 'Create surface from polydata')
... ... @@ -338,16 +337,6 @@ class SurfaceManager():
338 337 name = os.path.splitext(os.path.split(filename)[-1])[0]
339 338 self.CreateSurfaceFromPolydata(polydata, name=name, scalar=scalar)
340 339  
341   - def UpdateAffineMatrix(self, affine):
342   - if affine is not None:
343   - prj_data = prj.Project()
344   - matrix_shape = tuple(prj_data.matrix_shape)
345   - affine = affine.copy()
346   - affine[1, -1] -= matrix_shape[1]
347   - self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine)
348   - else:
349   - self.affine_vtk = None
350   -
351 340 def UpdateConvert2InvFlag(self, convert2inv=False):
352 341 self.convert2inv = convert2inv
353 342  
... ... @@ -373,12 +362,17 @@ class SurfaceManager():
373 362 actor.SetMapper(mapper)
374 363 actor.GetProperty().SetBackfaceCulling(1)
375 364  
376   - if self.convert2inv and (self.affine_vtk is not None):
377   - actor.SetUserMatrix(self.affine_vtk)
  365 + if self.convert2inv:
  366 + # convert between invesalius and world space with shift in the Y coordinate
  367 + affine = sl.Slice().affine
  368 + if affine is not None:
  369 + affine[1, -1] -= sl.Slice().spacing[1] * (sl.Slice().matrix.shape[1] - 1)
  370 + affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine)
  371 + actor.SetUserMatrix(affine_vtk)
378 372  
379 373 if overwrite:
380 374 if index is None:
381   - index = self.last_surface_index
  375 + index = self.last_surface_index
382 376 surface = Surface(index=index)
383 377 else:
384 378 surface = Surface()
... ...
invesalius/gui/dialogs.py
... ... @@ -4200,37 +4200,23 @@ class GoToDialogScannerCoord(wx.Dialog):
4200 4200 self.__bind_events()
4201 4201  
4202 4202 btn_ok.Bind(wx.EVT_BUTTON, self.OnOk)
4203   - Publisher.sendMessage('Get affine matrix')
4204 4203  
4205 4204 def __bind_events(self):
4206 4205 Publisher.subscribe(self.SetNewFocalPoint, 'Cross focal point')
4207   - Publisher.subscribe(self.UpdateAffineMatrix, 'Update affine matrix')
4208   -
4209   - def UpdateAffineMatrix(self, affine):
4210   - self.affine = affine
4211 4206  
4212 4207 def SetNewFocalPoint(self, coord, spacing):
4213 4208 Publisher.sendMessage('Update cross pos', coord=self.result*spacing)
4214 4209  
4215 4210 def OnOk(self, evt):
4216   - from numpy.linalg import inv
4217 4211 import invesalius.data.slice_ as slc
4218 4212 try:
4219   - #get affine from image import
4220   - if self.affine is not None:
4221   - affine = self.affine
4222   - #get affine from project
4223   - else:
4224   - from invesalius.project import Project
4225   - affine = Project().affine
4226   -
4227 4213 point = [float(self.goto_sagital.GetValue()),
4228 4214 float(self.goto_coronal.GetValue()),
4229 4215 float(self.goto_axial.GetValue())]
4230 4216  
4231 4217 # transformation from scanner coordinates to inv coord system
4232   - affine = inv(affine)
4233   - self.result = np.dot(affine[:3, :3], np.transpose(point[0:3])) + affine[:3, 3]
  4218 + affine_inverse = np.linalg.inv(slc.Slice().affine)
  4219 + self.result = np.dot(affine_inverse[:3, :3], np.transpose(point[0:3])) + affine_inverse[:3, 3]
4234 4220 self.result[1] = slc.Slice().GetMaxSliceNumber(const.CORONAL_STR) - self.result[1]
4235 4221  
4236 4222 Publisher.sendMessage('Update status text in GUI', label=_("Calculating the transformation ..."))
... ...
invesalius/gui/frame.py
... ... @@ -892,7 +892,7 @@ class MenuBar(wx.MenuBar):
892 892 sub(self.OnEnableState, "Enable state project")
893 893 sub(self.OnEnableUndo, "Enable undo")
894 894 sub(self.OnEnableRedo, "Enable redo")
895   - sub(self.OnEnableGotoCoord, "Update affine matrix")
  895 + sub(self.OnEnableGotoCoord, "Enable Go-to-Coord")
896 896 sub(self.OnEnableNavigation, "Navigation status")
897 897  
898 898 sub(self.OnAddMask, "Add mask")
... ... @@ -1242,15 +1242,13 @@ class MenuBar(wx.MenuBar):
1242 1242 else:
1243 1243 self.FindItemById(wx.ID_REDO).Enable(False)
1244 1244  
1245   - def OnEnableGotoCoord(self, affine):
  1245 + def OnEnableGotoCoord(self, status=True):
1246 1246 """
1247   - Disable goto coord either if there is no affine matrix or affine is wrongly imported.
1248   - :param status: Affine matrix status
  1247 + Enable or disable goto coord depending on the imported affine matrix.
  1248 + :param status: True for enabling and False for disabling the Go-To-Coord
1249 1249 """
1250   - if affine is not None:
1251   - self.FindItemById(const.ID_GOTO_COORD).Enable(True)
1252   - else:
1253   - self.FindItemById(const.ID_GOTO_COORD).Enable(False)
  1250 +
  1251 + self.FindItemById(const.ID_GOTO_COORD).Enable(status)
1254 1252  
1255 1253 def OnEnableNavigation(self, nav_status, vis_status):
1256 1254 """
... ...
invesalius/project.py
... ... @@ -329,8 +329,6 @@ class Project(metaclass=Singleton):
329 329  
330 330 if project.get("affine", ""):
331 331 self.affine = project["affine"]
332   - Publisher.sendMessage('Update affine matrix',
333   - affine=np.asarray(self.affine).reshape(4, 4))
334 332  
335 333 # Opening the masks
336 334 self.mask_dict = TwoWaysDictionary()
... ...