diff --git a/src/navigate/controller/sub_controllers/acquire_bar.py b/src/navigate/controller/sub_controllers/acquire_bar.py index 5fa0f8c00..2d7c45348 100644 --- a/src/navigate/controller/sub_controllers/acquire_bar.py +++ b/src/navigate/controller/sub_controllers/acquire_bar.py @@ -161,9 +161,9 @@ def progress_bar( number_of_positions = 1 else: # The first row is a header that describes stage axis designation. - number_of_positions = len( - self.parent_controller.configuration["multi_positions"] - ) - 1 + number_of_positions = ( + len(self.parent_controller.configuration["multi_positions"]) - 1 + ) if mode == "single": number_of_slices = 1 @@ -540,9 +540,14 @@ def toggle_bdv_widgets(self, main_widget: str, dependent_widgets: list) -> None: """ state = self.acquire_pop.tab_frame.inputs[main_widget].get_variable().get() for widget in dependent_widgets: - self.acquire_pop.tab_frame.inputs[widget].widget.config( - state="readonly" if state else "disabled" - ) + if "dimension" in widget: + self.acquire_pop.tab_frame.inputs[widget].widget.config( + state="readonly" if state else "disabled" + ) + else: + self.acquire_pop.tab_frame.inputs[widget].widget.config( + state="normal" if state else "disabled" + ) def update_microscope_mode(self, *args: Iterable) -> None: """Gets the state of the pull-down menu and tells the central controller diff --git a/src/navigate/model/metadata_sources/bdv_metadata.py b/src/navigate/model/metadata_sources/bdv_metadata.py index b8cba543d..e4e1fefa7 100644 --- a/src/navigate/model/metadata_sources/bdv_metadata.py +++ b/src/navigate/model/metadata_sources/bdv_metadata.py @@ -58,16 +58,9 @@ class BigDataViewerMetadata(XMLMetadata): """ def __init__(self) -> None: - """Initialize the BigDataViewer metadata object. - - Parameters - ---------- - configuration : Optional[Dict[str, Any]] - Configuration dictionary. - """ + """Initialize the BigDataViewer metadata object.""" super().__init__() - # Affine Transform Parameters #: bool: Shear the data. self.shear_data = False @@ -80,7 +73,6 @@ def __init__(self) -> None: #: npt.NDArray: Shear transform matrix. self.shear_transform = np.eye(3, 4) - # Rotation Transform Parameters #: bool: Rotate the data. self.rotate_data = False @@ -194,9 +186,23 @@ def bdv_xml_dict( } # Calculate shear and rotation transforms + pixel_size = [self.dx, self.dy, self.dz] self.bdv_shear_transform() self.bdv_rotate_transform() + if self.shear_data: + shear_angle = np.deg2rad(self.shear_angle) + # Must scale the voxel size to account for shear + if self.shear_dimension == "YZ": + scaled_z = abs(self.dz * np.sin(shear_angle)) + pixel_size = [self.dx, self.dy, scaled_z] + elif self.shear_dimension == "XZ": + # TODO: Implement + pass + else: + # TODO: Implement + pass + # Populate ViewSetups bdv_dict["SequenceDescription"]["ViewSetups"] = {} bdv_dict["SequenceDescription"]["ViewSetups"]["ViewSetup"] = [] @@ -210,6 +216,7 @@ def bdv_xml_dict( {"name": "tile", "Tile": []}, {"name": "angle", "Angle": {"id": {"text": 0}, "name": {"text": 0}}}, ] + # The actual loop that populates ViewSetup view_id = 0 for c in range(self.shape_c): @@ -226,7 +233,9 @@ def bdv_xml_dict( "size": {"text": f"{self.shape_x} {self.shape_y} {self.shape_z}"}, "voxelSize": { "unit": {"text": "um"}, - "size": {"text": f"{self.dx} {self.dy} {self.dz}"}, + "size": { + "text": f"{pixel_size[0]} {pixel_size[1]} {pixel_size[2]}" + }, }, "attributes": { "illumination": {"text": "0"}, @@ -238,7 +247,8 @@ def bdv_xml_dict( bdv_dict["SequenceDescription"]["ViewSetups"]["ViewSetup"].append(d) view_id += 1 - # Finish up the Tile Attributes outside of the channels loop so we have + + # Finish up the Tile Attributes outside the channels loop so we have # one per tile for pos in range(self.positions): tile = {"id": {"text": str(pos)}, "name": {"text": str(pos)}} @@ -254,6 +264,7 @@ def bdv_xml_dict( } # View registrations + reference_position = np.eye(3, 4, dtype=float) bdv_dict["ViewRegistrations"] = {"ViewRegistration": []} for t in range(self.shape_t): for p in range(self.positions): @@ -282,6 +293,13 @@ def bdv_xml_dict( # an acquisition. pass + if t == 0 and p == 0 and c == 0: + # Save reference position to allow translation offsets for + # shear to be applied relative to this position. + reference_position = mat.ravel() + + current_position = mat.ravel() + view_transforms = [ { "type": "affine", @@ -308,6 +326,33 @@ def bdv_xml_dict( } ) + delta_z_position = current_position[-1] - reference_position[-1] + shear_offset = np.eye(3, 4, dtype=float).ravel() + if self.shear_dimension == "YZ": + shear_offset[7] = ( + delta_z_position + * self.dz + * np.tan(np.deg2rad(self.shear_angle)) + / self.dy + ) + + elif self.shear_dimension == "XZ": + # TODO: Implement + pass + else: + # TODO: Implement + pass + + view_transforms.append( + { + "type": "affine", + "Name": "Shear Offset Transform", + "affine": { + "text": " ".join([f"{x:.6f}" for x in shear_offset]) + }, + } + ) + if self.rotate_data: view_transforms.append( { @@ -328,9 +373,7 @@ def bdv_xml_dict( bdv_dict["ViewRegistrations"]["ViewRegistration"].append(d) - bdv_dict["Misc"] = { - "Entry": {"Key": "Note", "text": self.misc} - } + bdv_dict["Misc"] = {"Entry": {"Key": "Note", "text": self.misc}} return bdv_dict diff --git a/src/navigate/tools/linear_algebra.py b/src/navigate/tools/linear_algebra.py index 9283fbe63..db468215f 100644 --- a/src/navigate/tools/linear_algebra.py +++ b/src/navigate/tools/linear_algebra.py @@ -157,7 +157,7 @@ def affine_shear(dz, dy, dx, dimension="YZ", angle=0): return shear_transform scaled_angle = np.multiply( - np.cos(np.deg2rad(angle)), + np.tan(np.deg2rad(angle)), [dy / dx, dz / dx, dz / dy], ) diff --git a/test/model/metadata_sources/test_bdv_metadata.py b/test/model/metadata_sources/test_bdv_metadata.py index 77c771254..8a5da6360 100644 --- a/test/model/metadata_sources/test_bdv_metadata.py +++ b/test/model/metadata_sources/test_bdv_metadata.py @@ -55,7 +55,7 @@ def test_bdv_metadata(ext): md.shear_angle = 15 md.dx, md.dy, md.dz = 1, 1, 1 md.bdv_shear_transform() - assert md.shear_transform[0, 2] == np.cos(np.deg2rad(15)) + assert md.shear_transform[0, 2] == np.tan(np.deg2rad(15)) md.rotate_data = True md.rotate_angle_x = 15 diff --git a/test/tools/test_linear_algebra.py b/test/tools/test_linear_algebra.py index 2782c66d2..fd8fd78b7 100644 --- a/test/tools/test_linear_algebra.py +++ b/test/tools/test_linear_algebra.py @@ -82,18 +82,18 @@ def test_no_shear(self): def test_shear_xy(self): result = affine_shear(1, 1, 1, dimension="XY", angle=45) expected = np.eye(4, 4) - expected[0, 1] = 0.70710678 + expected[0, 1] = 1.0 np.testing.assert_array_almost_equal(result, expected) def test_different_pixe_sizes(self): result = affine_shear(167, 167, 333, dimension="XY", angle=45) expected = np.eye(4, 4) - expected[0, 1] = 0.35461511 + expected[0, 1] = 0.501502 np.testing.assert_array_almost_equal(result, expected) def test_shear_xz(self): # Test shear in XZ dimension result = affine_shear(167, 167, 333, dimension="XZ", angle=45) expected = np.eye(4, 4) - expected[0, 2] = 0.35461511 + expected[0, 2] = 0.501502 np.testing.assert_array_almost_equal(result, expected)