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 | 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() | ... | ... |