Commit 615280da8db5b1488db76d00450f29f21186cc38
Committed by
GitHub
1 parent
1239b41c
Exists in
master
FIX: Save affine in project and refactor instances of affine creation in OtherProjects (#386)
Showing
5 changed files
with
44 additions
and
70 deletions
Show diff stats
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() |