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

Make feature maps work for all layers #80

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
78 changes: 78 additions & 0 deletions napari_skimage_regionprops/_parametric_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,84 @@
import numpy
from deprecated import deprecated


def create_feature_map(layer: "napari.layers.Layer",
selected_column: str,
colormap: str = 'jet'
) -> "napari.layers.Layer":
"""
Create feature map from layer and column name.

Parameters
----------
layer : "napari.layers.Layer"
Layer to create feature map from.
column_name : str
Column name to create feature map from.

Returns
-------
"napari.layers.Layer"
Feature map.
"""
# Label layers
from napari.layers import Layer, Labels, Points, Vectors, Surface
properties = {}
if isinstance(layer, Labels):
if "label" not in layer.properties.keys():
raise ValueError("Layer does not have a 'label' property.")
if selected_column is None:
return None

print("Selected column", selected_column)

data = map_measurements_on_labels(
layer, selected_column)

properties['contrast_limits'] = [np.min(layer.features[selected_column]),
np.max(layer.features[selected_column])]
properties['colormap'] = colormap
properties['interpolation'] = 'nearest'
layertype = 'image'

elif isinstance(layer, Points):
data = layer.data
properties['face_color'] = selected_column
properties['face_colormap'] = colormap
properties['features'] = {selected_column: layer.features[selected_column].values}
layertype = 'points'

elif isinstance(layer, Vectors):
data = layer.data
properties['features'] = {selected_column: layer.features[selected_column].values}
properties['edge_color'] = selected_column
properties['edge_colormap'] = colormap
layertype = 'vectors'

# Surface layer
elif isinstance(layer, Surface):
data = list(layer.data)

# We may have stored features in the metadata to avoid napari complaining
if not hasattr(layer, "features") and 'features' not in layer.metadata.keys():
raise ValueError("Layer does not have a 'features' property.")

if not hasattr(layer, "features") and "features" in layer.metadata.keys():
layer.features = layer.metadata["features"]
layer.metadata.pop("features")

data[2] = np.asarray(layer.features[selected_column].values)

properties['colormap'] = colormap
properties['contrast_limits'] = [np.min(layer.features[selected_column]),
np.max(layer.features[selected_column])]
if "annotation" in selected_column or "CLUSTER_ID" in selected_column:
properties.colormap = "hsv"
layertype = 'surface'

return Layer.create(data, properties, layertype)


@register_function(menu="Measurement maps > Measurements on labels (nsr)")
@register_function(menu="Visualization > Measurements on labels (nsr)")
def map_measurements_on_labels(labels_layer:"napari.layers.Labels", column:str = "label", viewer:"napari.Viewer" = None) -> "napari.types.ImageData":
Expand Down
65 changes: 19 additions & 46 deletions napari_skimage_regionprops/_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ def __init__(self, layer: "napari.layers.Layer", viewer: "napari.Viewer" = None)

self._view = QTableWidget()
self._view.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)

if hasattr(layer, "properties"):
self.set_content(layer.properties)
else:
self.set_content({})
content = layer.properties
elif hasattr(layer, "features"):
content = layer.features.to_dict('list')

# to accomodate passing features to surfaces through the metadata
elif 'features' in layer.metadata.keys():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give an example (plugin) where there is data in layer.metadata['features'] but not in layer.features? Might it be possible to talk to the developers of these plugins so that they use layer.features? It would make more plugjns compatible to each other if we all use the same API 😉

layer.features = layer.metadata['features']
content = layer.features.to_dict('list')
self.set_content(content)
jo-mueller marked this conversation as resolved.
Show resolved Hide resolved

self._view.clicked.connect(self._clicked_table)
self._view.horizontalHeader().sectionDoubleClicked.connect(self._double_clicked_table)
Expand Down Expand Up @@ -71,49 +78,15 @@ def _clicked_table(self):
self._viewer.dims.current_step = current_step

def _double_clicked_table(self):
if "label" in self._table.keys():
selected_column = list(self._table.keys())[self._view.currentColumn()]
print("Selected column", selected_column)
if selected_column is not None:
if isinstance(self._layer, napari.layers.Labels):
from ._parametric_images import visualize_measurement_on_labels, map_measurements_on_labels
new_layer = self._viewer.add_image( map_measurements_on_labels(self._layer, selected_column, self._viewer) ,
name=selected_column + " in " + self._layer.name ,
affine=self._layer.affine ,
scale=self._layer.scale,
rotate=self._layer.rotate
)
new_layer.contrast_limits = [np.min(self._table[selected_column]), np.max(self._table[selected_column])]
new_layer.colormap = "jet"
elif isinstance(self._layer, napari.layers.Points):
features = self._layer.features
new_layer = self._viewer.add_points(self._layer.data,
features=features,
face_color=selected_column,
face_colormap="jet",
size=self._layer.size,
name=selected_column + " in " + self._layer.name,
affine=self._layer.affine,
scale=self._layer.scale,
rotate=self._layer.rotate
)
new_layer.contrast_limits = [np.min(self._table[selected_column]), np.max(self._table[selected_column])]

if "vertex_index" in self._table.keys():
selected_column = list(self._table.keys())[self._view.currentColumn()]
print("Selected column (T)", selected_column)
if selected_column is not None and isinstance(self._layer, napari.layers.Surface):
values = np.asarray(self._table[selected_column])
data = self._layer.data
data = [np.asarray(data[0]).copy(), np.asarray(data[1]).copy(), values]

new_layer = self._viewer.add_surface(data,
name=selected_column + " in " + self._layer.name)
new_layer.contrast_limits = [np.min(self._table[selected_column]), np.max(self._table[selected_column])]
if "annotation" in selected_column or "CLUSTER_ID" in selected_column:
new_layer.colormap = "hsv"
else:
new_layer.colormap = "jet"
Comment on lines -74 to -116
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure, you can remove this entire code? It seems that this breaks backwards compatibility. E.g. where is the new_layer.colormap going?

Copy link
Contributor Author

@jo-mueller jo-mueller Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the setting of the colormap into the new create_feature_map function. The colormaps for the respective different layers are set at the following locations:

  • Here for labels layers (which will be returned as ImageData)
  • Here for PointsData
  • Here for VectorsData
  • Here for SurfaceData

The reason for doing it this way is that the color displays for different layers are controlled by different attributes (e.g., colormap, face_colormap, edge_colormap)

"""
If table header is double clicked, create a feature map from the selected column.
"""
from ._parametric_images import create_feature_map
selected_column = list(self._table.keys())[self._view.currentColumn()]
print('Selected', selected_column)
layer = create_feature_map(self._layer, selected_column)
layer.name = selected_column + " in " + self._layer.name
self._viewer.add_layer(layer)

def _after_labels_clicked(self):
if "label" in self._table.keys() and hasattr(self._layer, "selected_label"):
Expand Down
83 changes: 83 additions & 0 deletions napari_skimage_regionprops/_tests/test_featuremaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import numpy as np


def test_label_featuremaps(make_napari_viewer):
from napari_skimage_regionprops import regionprops
from .._parametric_images import create_feature_map

viewer = make_napari_viewer()

image = np.asarray([
[0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 2, 2],
[1, 1, 1, 0, 0, 2, 2],
[1, 1, 1, 0, 0, 2, 2],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 0, 4],
]).astype(float)

image_layer = viewer.add_image(image)
labels_layer = viewer.add_labels(image.astype(int))

# analyze a few things
regionprops(image_layer, labels_layer, True, False, False, False, False, False, viewer)
feature_map = create_feature_map(viewer.layers[-1], 'area')

assert feature_map.data.max() == 9


def test_vector_featuremaps(make_napari_viewer):
from napari_skimage_regionprops import regionprops
from .._parametric_images import create_feature_map
import pandas as pd

viewer = make_napari_viewer()

np.random.seed(0)
vectors = np.random.random((10, 2, 3))
feature1 = np.random.random((10))
feature2 = np.random.random((10))
features = pd.DataFrame({'feature1': feature1, 'feature2': feature2})
layer = viewer.add_vectors(vectors, features=features)

feature_map = create_feature_map(layer, 'feature1')
viewer.add_layer(feature_map)


def test_points_featuremaps(make_napari_viewer):
from napari_skimage_regionprops import regionprops
from .._parametric_images import create_feature_map
import pandas as pd

viewer = make_napari_viewer()

np.random.seed(0)
points = np.random.random((10, 2))
feature1 = np.random.random((10))
feature2 = np.random.random((10))
features = pd.DataFrame({'feature1': feature1, 'feature2': feature2})
layer = viewer.add_points(points, features=features)

feature_map = create_feature_map(layer, 'feature1')
viewer.add_layer(feature_map)


def test_surface_featuremaps(make_napari_viewer):
from napari_skimage_regionprops import regionprops
from .._parametric_images import create_feature_map
import pandas as pd

viewer = make_napari_viewer()

np.random.seed(0)
vertices = np.random.random((10, 3))
faces = np.random.randint(size=(10, 3), high=9, low=0)
surface = (vertices, faces)
feature1 = np.random.random((10))
feature2 = np.random.random((10))
features = pd.DataFrame({'feature1': feature1, 'feature2': feature2})
layer = viewer.add_surface(surface, metadata={'features': features})

feature_map = create_feature_map(layer, 'feature1')
assert np.array_equal(feature_map.data[2], feature1)