-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathhistogram.py
More file actions
239 lines (189 loc) · 6.72 KB
/
histogram.py
File metadata and controls
239 lines (189 loc) · 6.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# -*- coding: utf-8 -*-
# pylint: disable=C0103
"""
Histogram item
"""
from __future__ import annotations
import weakref
from typing import TYPE_CHECKING
import numpy as np
from guidata.configtools import get_icon
from guidata.dataset import update_dataset
from guidata.utils.misc import assert_interfaces_valid
from qwt import QwtPlotCurve
from plotpy.config import _
from plotpy.interfaces import IBasePlotItem, IHistDataSource
from plotpy.items.curve.base import CurveItem
from plotpy.styles.curve import CurveParam
from plotpy.styles.histogram import HistogramParam
if TYPE_CHECKING:
from plotpy.items.image.base import BaseImageItem
from plotpy.styles.base import ItemParameters
class HistDataSource:
"""An objects that provides an Histogram data source interface
to a simple numpy array of data
"""
__implements__ = (IHistDataSource,)
def __init__(self, data: np.ndarray) -> None:
self.data = data
def get_histogram(
self, nbins: int, drange: tuple[float, float] | None = None
) -> tuple[np.ndarray, np.ndarray]:
"""
Return a tuple (hist, bins) where hist is a list of histogram values
Args:
nbins: number of bins
drange: lower and upper range of the bins. If not provided, range is
simply (data.min(), data.max()). Values outside the range are ignored.
Returns:
Tuple (hist, bins)
"""
return np.histogram(self.data, bins=nbins, range=drange)
assert_interfaces_valid(HistDataSource)
class HistogramItem(CurveItem):
"""Histogram plot item
Args:
curveparam: Curve parameters
histparam: Histogram parameters
"""
__implements__ = (IBasePlotItem,)
def __init__(
self,
curveparam: CurveParam | None = None,
histparam: HistogramParam | None = None,
keep_weakref: bool = False,
) -> None:
self.hist_count = None
self.hist_bins = None
self.bins = None
self.bin_range = None
self.old_bins = None
self.source: BaseImageItem | None = None
self.logscale: bool | None = None
self.old_logscale = None
self.keep_weakref = keep_weakref
if curveparam is None:
curveparam = CurveParam(_("Curve"), icon="curve.png")
curveparam.curvestyle = "Steps"
if histparam is None:
self.histparam = HistogramParam(title=_("Histogram"), icon="histogram.png")
else:
self.histparam = histparam
CurveItem.__init__(self, curveparam)
self.setCurveAttribute(QwtPlotCurve.Inverted)
self.setIcon(get_icon("histogram.png"))
def set_hist_source(self, src: BaseImageItem) -> None:
"""Set histogram source
Args:
src: Object with method `get_histogram`, e.g. objects derived from
:py:class:`.ImageItem`
"""
if self.keep_weakref:
self.source = weakref.ref(src)
else:
self.source = src
self.update_histogram()
def get_hist_source(self) -> BaseImageItem | None:
"""Return histogram source
Returns:
object: Object with method `get_histogram`, e.g. objects derived from
:py:class:`.ImageItem`
"""
if self.source is not None:
if self.keep_weakref:
return self.source()
return self.source
def set_hist_data(self, data: np.ndarray) -> None:
"""Set histogram data
Args:
data: numpy array
"""
self.set_hist_source(HistDataSource(data))
def set_logscale(self, state: bool) -> None:
"""Sets whether we use a logarithm or linear scale
for the histogram counts
Args:
state: True for logarithmic scale
"""
self.logscale = state
self.update_histogram()
def get_logscale(self) -> bool | None:
"""Returns the status of the scale
Returns:
bool: True for logarithmic scale
"""
return self.logscale
def set_bins(self, n_bins: int) -> None:
"""Sets the number of bins
Args:
n_bins: number of bins
"""
self.bins = n_bins
self.update_histogram()
def get_bins(self) -> int | None:
"""Returns the number of bins
Returns:
int: number of bins
"""
return self.bins
def set_bin_range(self, bin_range: tuple[float, float] | None) -> None:
"""Sets the range of the bins
Args:
bin_range: (min, max) or None for automatic range
"""
self.bin_range = bin_range
self.update_histogram()
def get_bin_range(self) -> tuple[float, float] | None:
"""Returns the range of the bins
Returns:
tuple: (min, max)
"""
return self.bin_range
def compute_histogram(self) -> tuple[np.ndarray, np.ndarray]:
"""Compute histogram data
Returns:
tuple: (hist, bins)
"""
return self.get_hist_source().get_histogram(self.bins, self.bin_range)
def update_histogram(self) -> None:
"""Update histogram data"""
if self.get_hist_source() is None:
return
hist, bin_edges = self.compute_histogram()
# Duplicate the first `hist` value to get a step-like histogram:
hist = np.concatenate(([hist[0]], hist))
if self.logscale:
hist = np.log(hist + 1)
self.set_data(bin_edges, hist)
# Autoscale only if logscale/bins have changed
if self.bins != self.old_bins or self.logscale != self.old_logscale:
if self.plot():
self.plot().do_autoscale()
self.old_bins = self.bins
self.old_logscale = self.logscale
plot = self.plot()
if plot is not None:
plot.do_autoscale(replot=True)
def update_params(self):
"""Update histogram parameters"""
self.histparam.update_hist(self)
CurveItem.update_params(self)
def get_item_parameters(self, itemparams: ItemParameters) -> None:
"""
Appends datasets to the list of DataSets describing the parameters
used to customize apearance of this item
Args:
itemparams: Item parameters
"""
CurveItem.get_item_parameters(self, itemparams)
itemparams.add("HistogramParam", self, self.histparam)
def set_item_parameters(self, itemparams):
"""
:param itemparams:
"""
update_dataset(
self.histparam, itemparams.get("HistogramParam"), visible_only=True
)
self.histparam.update_hist(self)
CurveItem.set_item_parameters(self, itemparams)
assert_interfaces_valid(HistogramItem)