Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to scale sample image #26

Merged
merged 14 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions brainglobe_registration/registration_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
from bg_atlasapi import BrainGlobeAtlas
from bg_atlasapi.list_atlases import get_downloaded_atlases
from brainglobe_utils.qtpy.collapsible_widget import CollapsibleWidgetContainer
from napari.utils.notifications import show_error
from napari.viewer import Viewer
from qtpy.QtWidgets import (
QPushButton,
QTabWidget,
)
from skimage.segmentation import find_boundaries
from skimage.transform import rescale

from brainglobe_registration.elastix.register import run_registration
from brainglobe_registration.utils.brainglobe_logo import header_widget
Expand Down Expand Up @@ -49,6 +51,7 @@ def __init__(self, napari_viewer: Viewer):
self._viewer = napari_viewer
self._atlas: BrainGlobeAtlas = None
self._moving_image = None
self._moving_image_data_backup = None

self.transform_params: dict[str, dict] = {
"rigid": {},
Expand Down Expand Up @@ -101,10 +104,12 @@ def __init__(self, napari_viewer: Viewer):
self.adjust_moving_image_widget.adjust_image_signal.connect(
self._on_adjust_moving_image
)

self.adjust_moving_image_widget.reset_image_signal.connect(
self._on_adjust_moving_image_reset_button_click
)
self.adjust_moving_image_widget.scale_image_signal.connect(
self._on_scale_moving_image
)

self.transform_select_view = TransformSelectView()
self.transform_select_view.transform_type_added_signal.connect(
Expand Down Expand Up @@ -151,11 +156,11 @@ def _on_atlas_dropdown_index_changed(self, index):
# Hacky way of having an empty first dropdown
if index == 0:
if self._atlas:
curr_atlas_layer_index = find_layer_index(
current_atlas_layer_index = find_layer_index(
self._viewer, self._atlas.atlas_name
)

self._viewer.layers.pop(curr_atlas_layer_index)
self._viewer.layers.pop(current_atlas_layer_index)
self._atlas = None
self.run_button.setEnabled(False)
self._viewer.grid.enabled = False
Expand All @@ -166,11 +171,11 @@ def _on_atlas_dropdown_index_changed(self, index):
atlas = BrainGlobeAtlas(atlas_name)

if self._atlas:
curr_atlas_layer_index = find_layer_index(
current_atlas_layer_index = find_layer_index(
self._viewer, self._atlas.atlas_name
)

self._viewer.layers.pop(curr_atlas_layer_index)
self._viewer.layers.pop(current_atlas_layer_index)
else:
self.run_button.setEnabled(True)

Expand All @@ -189,6 +194,7 @@ def _on_sample_dropdown_index_changed(self, index):
self._viewer, self._sample_images[index]
)
self._moving_image = self._viewer.layers[viewer_index]
self._moving_image_data_backup = self._moving_image.data.copy()

def _on_adjust_moving_image(self, x: int, y: int, rotate: float):
adjust_napari_image_layer(self._moving_image, x, y, rotate)
Expand Down Expand Up @@ -299,3 +305,41 @@ def _on_default_file_selection_change(
def _on_sample_popup_about_to_show(self):
self._sample_images = get_image_layer_names(self._viewer)
self.get_atlas_widget.update_sample_image_names(self._sample_images)

def _on_scale_moving_image(self, x: float, y: float):
IgorTatarnikov marked this conversation as resolved.
Show resolved Hide resolved
"""
Scale the moving image to have resolution equal to the atlas.

Parameters
----------
x : float
Moving image x pixel size (> 0.0).
y : float
Moving image y pixel size (> 0.0).

Will show an error if the pixel sizes are less than or equal to 0.
Will show an error if the moving image or atlas is not selected.
"""
if x <= 0 or y <= 0:
show_error("Pixel sizes must be greater than 0")
return

if self._moving_image and self._atlas:
if self._moving_image_data_backup is None:
self._moving_image_data_backup = self._moving_image.data.copy()

x_factor = x / self._atlas.resolution[0]
y_factor = y / self._atlas.resolution[1]

self._moving_image.data = rescale(
self._moving_image_data_backup,
(y_factor, x_factor),
mode="constant",
preserve_range=True,
anti_aliasing=True,
)
else:
show_error(
"Sample image or atlas not selected. "
"Please select a sample image and atlas before scaling",
)
53 changes: 44 additions & 9 deletions brainglobe_registration/widgets/adjust_moving_image_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class AdjustMovingImageView(QWidget):
A QWidget subclass that provides controls for adjusting the moving image.

This widget provides controls for adjusting the x and y offsets and
rotation of the moving image. It emits signals when the image is adjusted
or reset.
rotation of the moving image. It emits signals when the image is adjusted,
scaled, or reset.

Attributes
----------
Expand All @@ -33,9 +33,12 @@ class AdjustMovingImageView(QWidget):
_on_reset_image_button_click():
Resets the x and y offsets and rotation to 0 and emits the
reset_image_signal.
_on_scale_image_button_click():
Emits the scale_image_signal with the entered pixel sizes.
"""

adjust_image_signal = Signal(int, int, float)
scale_image_signal = Signal(float, float)
reset_image_signal = Signal()

def __init__(self, parent=None):
Expand All @@ -54,15 +57,27 @@ def __init__(self, parent=None):
offset_range = 2000
rotation_range = 360

self.adjust_moving_image_x = QSpinBox()
self.adjust_moving_image_voxel_size_x = QDoubleSpinBox(parent=self)
self.adjust_moving_image_voxel_size_x.setDecimals(2)
self.adjust_moving_image_voxel_size_x.setRange(0.01, 100.00)
self.adjust_moving_image_voxel_size_y = QDoubleSpinBox(parent=self)
self.adjust_moving_image_voxel_size_y.setDecimals(2)
self.adjust_moving_image_voxel_size_y.setRange(0.01, 100.00)
self.scale_moving_image_button = QPushButton()
self.scale_moving_image_button.setText("Scale Image")
self.scale_moving_image_button.clicked.connect(
self._on_scale_image_button_click
)

self.adjust_moving_image_x = QSpinBox(parent=self)
self.adjust_moving_image_x.setRange(-offset_range, offset_range)
self.adjust_moving_image_x.valueChanged.connect(self._on_adjust_image)

self.adjust_moving_image_y = QSpinBox()
self.adjust_moving_image_y = QSpinBox(parent=self)
self.adjust_moving_image_y.setRange(-offset_range, offset_range)
self.adjust_moving_image_y.valueChanged.connect(self._on_adjust_image)

self.adjust_moving_image_rotate = QDoubleSpinBox()
self.adjust_moving_image_rotate = QDoubleSpinBox(parent=self)
self.adjust_moving_image_rotate.setRange(
-rotation_range, rotation_range
)
Expand All @@ -71,15 +86,26 @@ def __init__(self, parent=None):
self._on_adjust_image
)

self.adjust_moving_image_reset_button = QPushButton(parent=self)
self.adjust_moving_image_reset_button = QPushButton()
self.adjust_moving_image_reset_button.setText("Reset Image")
self.adjust_moving_image_reset_button.clicked.connect(
self._on_reset_image_button_click
)

self.layout().addRow(QLabel("Adjust the moving image: "))
self.layout().addRow("X offset:", self.adjust_moving_image_x)
self.layout().addRow("Y offset:", self.adjust_moving_image_y)
self.layout().addRow(QLabel("Adjust the moving image scale:"))
self.layout().addRow(
"Sample image X pixel size (\u03BCm / pixel):",
self.adjust_moving_image_voxel_size_x,
)
self.layout().addRow(
"Sample image Y pixel size (\u03BCm / pixel):",
self.adjust_moving_image_voxel_size_y,
)
self.layout().addRow(self.scale_moving_image_button)

self.layout().addRow(QLabel("Adjust the sample image position:"))
self.layout().addRow("X offset (pixels):", self.adjust_moving_image_x)
self.layout().addRow("Y offset (pixels):", self.adjust_moving_image_y)
self.layout().addRow(
"Rotation (degrees):", self.adjust_moving_image_rotate
)
Expand All @@ -106,3 +132,12 @@ def _on_reset_image_button_click(self):
self.adjust_moving_image_rotate.setValue(0)

self.reset_image_signal.emit()

def _on_scale_image_button_click(self):
"""
Emit the scale_image_signal with the entered pixel sizes.
"""
self.scale_image_signal.emit(
self.adjust_moving_image_voxel_size_x.value(),
self.adjust_moving_image_voxel_size_y.value(),
)
12 changes: 7 additions & 5 deletions brainglobe_registration/widgets/transform_select_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def _on_transform_type_change(self, index):
self.file_selections[index].setCurrentIndex(0)

if index >= len(self.transform_type_selections) - 1:
curr_length = self.rowCount()
current_length = self.rowCount()
self.setRowCount(self.rowCount() + 1)

self.transform_type_selections.append(QComboBox())
Expand All @@ -180,7 +180,7 @@ def _on_transform_type_change(self, index):
self.transform_type_signaller.map
)
self.transform_type_signaller.setMapping(
self.transform_type_selections[-1], curr_length
self.transform_type_selections[-1], current_length
)

self.file_selections.append(QComboBox())
Expand All @@ -191,14 +191,16 @@ def _on_transform_type_change(self, index):
self.file_signaller.map
)
self.file_signaller.setMapping(
self.file_selections[-1], curr_length
self.file_selections[-1], current_length
)

self.setCellWidget(
curr_length, 0, self.transform_type_selections[curr_length]
current_length,
0,
self.transform_type_selections[current_length],
)
self.setCellWidget(
curr_length, 1, self.file_selections[curr_length]
current_length, 1, self.file_selections[current_length]
)

else:
Expand Down
29 changes: 29 additions & 0 deletions tests/test_adjust_moving_image_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ def adjust_moving_image_view() -> AdjustMovingImageView:
return adjust_moving_image_view


def test_init(qtbot, adjust_moving_image_view):
qtbot.addWidget(adjust_moving_image_view)

assert adjust_moving_image_view.layout().rowCount() == 9


@pytest.mark.parametrize(
"x_value, expected",
[
Expand Down Expand Up @@ -94,3 +100,26 @@ def test_reset_image_button_click(qtbot, adjust_moving_image_view):
assert adjust_moving_image_view.adjust_moving_image_x.value() == 0
assert adjust_moving_image_view.adjust_moving_image_y.value() == 0
assert adjust_moving_image_view.adjust_moving_image_rotate.value() == 0


@pytest.mark.parametrize(
"x_scale, y_scale",
[(2.5, 2.5), (10, 20), (10.221, 10.228)],
)
def test_scale_image_button_click(
qtbot, adjust_moving_image_view, x_scale, y_scale
):
qtbot.addWidget(adjust_moving_image_view)

with qtbot.waitSignal(
adjust_moving_image_view.scale_image_signal, timeout=1000
) as blocker:
adjust_moving_image_view.adjust_moving_image_voxel_size_x.setValue(
x_scale
)
adjust_moving_image_view.adjust_moving_image_voxel_size_y.setValue(
y_scale
)
adjust_moving_image_view.scale_moving_image_button.click()

assert blocker.args == [round(x_scale, 2), round(y_scale, 2)]
12 changes: 6 additions & 6 deletions tests/test_parameter_list_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def test_parameter_list_view_cell_change(parameter_list_view, qtbot):
def test_parameter_list_view_cell_change_last_row(parameter_list_view, qtbot):
qtbot.addWidget(parameter_list_view)

curr_row_count = parameter_list_view.rowCount()
current_row_count = parameter_list_view.rowCount()
last_row_index = len(param_dict)

parameter_list_view.setItem(
Expand All @@ -65,32 +65,32 @@ def test_parameter_list_view_cell_change_last_row(parameter_list_view, qtbot):
parameter_list_view.setItem(last_row_index, 1, QTableWidgetItem("true"))

assert parameter_list_view.param_dict["TestParameter"] == ["true"]
assert parameter_list_view.rowCount() == curr_row_count + 1
assert parameter_list_view.rowCount() == current_row_count + 1


def test_parameter_list_view_cell_change_last_row_no_param(
parameter_list_view, qtbot
):
qtbot.addWidget(parameter_list_view)

curr_row_count = parameter_list_view.rowCount()
current_row_count = parameter_list_view.rowCount()
last_row_index = len(param_dict)

parameter_list_view.setItem(last_row_index, 1, QTableWidgetItem("true"))

assert parameter_list_view.rowCount() == curr_row_count
assert parameter_list_view.rowCount() == current_row_count


def test_parameter_list_view_cell_change_last_row_no_value(
parameter_list_view, qtbot
):
qtbot.addWidget(parameter_list_view)

curr_row_count = parameter_list_view.rowCount()
current_row_count = parameter_list_view.rowCount()
last_row_index = len(param_dict)

parameter_list_view.setItem(
last_row_index, 0, QTableWidgetItem("TestParameter")
)

assert parameter_list_view.rowCount() == curr_row_count
assert parameter_list_view.rowCount() == current_row_count
Loading
Loading