Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitdc1f3bb

Browse files
authored
Merge pull request#148 from jo-mueller/add-feature-histogram
added a Feature Histogram Widget
2 parents3a8261a +e1ccfb1 commitdc1f3bb

File tree

10 files changed

+231
-12
lines changed

10 files changed

+231
-12
lines changed

‎baseline/test_feature_histogram2.png

12.6 KB
Loading

‎docs/changelog.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
Changelog
22
=========
3-
1.0.3
3+
1.1.0
44
-----
5+
Additions
6+
~~~~~~~~~
7+
- Added a widget to draw a histogram of features.
8+
59
Changes
610
~~~~~~~
711
- The slice widget is now limited to slicing along the x/y dimensions. Support

‎docs/user_guide.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ These widgets plot the data stored in the ``.features`` attribute of individual
3030
Currently available are:
3131

3232
- 2D scatter plots of two features against each other.
33+
- Histograms of individual features.
3334

3435
To use these:
3536

‎src/napari_matplotlib/features.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fromnapari.layersimportLabels,Points,Shapes,Tracks,Vectors
2+
3+
FEATURES_LAYER_TYPES= (
4+
Labels,
5+
Points,
6+
Shapes,
7+
Tracks,
8+
Vectors,
9+
)

‎src/napari_matplotlib/histogram.py

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
fromtypingimportOptional
1+
fromtypingimportAny,List,Optional,Tuple
22

33
importnapari
44
importnumpyasnp
5-
fromqtpy.QtWidgetsimportQWidget
5+
importnumpy.typingasnpt
6+
fromqtpy.QtWidgetsimportQComboBox,QLabel,QVBoxLayout,QWidget
67

78
from .baseimportSingleAxesWidget
9+
from .featuresimportFEATURES_LAYER_TYPES
810
from .utilimportInterval
911

10-
__all__= ["HistogramWidget"]
12+
__all__= ["HistogramWidget","FeaturesHistogramWidget"]
1113

1214
_COLORS= {"r":"tab:red","g":"tab:green","b":"tab:blue"}
1315

@@ -61,3 +63,112 @@ def draw(self) -> None:
6163
self.axes.hist(data.ravel(),bins=bins,label=layer.name)
6264

6365
self.axes.legend()
66+
67+
68+
classFeaturesHistogramWidget(SingleAxesWidget):
69+
"""
70+
Display a histogram of selected feature attached to selected layer.
71+
"""
72+
73+
n_layers_input=Interval(1,1)
74+
# All layers that have a .features attributes
75+
input_layer_types=FEATURES_LAYER_TYPES
76+
77+
def__init__(
78+
self,
79+
napari_viewer:napari.viewer.Viewer,
80+
parent:Optional[QWidget]=None,
81+
):
82+
super().__init__(napari_viewer,parent=parent)
83+
84+
self.layout().addLayout(QVBoxLayout())
85+
self._key_selection_widget=QComboBox()
86+
self.layout().addWidget(QLabel("Key:"))
87+
self.layout().addWidget(self._key_selection_widget)
88+
89+
self._key_selection_widget.currentTextChanged.connect(
90+
self._set_axis_keys
91+
)
92+
93+
self._update_layers(None)
94+
95+
@property
96+
defx_axis_key(self)->Optional[str]:
97+
"""Key to access x axis data from the FeaturesTable"""
98+
returnself._x_axis_key
99+
100+
@x_axis_key.setter
101+
defx_axis_key(self,key:Optional[str])->None:
102+
self._x_axis_key=key
103+
self._draw()
104+
105+
def_set_axis_keys(self,x_axis_key:str)->None:
106+
"""Set both axis keys and then redraw the plot"""
107+
self._x_axis_key=x_axis_key
108+
self._draw()
109+
110+
def_get_valid_axis_keys(self)->List[str]:
111+
"""
112+
Get the valid axis keys from the layer FeatureTable.
113+
114+
Returns
115+
-------
116+
axis_keys : List[str]
117+
The valid axis keys in the FeatureTable. If the table is empty
118+
or there isn't a table, returns an empty list.
119+
"""
120+
iflen(self.layers)==0ornot (hasattr(self.layers[0],"features")):
121+
return []
122+
else:
123+
returnself.layers[0].features.keys()
124+
125+
def_get_data(self)->Tuple[Optional[npt.NDArray[Any]],str]:
126+
"""Get the plot data.
127+
128+
Returns
129+
-------
130+
data : List[np.ndarray]
131+
List contains X and Y columns from the FeatureTable. Returns
132+
an empty array if nothing to plot.
133+
x_axis_name : str
134+
The title to display on the x axis. Returns
135+
an empty string if nothing to plot.
136+
"""
137+
ifnothasattr(self.layers[0],"features"):
138+
# if the selected layer doesn't have a featuretable,
139+
# skip draw
140+
returnNone,""
141+
142+
feature_table=self.layers[0].features
143+
144+
if (len(feature_table)==0)or (self.x_axis_keyisNone):
145+
returnNone,""
146+
147+
data=feature_table[self.x_axis_key]
148+
x_axis_name=self.x_axis_key.replace("_"," ")
149+
150+
returndata,x_axis_name
151+
152+
defon_update_layers(self)->None:
153+
"""
154+
Called when the layer selection changes by ``self.update_layers()``.
155+
"""
156+
# reset the axis keys
157+
self._x_axis_key=None
158+
159+
# Clear combobox
160+
self._key_selection_widget.clear()
161+
self._key_selection_widget.addItems(self._get_valid_axis_keys())
162+
163+
defdraw(self)->None:
164+
"""Clear the axes and histogram the currently selected layer/slice."""
165+
data,x_axis_name=self._get_data()
166+
167+
ifdataisNone:
168+
return
169+
170+
self.axes.hist(data,bins=50,edgecolor="white",linewidth=0.3)
171+
172+
# set ax labels
173+
self.axes.set_xlabel(x_axis_name)
174+
self.axes.set_ylabel("Counts [#]")

‎src/napari_matplotlib/napari.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ contributions:
1414
python_name:napari_matplotlib:FeaturesScatterWidget
1515
title:Make a scatter plot of layer features
1616

17+
-id:napari-matplotlib.features_histogram
18+
python_name:napari_matplotlib:FeaturesHistogramWidget
19+
title:Plot feature histograms
20+
1721
-id:napari-matplotlib.slice
1822
python_name:napari_matplotlib:SliceWidget
1923
title:Plot a 1D slice
@@ -28,5 +32,8 @@ contributions:
2832
-command:napari-matplotlib.features_scatter
2933
display_name:FeaturesScatter
3034

35+
-command:napari-matplotlib.features_histogram
36+
display_name:FeaturesHistogram
37+
3138
-command:napari-matplotlib.slice
3239
display_name:1D slice

‎src/napari_matplotlib/scatter.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
fromqtpy.QtWidgetsimportQComboBox,QLabel,QVBoxLayout,QWidget
66

77
from .baseimportSingleAxesWidget
8+
from .featuresimportFEATURES_LAYER_TYPES
89
from .utilimportInterval
910

1011
__all__= ["ScatterBaseWidget","ScatterWidget","FeaturesScatterWidget"]
@@ -94,13 +95,7 @@ class FeaturesScatterWidget(ScatterBaseWidget):
9495

9596
n_layers_input=Interval(1,1)
9697
# All layers that have a .features attributes
97-
input_layer_types= (
98-
napari.layers.Labels,
99-
napari.layers.Points,
100-
napari.layers.Shapes,
101-
napari.layers.Tracks,
102-
napari.layers.Vectors,
103-
)
98+
input_layer_types=FEATURES_LAYER_TYPES
10499

105100
def__init__(
106101
self,

‎src/napari_matplotlib/tests/test_histogram.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
fromcopyimportdeepcopy
22

3+
importnumpyasnp
34
importpytest
45

5-
fromnapari_matplotlibimportHistogramWidget
6+
fromnapari_matplotlibimportFeaturesHistogramWidget,HistogramWidget
7+
fromnapari_matplotlib.tests.helpersimport (
8+
assert_figures_equal,
9+
assert_figures_not_equal,
10+
)
611

712

813
@pytest.mark.mpl_image_compare
@@ -28,3 +33,90 @@ def test_histogram_3D(make_napari_viewer, brain_data):
2833
# Need to return a copy, as original figure is too eagerley garbage
2934
# collected by the widget
3035
returndeepcopy(fig)
36+
37+
38+
deftest_feature_histogram(make_napari_viewer):
39+
n_points=1000
40+
random_points=np.random.random((n_points,3))*10
41+
feature1=np.random.random(n_points)
42+
feature2=np.random.normal(size=n_points)
43+
44+
viewer=make_napari_viewer()
45+
viewer.add_points(
46+
random_points,
47+
properties={"feature1":feature1,"feature2":feature2},
48+
name="points1",
49+
)
50+
viewer.add_points(
51+
random_points,
52+
properties={"feature1":feature1,"feature2":feature2},
53+
name="points2",
54+
)
55+
56+
widget=FeaturesHistogramWidget(viewer)
57+
viewer.window.add_dock_widget(widget)
58+
59+
# Check whether changing the selected key changes the plot
60+
widget._set_axis_keys("feature1")
61+
fig1=deepcopy(widget.figure)
62+
63+
widget._set_axis_keys("feature2")
64+
assert_figures_not_equal(widget.figure,fig1)
65+
66+
# check whether selecting a different layer produces the same plot
67+
viewer.layers.selection.clear()
68+
viewer.layers.selection.add(viewer.layers[1])
69+
assert_figures_equal(widget.figure,fig1)
70+
71+
72+
@pytest.mark.mpl_image_compare
73+
deftest_feature_histogram2(make_napari_viewer):
74+
importnumpyasnp
75+
76+
np.random.seed(0)
77+
n_points=1000
78+
random_points=np.random.random((n_points,3))*10
79+
feature1=np.random.random(n_points)
80+
feature2=np.random.normal(size=n_points)
81+
82+
viewer=make_napari_viewer()
83+
viewer.add_points(
84+
random_points,
85+
properties={"feature1":feature1,"feature2":feature2},
86+
name="points1",
87+
)
88+
viewer.add_points(
89+
random_points,
90+
properties={"feature1":feature1,"feature2":feature2},
91+
name="points2",
92+
)
93+
94+
widget=FeaturesHistogramWidget(viewer)
95+
viewer.window.add_dock_widget(widget)
96+
widget._set_axis_keys("feature1")
97+
98+
fig=FeaturesHistogramWidget(viewer).figure
99+
returndeepcopy(fig)
100+
101+
102+
deftest_change_layer(make_napari_viewer,brain_data,astronaut_data):
103+
viewer=make_napari_viewer()
104+
widget=HistogramWidget(viewer)
105+
106+
viewer.add_image(brain_data[0],**brain_data[1])
107+
viewer.add_image(astronaut_data[0],**astronaut_data[1])
108+
109+
# Select first layer
110+
viewer.layers.selection.clear()
111+
viewer.layers.selection.add(viewer.layers[0])
112+
fig1=deepcopy(widget.figure)
113+
114+
# Re-selecting first layer should produce identical plot
115+
viewer.layers.selection.clear()
116+
viewer.layers.selection.add(viewer.layers[0])
117+
assert_figures_equal(widget.figure,fig1)
118+
119+
# Plotting the second layer should produce a different plot
120+
viewer.layers.selection.clear()
121+
viewer.layers.selection.add(viewer.layers[1])
122+
assert_figures_not_equal(widget.figure,fig1)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp