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,8 +122,6 @@ class Controller():
122 122
123 Publisher.subscribe(self.OnSaveProject, 'Save project') 123 Publisher.subscribe(self.OnSaveProject, 'Save project')
124 124
125 - Publisher.subscribe(self.Send_affine, 'Get affine matrix')  
126 -  
127 Publisher.subscribe(self.create_project_from_matrix, 'Create project from matrix') 125 Publisher.subscribe(self.create_project_from_matrix, 'Create project from matrix')
128 126
129 Publisher.subscribe(self.show_mask_preview, 'Show mask preview') 127 Publisher.subscribe(self.show_mask_preview, 'Show mask preview')
@@ -506,8 +504,9 @@ class Controller(): @@ -506,8 +504,9 @@ class Controller():
506 utils.debug("No medical images found on given directory") 504 utils.debug("No medical images found on given directory")
507 return 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 # OPTION 4: Nothing... 510 # OPTION 4: Nothing...
512 511
513 self.LoadProject() 512 self.LoadProject()
@@ -623,6 +622,11 @@ class Controller(): @@ -623,6 +622,11 @@ class Controller():
623 Publisher.sendMessage(('Set scroll position', 'AXIAL'), index=proj.matrix_shape[0]/2) 622 Publisher.sendMessage(('Set scroll position', 'AXIAL'), index=proj.matrix_shape[0]/2)
624 Publisher.sendMessage(('Set scroll position', 'SAGITAL'),index=proj.matrix_shape[1]/2) 623 Publisher.sendMessage(('Set scroll position', 'SAGITAL'),index=proj.matrix_shape[1]/2)
625 Publisher.sendMessage(('Set scroll position', 'CORONAL'),index=proj.matrix_shape[2]/2) 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 Publisher.sendMessage('End busy cursor') 631 Publisher.sendMessage('End busy cursor')
628 632
@@ -715,9 +719,6 @@ class Controller(): @@ -715,9 +719,6 @@ class Controller():
715 proj.matrix_dtype = matrix.dtype.name 719 proj.matrix_dtype = matrix.dtype.name
716 proj.matrix_filename = matrix_filename 720 proj.matrix_filename = matrix_filename
717 721
718 - if self.affine is not None:  
719 - proj.affine = self.affine.tolist()  
720 -  
721 # Orientation must be CORONAL in order to as_closes_canonical and 722 # Orientation must be CORONAL in order to as_closes_canonical and
722 # swap axis in img2memmap to work in a standardized way. 723 # swap axis in img2memmap to work in a standardized way.
723 # TODO: Create standard import image for all acquisition orientations 724 # TODO: Create standard import image for all acquisition orientations
@@ -730,6 +731,8 @@ class Controller(): @@ -730,6 +731,8 @@ class Controller():
730 proj.level = self.Slice.window_level 731 proj.level = self.Slice.window_level
731 proj.threshold_range = int(matrix.min()), int(matrix.max()) 732 proj.threshold_range = int(matrix.min()), int(matrix.max())
732 proj.spacing = self.Slice.spacing 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 session = ses.Session() 738 session = ses.Session()
@@ -915,16 +918,7 @@ class Controller(): @@ -915,16 +918,7 @@ class Controller():
915 matrix, matrix_filename = self.OpenOtherFiles(group) 918 matrix, matrix_filename = self.OpenOtherFiles(group)
916 self.CreateOtherProject(name, matrix, matrix_filename) 919 self.CreateOtherProject(name, matrix, matrix_filename)
917 self.LoadProject() 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 Publisher.sendMessage("Enable state project", state=True) 922 Publisher.sendMessage("Enable state project", state=True)
929 else: 923 else:
930 dialog.ImportInvalidFiles(ftype="Others") 924 dialog.ImportInvalidFiles(ftype="Others")
@@ -1034,13 +1028,7 @@ class Controller(): @@ -1034,13 +1028,7 @@ class Controller():
1034 self.matrix, scalar_range, self.filename = image_utils.img2memmap(group) 1028 self.matrix, scalar_range, self.filename = image_utils.img2memmap(group)
1035 1029
1036 hdr = group.header 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 hdr.set_data_dtype('int16') 1031 hdr.set_data_dtype('int16')
1042 - dims = hdr.get_zooms()  
1043 - dimsf = tuple([float(s) for s in dims])  
1044 1032
1045 wl = float((scalar_range[0] + scalar_range[1]) * 0.5) 1033 wl = float((scalar_range[0] + scalar_range[1]) * 0.5)
1046 ww = float((scalar_range[1] - scalar_range[0])) 1034 ww = float((scalar_range[1] - scalar_range[0]))
@@ -1048,19 +1036,29 @@ class Controller(): @@ -1048,19 +1036,29 @@ class Controller():
1048 self.Slice = sl.Slice() 1036 self.Slice = sl.Slice()
1049 self.Slice.matrix = self.matrix 1037 self.Slice.matrix = self.matrix
1050 self.Slice.matrix_filename = self.filename 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 self.Slice.window_level = wl 1045 self.Slice.window_level = wl
1054 self.Slice.window_width = ww 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 scalar_range = int(scalar_range[0]), int(scalar_range[1]) 1056 scalar_range = int(scalar_range[0]), int(scalar_range[1])
  1057 +
1057 Publisher.sendMessage('Update threshold limits list', 1058 Publisher.sendMessage('Update threshold limits list',
1058 threshold_range=scalar_range) 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 def LoadImagedataInfo(self): 1063 def LoadImagedataInfo(self):
1066 proj = prj.Project() 1064 proj = prj.Project()
invesalius/data/surface.py
@@ -51,6 +51,7 @@ else: @@ -51,6 +51,7 @@ else:
51 51
52 import invesalius.constants as const 52 import invesalius.constants as const
53 import invesalius.data.imagedata_utils as iu 53 import invesalius.data.imagedata_utils as iu
  54 +import invesalius.data.slice_ as sl
54 import invesalius.data.polydata_utils as pu 55 import invesalius.data.polydata_utils as pu
55 import invesalius.project as prj 56 import invesalius.project as prj
56 import invesalius.session as ses 57 import invesalius.session as ses
@@ -159,7 +160,6 @@ class SurfaceManager(): @@ -159,7 +160,6 @@ class SurfaceManager():
159 def __init__(self): 160 def __init__(self):
160 self.actors_dict = {} 161 self.actors_dict = {}
161 self.last_surface_index = 0 162 self.last_surface_index = 0
162 - self.affine_vtk = None  
163 self.convert2inv = None 163 self.convert2inv = None
164 self.__bind_events() 164 self.__bind_events()
165 165
@@ -207,7 +207,6 @@ class SurfaceManager(): @@ -207,7 +207,6 @@ class SurfaceManager():
207 207
208 Publisher.subscribe(self.OnImportSurfaceFile, 'Import surface file') 208 Publisher.subscribe(self.OnImportSurfaceFile, 'Import surface file')
209 209
210 - Publisher.subscribe(self.UpdateAffineMatrix, 'Update affine matrix')  
211 Publisher.subscribe(self.UpdateConvert2InvFlag, 'Update convert2inv flag') 210 Publisher.subscribe(self.UpdateConvert2InvFlag, 'Update convert2inv flag')
212 211
213 Publisher.subscribe(self.CreateSurfaceFromPolydata, 'Create surface from polydata') 212 Publisher.subscribe(self.CreateSurfaceFromPolydata, 'Create surface from polydata')
@@ -338,16 +337,6 @@ class SurfaceManager(): @@ -338,16 +337,6 @@ class SurfaceManager():
338 name = os.path.splitext(os.path.split(filename)[-1])[0] 337 name = os.path.splitext(os.path.split(filename)[-1])[0]
339 self.CreateSurfaceFromPolydata(polydata, name=name, scalar=scalar) 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 def UpdateConvert2InvFlag(self, convert2inv=False): 340 def UpdateConvert2InvFlag(self, convert2inv=False):
352 self.convert2inv = convert2inv 341 self.convert2inv = convert2inv
353 342
@@ -373,12 +362,17 @@ class SurfaceManager(): @@ -373,12 +362,17 @@ class SurfaceManager():
373 actor.SetMapper(mapper) 362 actor.SetMapper(mapper)
374 actor.GetProperty().SetBackfaceCulling(1) 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 if overwrite: 373 if overwrite:
380 if index is None: 374 if index is None:
381 - index = self.last_surface_index 375 + index = self.last_surface_index
382 surface = Surface(index=index) 376 surface = Surface(index=index)
383 else: 377 else:
384 surface = Surface() 378 surface = Surface()
invesalius/gui/dialogs.py
@@ -4200,37 +4200,23 @@ class GoToDialogScannerCoord(wx.Dialog): @@ -4200,37 +4200,23 @@ class GoToDialogScannerCoord(wx.Dialog):
4200 self.__bind_events() 4200 self.__bind_events()
4201 4201
4202 btn_ok.Bind(wx.EVT_BUTTON, self.OnOk) 4202 btn_ok.Bind(wx.EVT_BUTTON, self.OnOk)
4203 - Publisher.sendMessage('Get affine matrix')  
4204 4203
4205 def __bind_events(self): 4204 def __bind_events(self):
4206 Publisher.subscribe(self.SetNewFocalPoint, 'Cross focal point') 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 def SetNewFocalPoint(self, coord, spacing): 4207 def SetNewFocalPoint(self, coord, spacing):
4213 Publisher.sendMessage('Update cross pos', coord=self.result*spacing) 4208 Publisher.sendMessage('Update cross pos', coord=self.result*spacing)
4214 4209
4215 def OnOk(self, evt): 4210 def OnOk(self, evt):
4216 - from numpy.linalg import inv  
4217 import invesalius.data.slice_ as slc 4211 import invesalius.data.slice_ as slc
4218 try: 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 point = [float(self.goto_sagital.GetValue()), 4213 point = [float(self.goto_sagital.GetValue()),
4228 float(self.goto_coronal.GetValue()), 4214 float(self.goto_coronal.GetValue()),
4229 float(self.goto_axial.GetValue())] 4215 float(self.goto_axial.GetValue())]
4230 4216
4231 # transformation from scanner coordinates to inv coord system 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 self.result[1] = slc.Slice().GetMaxSliceNumber(const.CORONAL_STR) - self.result[1] 4220 self.result[1] = slc.Slice().GetMaxSliceNumber(const.CORONAL_STR) - self.result[1]
4235 4221
4236 Publisher.sendMessage('Update status text in GUI', label=_("Calculating the transformation ...")) 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,7 +892,7 @@ class MenuBar(wx.MenuBar):
892 sub(self.OnEnableState, "Enable state project") 892 sub(self.OnEnableState, "Enable state project")
893 sub(self.OnEnableUndo, "Enable undo") 893 sub(self.OnEnableUndo, "Enable undo")
894 sub(self.OnEnableRedo, "Enable redo") 894 sub(self.OnEnableRedo, "Enable redo")
895 - sub(self.OnEnableGotoCoord, "Update affine matrix") 895 + sub(self.OnEnableGotoCoord, "Enable Go-to-Coord")
896 sub(self.OnEnableNavigation, "Navigation status") 896 sub(self.OnEnableNavigation, "Navigation status")
897 897
898 sub(self.OnAddMask, "Add mask") 898 sub(self.OnAddMask, "Add mask")
@@ -1242,15 +1242,13 @@ class MenuBar(wx.MenuBar): @@ -1242,15 +1242,13 @@ class MenuBar(wx.MenuBar):
1242 else: 1242 else:
1243 self.FindItemById(wx.ID_REDO).Enable(False) 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 def OnEnableNavigation(self, nav_status, vis_status): 1253 def OnEnableNavigation(self, nav_status, vis_status):
1256 """ 1254 """
invesalius/project.py
@@ -329,8 +329,6 @@ class Project(metaclass=Singleton): @@ -329,8 +329,6 @@ class Project(metaclass=Singleton):
329 329
330 if project.get("affine", ""): 330 if project.get("affine", ""):
331 self.affine = project["affine"] 331 self.affine = project["affine"]
332 - Publisher.sendMessage('Update affine matrix',  
333 - affine=np.asarray(self.affine).reshape(4, 4))  
334 332
335 # Opening the masks 333 # Opening the masks
336 self.mask_dict = TwoWaysDictionary() 334 self.mask_dict = TwoWaysDictionary()