Skip to content

Commit

Permalink
use morphometrics-engine for region props (morphometrics#55)
Browse files Browse the repository at this point in the history
* fix init

* fix widget import

* update setup conda version
  • Loading branch information
kevinyamauchi committed May 31, 2023
1 parent ca7135c commit 1505ee1
Show file tree
Hide file tree
Showing 8 changed files with 24 additions and 359 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test_and_deploy.yml
Expand Up @@ -53,7 +53,7 @@ jobs:
# and
# tox-conda: https://github.com/tox-dev/tox-conda
- name: Set up conda ${{ matrix.python-version }}
uses: conda-incubator/setup-miniconda@v2.0.0
uses: conda-incubator/setup-miniconda@v2
with:
auto-update-conda: true
activate-environment: test
Expand Down
4 changes: 2 additions & 2 deletions examples/measure_with_widget.py
Expand Up @@ -2,9 +2,9 @@
import numpy as np

from morphometrics.data import simple_labeled_cube
from morphometrics.measure import available_measurments
from morphometrics.measure import available_measurements

print(f"available_measurements: {available_measurments()}")
print(f"available_measurements: {available_measurements()}")

label_image = simple_labeled_cube()
intensity_image = np.random.random(label_image.shape)
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Expand Up @@ -32,8 +32,10 @@ install_requires =
glasbey
imageio>=2.5.0,!=2.11.0,!=2.22.1
leidenalg
morphometrics-engine
napari-skimage-regionprops
napari
napari-threedee
numba
numpy
qtpy
Expand Down
201 changes: 2 additions & 199 deletions src/morphometrics/_gui/_qt/measurement_widgets.py
@@ -1,199 +1,2 @@
import warnings
from typing import Any, Dict, List, Union

import napari
import numpy as np
from magicgui import magicgui
from napari_skimage_regionprops._table import add_table
from qtpy.QtWidgets import (
QCheckBox,
QHBoxLayout,
QLabel,
QPushButton,
QVBoxLayout,
QWidget,
)
from superqt.collapsible import QCollapsible

from morphometrics.measure import _measurements, measure_selected


class QtSingleMeasurement(QWidget):
def __init__(self, name: str):
super().__init__()
self._measurement_name = name
self._label_widget = QLabel(name)
self._check_box = QCheckBox()

self.setLayout(QHBoxLayout())
self.layout().addWidget(self._check_box)
self.layout().addWidget(self._label_widget)

self.layout().setSpacing(0)
self.layout().setContentsMargins(0,0,0,0)

@property
def measurement_name(self) -> str:
return self._measurement_name

@property
def include_measurement(self) -> bool:
return self._check_box.isChecked()


class QtMeasurementSet(QWidget):
def __init__(self, name: str, choices: List[str]):
super().__init__()
self._measurement_name = name

self._measurement_section = QCollapsible(
title=self._measurement_name, parent=self
)
self._choice_widgets = [QtSingleMeasurement(name=choice) for choice in choices]
for choice_widget in self._choice_widgets:
self._measurement_section.addWidget(choice_widget)

self.setLayout(QVBoxLayout())
self.layout().addWidget(self._measurement_section)

self.layout().setSpacing(0)
self.layout().setContentsMargins(0,0,0,0)

self._measurement_section.layout().layout().setSpacing(0)
self._measurement_section.layout().setContentsMargins(0,0,0,0)

@property
def measurement_name(self) -> str:
return self._measurement_name

@property
def include_measurement(self) -> bool:
return np.any([widget.include_measurement for widget in self._choice_widgets])

@property
def measurement_choices(self) -> Dict[str, bool]:
return {
widget.measurement_name: widget.include_measurement
for widget in self._choice_widgets
}


def create_measurement_widgets(
measurements: List[Dict[str, Any]]
) -> List[Union[QtSingleMeasurement, QtMeasurementSet]]:
widgets = []
for measurement_name, measurement_config in measurements.items():
if measurement_config["type"] == "single":
widgets.append(QtSingleMeasurement(name=measurement_name))
elif measurement_config["type"] == "set":
widgets.append(
QtMeasurementSet(
name=measurement_name, choices=measurement_config["choices"]
)
)
return widgets


class QtMeasurementWidget(QWidget):
def __init__(self, viewer: napari.Viewer):
super().__init__()
self._viewer = viewer

# create the layer selection widgets
self._layer_selection_widget = magicgui(
self._select_layers,
intensity_image={"choices": self._get_image_layers},
label_image={"choices": self._get_labels_layers},
auto_call=True,
call_button=False,
)
self._layer_selection_widget()

# create the measurement widgets
self.measurement_widgets = create_measurement_widgets(_measurements)

self._run_button = QPushButton("Run", self)
self._run_button.clicked.connect(self._run)

# add widgets to the layout
self.setLayout(QVBoxLayout())
self.layout().addWidget(self._layer_selection_widget.native)
for widget in self.measurement_widgets:
self.layout().addWidget(widget)
self.layout().addWidget(self._run_button)

@property
def measurement_selection(self) -> List[Union[str, Dict[str, Any]]]:
measurement_selection = []
for widget in self.measurement_widgets:
if not widget.include_measurement:
continue
if isinstance(widget, QtSingleMeasurement):
measurement_selection.append(widget.measurement_name)
elif isinstance(widget, QtMeasurementSet):
measurement_selection.append(
{
"name": widget.measurement_name,
"choices": widget.measurement_choices,
}
)
else:
raise TypeError(
"Widgets should be QtSingleMeasurement or QtMeasurementSet"
)

return measurement_selection

def _select_layers(
self, intensity_image: napari.layers.Image, label_image: napari.layers.Labels
):
self._intensity_image_layer = intensity_image
self._label_image_layer = label_image

def _get_image_layers(self, combo_widget):
return [
layer
for layer in self._viewer.layers
if isinstance(layer, napari.layers.Image)
]

def _get_labels_layers(self, combo_widget):
return [
layer
for layer in self._viewer.layers
if isinstance(layer, napari.layers.Labels)
]

def _run(self):
for widget in self.measurement_widgets:
print(f"{widget.measurement_name}: {widget.include_measurement}")

widget_is_set = isinstance(widget, QtMeasurementSet)
print(f"{widget.measurement_name} is set: {widget_is_set}")

labels = self._label_image_layer.data
image = self._intensity_image_layer.data

# deal with dimensionality of data
if len(image.shape) > len(labels.shape):
dim = 0
subset = ""
while len(image.shape) > len(labels.shape):
current_dim_value = self._viewer.dims.current_step[dim]
dim = dim + 1
image = image[current_dim_value]
subset = subset + ", " + str(current_dim_value)
warnings.warn(
"Not the full image was analysed, just the subset ["
+ subset[2:]
+ "] according to selected timepoint / slice."
)

measurement_table = measure_selected(
label_image=labels,
intensity_image=image,
measurement_selection=self.measurement_selection,
)

self._label_image_layer.properties = measurement_table.reset_index()
add_table(self._label_image_layer, self._viewer)
# backwards compatibility import
from morphometrics_engine._widget import QtMeasurementWidget # noqa
55 changes: 15 additions & 40 deletions src/morphometrics/measure/__init__.py
@@ -1,43 +1,18 @@
from typing import Callable, List, Optional

from toolz import curry

_measurements = dict()


@curry
def register_measurement(
func: Callable, name: Optional[str] = None, uses_intensity_image: bool = True
) -> Callable:
_measurements[name] = {
"type": "single",
"callable": func,
"choices": None,
"intensity_image": uses_intensity_image,
}
return func


@curry
def register_measurement_set(
func: Callable,
choices: List[str],
name: Optional[str] = None,
uses_intensity_image: bool = True,
) -> Callable:
_measurements[name] = {
"type": "set",
"callable": func,
"choices": choices,
"intensity_image": uses_intensity_image,
}
return func


def available_measurments() -> List[str]:
return [k for k in _measurements]

from morphometrics_engine import (
available_measurements,
measure_all_with_defaults,
measure_selected,
)

from .intensity import measure_boundary_intensity, measure_internal_intensity
from .label import measure_surface_properties_from_labels, regionprops
from .measure import measure_all_with_defaults, measure_selected

__all__ = [
"available_measurements",
"measure_selected",
"measure_all_with_defaults",
"measure_boundary_intensity",
"measure_internal_intensity",
"measure_surface_properties_from_labels",
"regionprops",
]
2 changes: 1 addition & 1 deletion src/morphometrics/measure/intensity.py
@@ -1,11 +1,11 @@
import numpy as np
import pandas as pd
from morphometrics_engine import register_measurement
from skimage.measure import regionprops_table

from ..types import IntensityImage, LabelImage, LabelMeasurementTable
from ..utils.image_utils import make_boundary_mask
from ..utils.math_utils import safe_divide
from . import register_measurement


@register_measurement(name="boundary_intensity", uses_intensity_image=True)
Expand Down
2 changes: 1 addition & 1 deletion src/morphometrics/measure/label.py
Expand Up @@ -3,12 +3,12 @@

import numpy as np
import pandas as pd
from morphometrics_engine import register_measurement, register_measurement_set
from skimage.measure import regionprops_table
from tqdm.autonotebook import tqdm

from ..types import IntensityImage, LabelImage, LabelMeasurementTable
from ..utils.surface_utils import binary_mask_to_surface
from . import register_measurement, register_measurement_set
from .surface import measure_surface_properties


Expand Down

0 comments on commit 1505ee1

Please sign in to comment.