Skip to content

Module Overview

Parent module of all of the plotting related modules

Exports

Cut2D, CutHandler, serialize_cut, deserialize_cut Hist1D, Hist2D, Histogrammer

Cut2D

Implementation of 2D cuts/gates/selections as used in many types of graphical analyses

Uses Shapely Polygon objects. Takes in a name (to identify the cut) and a list of points. The Polygon takes the verticies, and can then be used to check if a point(s) is inside of the polygon using the contains_* functions. Can be serialized to json format. Can also retreive Nx2 ndarray of vertices for plotting after the fact.

Attributes:

Name Type Description
path Path

A matplotlib path (polygon) that is the actual cut shape

name str

A name for the cut

x_axis str

A name for the x-coordinate axis. Can be used with dataframes to programmatically specify what data should be used to process a cut.

y_axis str

A name for the y-coordinate axis. Can be used with dataframes to programmatically specify what data should be used to process a cut.

Methods:

Name Description
is_point_inside

Check if a single point (x,y) is inside the cut

is_arr_inside

Check if a list of points (x,y) are inside the cut

is_cols_inside

Check if a set of polars Columns are inside the cut

get_vertices

Get the cut vertices

serialize_json

Get the JSON representation of the cut

Source code in src/spyral_utils/plot/cut.py
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
class Cut2D:
    """Implementation of 2D cuts/gates/selections as used in many types of graphical analyses

    Uses Shapely Polygon objects. Takes in a name (to identify the cut) and a list of points. The Polygon
    takes the verticies, and can then be used to check if a point(s) is inside of the polygon using the
    contains_* functions. Can be serialized to json format. Can also retreive Nx2 ndarray of vertices
    for plotting after the fact.

    Attributes
    ----------
    path: matplotlib.path.Path
        A matplotlib path (polygon) that is the actual cut shape
    name: str
        A name for the cut
    x_axis: str
        A name for the x-coordinate axis. Can be used with dataframes
        to programmatically specify what data should be used to process
        a cut.
    y_axis: str
        A name for the y-coordinate axis. Can be used with dataframes
        to programmatically specify what data should be used to process
        a cut.

    Methods
    -------
    is_point_inside(x: float, y: float) -> bool
        Check if a single point (x,y) is inside the cut
    is_arr_inside(points: list[tuple[float, float]]) -> list[bool]
        Check if a list of points (x,y) are inside the cut
    is_cols_inside(columns: Series) -> Series
        Check if a set of polars Columns are inside the cut
    get_vertices() -> ndarray
        Get the cut vertices
    serialize_json() -> str
        Get the JSON representation of the cut
    """

    def __init__(
        self,
        name: str,
        vertices: list[tuple[float, float]],
        x_axis: str = DEFAULT_CUT_AXIS,
        y_axis: str = DEFAULT_CUT_AXIS,
    ):
        self.polygon: Polygon = Polygon(vertices)
        self.name = name
        self.x_axis = x_axis
        self.y_axis = y_axis

    def is_point_inside(self, x: float, y: float) -> bool:
        """Is a point in the cut

        Parameters
        ----------
        x: float
            point x-coordinate
        y: float
            point y-coordinate

        Returns
        -------
        bool
            true if inside, false if outside
        """
        return self.polygon.contains(Point(x, y))

    def is_arr_inside(self, points: list[tuple[float, float]]) -> list[bool]:
        """Which of the points in this list are in the cut

        Parameters
        ----------
        points: list[tuple[float, float]]
            List of points (x,y)

        Returns
        -------
        list[bool]
            List of results of checking each point
        """
        return [contains_xy(self.polygon, point) for point in points]  # type: ignore

    def is_cols_inside(self, columns: Series) -> Series:
        """Which of the points in this Series are in the cut

        Parameters
        ----------
        columns: Series
            Polars dataframe series to check

        Returns
        -------
        Series
            Series of True or False for each point
        """
        data = np.transpose(
            [columns.struct.field(name).to_list() for name in columns.struct.fields]
        )
        return Series(
            [bool(contains_xy(self.polygon, x=point[0], y=point[1])) for point in data]
        )

    def get_vertices(self) -> list[tuple[float, float]]:
        """Get the cut vertices

        Returns
        -------
        list[tuple]
            the vertices

        """
        return tuple(self.polygon.exterior.coords)  # type: ignore

    def get_x_axis(self) -> str:
        """Get the name of the cut data x-axis

        The x-axis is the name of the data used to form
        the cut along the x-axis. Typically, this is the name
        of a dataframe column.

        Returns
        -------
        str
            The name of the x-axis. By default, the value
            is DefaultAxis.
        """
        return self.x_axis

    def get_y_axis(self) -> str:
        """Get the name of the cut data y-axis

        The y-axis is the name of the data used to form
        the cut along the y-axis. Typically, this is the name
        of a dataframe column.

        Returns
        -------
        str
            The name of the y-axis. By default, the value
            is DefaultAxis.
        """
        return self.y_axis

    def is_default_x_axis(self) -> bool:
        """Check if the x-axis is the default value

        If the x-axis field is unset, it defaults to DefaultAxis.

        Returns
        -------
        bool
            True if default, False otherwise
        """
        return self.x_axis == DEFAULT_CUT_AXIS

    def is_default_y_axis(self) -> bool:
        """Check if the y-axis is the default value

        If the y-axis field is unset, it defaults to DefaultAxis.

        Returns
        -------
        bool
            True if default, False otherwise
        """
        return self.y_axis == DEFAULT_CUT_AXIS

    def serialize_json(self) -> str:
        """Serialize to JSON

        Returns
        -------
        str
            JSON representation
        """
        return json.dumps(
            self,
            default=lambda obj: {
                "name": obj.name,
                "xaxis": obj.x_axis,
                "yaxis": obj.y_axis,
                "vertices": tuple(obj.polygon.exterior.coords),
            },
            indent=4,
        )

get_vertices()

Get the cut vertices

Returns:

Type Description
list[tuple]

the vertices

Source code in src/spyral_utils/plot/cut.py
231
232
233
234
235
236
237
238
239
240
def get_vertices(self) -> list[tuple[float, float]]:
    """Get the cut vertices

    Returns
    -------
    list[tuple]
        the vertices

    """
    return tuple(self.polygon.exterior.coords)  # type: ignore

get_x_axis()

Get the name of the cut data x-axis

The x-axis is the name of the data used to form the cut along the x-axis. Typically, this is the name of a dataframe column.

Returns:

Type Description
str

The name of the x-axis. By default, the value is DefaultAxis.

Source code in src/spyral_utils/plot/cut.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
def get_x_axis(self) -> str:
    """Get the name of the cut data x-axis

    The x-axis is the name of the data used to form
    the cut along the x-axis. Typically, this is the name
    of a dataframe column.

    Returns
    -------
    str
        The name of the x-axis. By default, the value
        is DefaultAxis.
    """
    return self.x_axis

get_y_axis()

Get the name of the cut data y-axis

The y-axis is the name of the data used to form the cut along the y-axis. Typically, this is the name of a dataframe column.

Returns:

Type Description
str

The name of the y-axis. By default, the value is DefaultAxis.

Source code in src/spyral_utils/plot/cut.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def get_y_axis(self) -> str:
    """Get the name of the cut data y-axis

    The y-axis is the name of the data used to form
    the cut along the y-axis. Typically, this is the name
    of a dataframe column.

    Returns
    -------
    str
        The name of the y-axis. By default, the value
        is DefaultAxis.
    """
    return self.y_axis

is_arr_inside(points)

Which of the points in this list are in the cut

Parameters:

Name Type Description Default
points list[tuple[float, float]]

List of points (x,y)

required

Returns:

Type Description
list[bool]

List of results of checking each point

Source code in src/spyral_utils/plot/cut.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def is_arr_inside(self, points: list[tuple[float, float]]) -> list[bool]:
    """Which of the points in this list are in the cut

    Parameters
    ----------
    points: list[tuple[float, float]]
        List of points (x,y)

    Returns
    -------
    list[bool]
        List of results of checking each point
    """
    return [contains_xy(self.polygon, point) for point in points]  # type: ignore

is_cols_inside(columns)

Which of the points in this Series are in the cut

Parameters:

Name Type Description Default
columns Series

Polars dataframe series to check

required

Returns:

Type Description
Series

Series of True or False for each point

Source code in src/spyral_utils/plot/cut.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
def is_cols_inside(self, columns: Series) -> Series:
    """Which of the points in this Series are in the cut

    Parameters
    ----------
    columns: Series
        Polars dataframe series to check

    Returns
    -------
    Series
        Series of True or False for each point
    """
    data = np.transpose(
        [columns.struct.field(name).to_list() for name in columns.struct.fields]
    )
    return Series(
        [bool(contains_xy(self.polygon, x=point[0], y=point[1])) for point in data]
    )

is_default_x_axis()

Check if the x-axis is the default value

If the x-axis field is unset, it defaults to DefaultAxis.

Returns:

Type Description
bool

True if default, False otherwise

Source code in src/spyral_utils/plot/cut.py
272
273
274
275
276
277
278
279
280
281
282
def is_default_x_axis(self) -> bool:
    """Check if the x-axis is the default value

    If the x-axis field is unset, it defaults to DefaultAxis.

    Returns
    -------
    bool
        True if default, False otherwise
    """
    return self.x_axis == DEFAULT_CUT_AXIS

is_default_y_axis()

Check if the y-axis is the default value

If the y-axis field is unset, it defaults to DefaultAxis.

Returns:

Type Description
bool

True if default, False otherwise

Source code in src/spyral_utils/plot/cut.py
284
285
286
287
288
289
290
291
292
293
294
def is_default_y_axis(self) -> bool:
    """Check if the y-axis is the default value

    If the y-axis field is unset, it defaults to DefaultAxis.

    Returns
    -------
    bool
        True if default, False otherwise
    """
    return self.y_axis == DEFAULT_CUT_AXIS

is_point_inside(x, y)

Is a point in the cut

Parameters:

Name Type Description Default
x float

point x-coordinate

required
y float

point y-coordinate

required

Returns:

Type Description
bool

true if inside, false if outside

Source code in src/spyral_utils/plot/cut.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def is_point_inside(self, x: float, y: float) -> bool:
    """Is a point in the cut

    Parameters
    ----------
    x: float
        point x-coordinate
    y: float
        point y-coordinate

    Returns
    -------
    bool
        true if inside, false if outside
    """
    return self.polygon.contains(Point(x, y))

serialize_json()

Serialize to JSON

Returns:

Type Description
str

JSON representation

Source code in src/spyral_utils/plot/cut.py
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
def serialize_json(self) -> str:
    """Serialize to JSON

    Returns
    -------
    str
        JSON representation
    """
    return json.dumps(
        self,
        default=lambda obj: {
            "name": obj.name,
            "xaxis": obj.x_axis,
            "yaxis": obj.y_axis,
            "vertices": tuple(obj.polygon.exterior.coords),
        },
        indent=4,
    )

CutHandler

Handler to recieve vertices from a matplotlib or plotly selector.

Typically will be used interactively. The appropriate on_select method should be passed to the selector object or callback for the plotting API used. CutHandler currently supports matplotlib and plotly. CutHandler can also be used in analysis applications to store cuts.

An example script for each API:

Matplotlib

from spyral_utils.plot import CutHandler, Cut2D, serialize_cut
from matplotlib.widgets import PolygonSelector
import matplotlib.pyplot as plt

fig, ax = plt.subplots(1,1)
handler = CutHandler()
selector = PolygonSelector(ax, handler.mpl_on_select)

# Plot some data here...

plt.show()

# Wait for user to draw a cut and close the window

my_cut = handler.cuts["cut_0"]
my_cut.name = "my_cut"
serialize_cut(mycut, "my_cut.json")

Plotly

from spyral_utils.plot import CutHandler, Cut2D, serialize_cut
import plotly.graph_objects as go

handler = CutHandler()

# Do some plotting

fig = go.Figure()
fig.add_trace(...)

# Bind the callback

my_plot = fig.data[0]
my_plot.on_select(handler.plotly_on_select)

# Wait for user selection

my_cut = handler.cuts["cut_0"]
my_cut.name = "my_cut"
serialize_cut(my_cut, "my_cut.json")

Attributes:

Name Type Description
cuts dict[str, Cut2D]

mapping of cut name to Cut2D

Methods:

Name Description
mpl_on_select

recieve a matplotlib polygon and create a Cut2D from it

plotly_on_select

recieve a plotly selection event and create a Cut2D from it

Source code in src/spyral_utils/plot/cut.py
 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
class CutHandler:
    """Handler to recieve vertices from a matplotlib or plotly selector.

    Typically will be used interactively. The appropriate on_select method should be passed to the selector object or callback for the
    plotting API used. CutHandler currently supports matplotlib and plotly. CutHandler can also be used in analysis applications to store cuts.

    An example script for each API:

    Matplotlib
    ```python
    from spyral_utils.plot import CutHandler, Cut2D, serialize_cut
    from matplotlib.widgets import PolygonSelector
    import matplotlib.pyplot as plt

    fig, ax = plt.subplots(1,1)
    handler = CutHandler()
    selector = PolygonSelector(ax, handler.mpl_on_select)

    # Plot some data here...

    plt.show()

    # Wait for user to draw a cut and close the window

    my_cut = handler.cuts["cut_0"]
    my_cut.name = "my_cut"
    serialize_cut(mycut, "my_cut.json")
    ```

    Plotly
    ```python
    from spyral_utils.plot import CutHandler, Cut2D, serialize_cut
    import plotly.graph_objects as go

    handler = CutHandler()

    # Do some plotting

    fig = go.Figure()
    fig.add_trace(...)

    # Bind the callback

    my_plot = fig.data[0]
    my_plot.on_select(handler.plotly_on_select)

    # Wait for user selection

    my_cut = handler.cuts["cut_0"]
    my_cut.name = "my_cut"
    serialize_cut(my_cut, "my_cut.json")

    ```

    Attributes
    ----------
    cuts: dict[str, Cut2D]
        mapping of cut name to Cut2D

    Methods
    -------
    mpl_on_select(verticies: list[tuple[float, float]])
        recieve a matplotlib polygon and create a Cut2D from it
    plotly_on_select(trace: Any, points: Any, selector: Any)
        recieve a plotly selection event and create a Cut2D from it
    """

    def __init__(self):
        self.cuts: dict[str, Cut2D] = {}

    def mpl_on_select(self, vertices: list[tuple[float, float]]):
        """Callback for use with matplotlib

        Parameters
        ----------
        vertices: list[tuple[float, float]]
            polygon vertices
        """
        cut_default_name = f"cut_{len(self.cuts)}"
        self.cuts[cut_default_name] = Cut2D(cut_default_name, vertices)

    def plotly_on_select(self, trace: Any, points: Any, selector: Any):
        """Callback for use with plotly

        Parameters
        ----------
        trace: Any
            The plotly trace from which the event originated (not relevant)
        points:
            A plotly Points object containing the data indicies within the selection (not relevant)
        selector:
            The selector object (either BoxSelector or LassoSelector)
        """
        if len(selector.xs) < 2:
            return

        cut_default_name = f"cut_{len(self.cuts)}"
        self.cuts[cut_default_name] = Cut2D(
            cut_default_name, list(zip(selector.xs, selector.ys))
        )

mpl_on_select(vertices)

Callback for use with matplotlib

Parameters:

Name Type Description Default
vertices list[tuple[float, float]]

polygon vertices

required
Source code in src/spyral_utils/plot/cut.py
 98
 99
100
101
102
103
104
105
106
107
def mpl_on_select(self, vertices: list[tuple[float, float]]):
    """Callback for use with matplotlib

    Parameters
    ----------
    vertices: list[tuple[float, float]]
        polygon vertices
    """
    cut_default_name = f"cut_{len(self.cuts)}"
    self.cuts[cut_default_name] = Cut2D(cut_default_name, vertices)

plotly_on_select(trace, points, selector)

Callback for use with plotly

Parameters:

Name Type Description Default
trace Any

The plotly trace from which the event originated (not relevant)

required
points Any

A plotly Points object containing the data indicies within the selection (not relevant)

required
selector Any

The selector object (either BoxSelector or LassoSelector)

required
Source code in src/spyral_utils/plot/cut.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def plotly_on_select(self, trace: Any, points: Any, selector: Any):
    """Callback for use with plotly

    Parameters
    ----------
    trace: Any
        The plotly trace from which the event originated (not relevant)
    points:
        A plotly Points object containing the data indicies within the selection (not relevant)
    selector:
        The selector object (either BoxSelector or LassoSelector)
    """
    if len(selector.xs) < 2:
        return

    cut_default_name = f"cut_{len(self.cuts)}"
    self.cuts[cut_default_name] = Cut2D(
        cut_default_name, list(zip(selector.xs, selector.ys))
    )

Hist1D dataclass

Dataclass wrapping a numpy array used to store histogram data and retrieve histogram statistics

Attributes:

Name Type Description
name str

histogram name

counts ndarray

array of histogram counts

bins ndarray

array of histogram bin edges

bin_width float

the width of histogram bins

Methods:

Name Description
get_bin

get the bin number for an x-coordinate value

stats_for_range

get some statistics for a subrange of the histogram

get_subrange

get a histogram subrange (bin edges, counts)

Source code in src/spyral_utils/plot/histogram.py
 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
@dataclass
class Hist1D:
    """Dataclass wrapping a numpy array used to store histogram data and retrieve histogram statistics

    Attributes
    ----------
    name: str
        histogram name
    counts: ndarray
        array of histogram counts
    bins: ndarray
        array of histogram bin edges
    bin_width: float
        the width of histogram bins

    Methods
    -------
    get_bin(x: float) -> int | None
        get the bin number for an x-coordinate value
    stats_for_range(xrange: tuple[float, float]) -> tuple[float, float, float] | None
        get some statistics for a subrange of the histogram
    get_subrange(xrange: tuple[float, float]) -> tuple[ndarray, ndarray]
        get a histogram subrange (bin edges, counts)
    """

    name: str
    counts: NDArray[np.float64]
    bins: NDArray[np.float64]
    bin_width: float

    def get_bin(self, x: float) -> int | None:
        """Get the bin number which contains the x-coordinate

        Parameters
        ----------
        x: float
            X-coordinate for which we want to find the bin number

        Returns
        -------
        int | None
            The bin number or None if the x value does not fall within the histogram
        """
        if x < self.bins.min() or x > self.bins.max():
            return None

        return int(floor((x - self.bins[0]) / self.bin_width))

    def stats_for_range(
        self, xrange: tuple[float, float]
    ) -> tuple[float, float, float] | None:
        """Get some statistics for a histogram subrange

        Calculates the mean, integral, and standard deviation of the sub-range

        Parameters
        ----------
        xrange: tuple[float, float]
            the subrange of the histogram (min, max) in x-coordinates

        Returns
        -------
        tuple[float, float, float] | None
            Returns a tuple of (integral, mean, std. dev.) for the subrange, or None if the subrange is not within the histogram bounds

        """
        clamped_range = clamp_range(xrange, (self.bins.min(), self.bins.max()))
        bin_min = self.get_bin(clamped_range[0])
        bin_max = self.get_bin(clamped_range[1])
        if bin_min is None or bin_max is None:
            return None
        integral = np.sum(self.counts[bin_min:bin_max])
        mean = np.average(
            self.bins[bin_min:bin_max], weights=self.counts[bin_min:bin_max]
        )
        variance = np.average(
            (self.bins[bin_min:bin_max] - mean) ** 2.0,
            weights=self.counts[bin_min:bin_max],
        )
        return (integral, mean, np.sqrt(variance))  # type: ignore

    def get_subrange(
        self, xrange: tuple[float, float]
    ) -> tuple[np.ndarray, np.ndarray]:
        """Get a subrange of the histogram

        Parameters
        ----------
        xrange: tuple[float, float]
            the subrange of the histogram (min, max) in x-coordinates

        Returns
        -------
        tuple[ndarray, ndarray]
            the subrange (bin edges, counts)
        """
        mask = np.logical_and(self.bins >= xrange[0], self.bins < xrange[1])
        return (self.bins[mask], self.counts[mask[:-1]])

get_bin(x)

Get the bin number which contains the x-coordinate

Parameters:

Name Type Description Default
x float

X-coordinate for which we want to find the bin number

required

Returns:

Type Description
int | None

The bin number or None if the x value does not fall within the histogram

Source code in src/spyral_utils/plot/histogram.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def get_bin(self, x: float) -> int | None:
    """Get the bin number which contains the x-coordinate

    Parameters
    ----------
    x: float
        X-coordinate for which we want to find the bin number

    Returns
    -------
    int | None
        The bin number or None if the x value does not fall within the histogram
    """
    if x < self.bins.min() or x > self.bins.max():
        return None

    return int(floor((x - self.bins[0]) / self.bin_width))

get_subrange(xrange)

Get a subrange of the histogram

Parameters:

Name Type Description Default
xrange tuple[float, float]

the subrange of the histogram (min, max) in x-coordinates

required

Returns:

Type Description
tuple[ndarray, ndarray]

the subrange (bin edges, counts)

Source code in src/spyral_utils/plot/histogram.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def get_subrange(
    self, xrange: tuple[float, float]
) -> tuple[np.ndarray, np.ndarray]:
    """Get a subrange of the histogram

    Parameters
    ----------
    xrange: tuple[float, float]
        the subrange of the histogram (min, max) in x-coordinates

    Returns
    -------
    tuple[ndarray, ndarray]
        the subrange (bin edges, counts)
    """
    mask = np.logical_and(self.bins >= xrange[0], self.bins < xrange[1])
    return (self.bins[mask], self.counts[mask[:-1]])

stats_for_range(xrange)

Get some statistics for a histogram subrange

Calculates the mean, integral, and standard deviation of the sub-range

Parameters:

Name Type Description Default
xrange tuple[float, float]

the subrange of the histogram (min, max) in x-coordinates

required

Returns:

Type Description
tuple[float, float, float] | None

Returns a tuple of (integral, mean, std. dev.) for the subrange, or None if the subrange is not within the histogram bounds

Source code in src/spyral_utils/plot/histogram.py
 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
def stats_for_range(
    self, xrange: tuple[float, float]
) -> tuple[float, float, float] | None:
    """Get some statistics for a histogram subrange

    Calculates the mean, integral, and standard deviation of the sub-range

    Parameters
    ----------
    xrange: tuple[float, float]
        the subrange of the histogram (min, max) in x-coordinates

    Returns
    -------
    tuple[float, float, float] | None
        Returns a tuple of (integral, mean, std. dev.) for the subrange, or None if the subrange is not within the histogram bounds

    """
    clamped_range = clamp_range(xrange, (self.bins.min(), self.bins.max()))
    bin_min = self.get_bin(clamped_range[0])
    bin_max = self.get_bin(clamped_range[1])
    if bin_min is None or bin_max is None:
        return None
    integral = np.sum(self.counts[bin_min:bin_max])
    mean = np.average(
        self.bins[bin_min:bin_max], weights=self.counts[bin_min:bin_max]
    )
    variance = np.average(
        (self.bins[bin_min:bin_max] - mean) ** 2.0,
        weights=self.counts[bin_min:bin_max],
    )
    return (integral, mean, np.sqrt(variance))  # type: ignore

Hist2D dataclass

Dataclass wrapping a numpy array used to store two-dimensional histogram data and retrieve histogram statistics

Attributes:

Name Type Description
name str

histogram name

counts ndarray

array of histogram counts

x_bins ndarray

array of histogram x bin edges

y_bins ndarray

array of histogram y bin edges

x_bin_width float

the width of histogram x bins

y_bin_width float

the width of histogram y bins

Methods:

Name Description
get_bin

get the x and y bin numbers for an (x,y)-coordinate value

stats_for_range

get some statistics for a subrange of the histogram

get_subrange

get a histogram subrange (x bin edges, y bin edges, counts)

Source code in src/spyral_utils/plot/histogram.py
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
@dataclass
class Hist2D:
    """Dataclass wrapping a numpy array used to store two-dimensional histogram data and retrieve histogram statistics

    Attributes
    ----------
    name: str
        histogram name
    counts: ndarray
        array of histogram counts
    x_bins: ndarray
        array of histogram x bin edges
    y_bins: ndarray
        array of histogram y bin edges
    x_bin_width: float
        the width of histogram x bins
    y_bin_width: float
        the width of histogram y bins

    Methods
    -------
    get_bin(coords: tuple[float, float]) -> tuple[int, int] | None
        get the x and y bin numbers for an (x,y)-coordinate value
    stats_for_range(xrange: tuple[float, float], yrange: tuple[float, float]) -> tuple[float, float, float, float, float] | None
        get some statistics for a subrange of the histogram
    get_subrange(xrange: tuple[float, float], xrange: tuple[float, float]) -> tuple[ndarray, ndarray, ndarray]
        get a histogram subrange (x bin edges, y bin edges, counts)
    """

    name: str
    counts: NDArray[np.float64]
    x_bins: NDArray[np.float64]
    y_bins: NDArray[np.float64]
    x_bin_width: float
    y_bin_width: float

    def get_bin(self, coords: tuple[float, float]) -> tuple[int, int] | None:
        """Get the x and y bin numbers for an (x,y)-coordinate value

        Parameters
        ----------
        coords: tuple[float, float]
            The (x,y) corrdinate for which we want to find the bin numbers

        Returns
        -------
        tuple[int, int] | None
            Returns the (x bin, y bin) numbers or None if out of range
        """
        if (coords[0] < self.x_bins.min() or coords[0] > self.x_bins.max()) or (
            coords[1] < self.y_bins.min() or coords[1] > self.y_bins.max()
        ):
            return None

        y_bin = int(floor((coords[1] - self.y_bins[0]) / self.y_bin_width))
        x_bin = int(floor((coords[0] - self.x_bins[0]) / self.x_bin_width))
        return (x_bin, y_bin)

    # returns (integral, mean x, std_dev x, mean y, std_dev y)
    def stats_for_range(
        self, xrange: tuple[float, float], yrange: tuple[float, float]
    ) -> tuple[float, float, float, float, float] | None:
        """Get some statistics for a histogram subrange

        Calculates the mean in x and y, integral, and standard deviation in x and y of the sub-range

        Parameters
        ----------
        xrange: tuple[float, float]
            the subrange of the histogram (min, max) in x-coordinates
        yrange: tuple[float, float]
            the subrange of the histogram (min, max) in y-coordinates

        Returns
        -------
        tuple[float, float, float, float, float] | None
            Returns a tuple of (integral, x mean, y mean, x std. dev., y std. dev.) for the subrange, or None if the subrange is not within the histogram bounds

        """
        clamped_x_range = clamp_range(xrange, (self.x_bins.min(), self.x_bins.max()))
        clamped_y_range = clamp_range(yrange, (self.y_bins.min(), self.y_bins.max()))
        bin_min = self.get_bin((clamped_x_range[0], clamped_y_range[0]))
        bin_max = self.get_bin((clamped_x_range[1], clamped_y_range[1]))
        if bin_min is None or bin_max is None:
            return None

        x_bin_range = np.arange(start=bin_min[0], stop=bin_max[0], step=1)
        y_bin_range = np.arange(start=bin_min[1], stop=bin_max[1], step=1)
        bin_mesh = np.ix_(y_bin_range, x_bin_range)

        integral = np.sum(self.counts[bin_mesh])
        mean_x = np.average(
            self.x_bins[bin_min[0] : bin_max[0]],
            weights=np.sum(self.counts.T[bin_min[0] : bin_max[0]], 1),
        )
        mean_y = np.average(
            self.y_bins[bin_min[1] : bin_max[1]],
            weights=np.sum(self.counts[bin_min[1] : bin_max[1]], 1),
        )
        var_x = np.average(
            (self.x_bins[bin_min[0] : bin_max[0]] - mean_x) ** 2.0,
            weights=np.sum(self.counts.T[bin_min[0] : bin_max[0]], 1),
        )
        var_y = np.average(
            (self.y_bins[bin_min[1] : bin_max[1]] - mean_y) ** 2.0,
            weights=np.sum(self.counts[bin_min[1] : bin_max[1]], 1),
        )
        return (integral, mean_x, mean_y, np.sqrt(var_x), np.sqrt(var_y))  # type: ignore

    def get_subrange(
        self, xrange: tuple[float, float], yrange: tuple[float, float]
    ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
        """Get a subrange of the histogram

        Parameters
        ----------
        xrange: tuple[float, float]
            the subrange of the histogram (min, max) in x-coordinates
        yrange: tuple[float, float]
            the subrange of the histogram (min, max) in y-coordinates

        Returns
        -------
        tuple[ndarray, ndarray, ndarray]
            the subrange (x bin edges, y bin edges, counts)
        """
        x_mask = np.logical_and(self.x_bins >= xrange[0], self.x_bins < xrange[1])
        y_mask = np.logical_and(self.y_bins >= yrange[0], self.y_bins < yrange[1])
        bin_mesh = np.ix_(y_mask, x_mask)
        return (self.x_bins[x_mask], self.y_bins[y_mask], self.counts[bin_mesh])

get_bin(coords)

Get the x and y bin numbers for an (x,y)-coordinate value

Parameters:

Name Type Description Default
coords tuple[float, float]

The (x,y) corrdinate for which we want to find the bin numbers

required

Returns:

Type Description
tuple[int, int] | None

Returns the (x bin, y bin) numbers or None if out of range

Source code in src/spyral_utils/plot/histogram.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def get_bin(self, coords: tuple[float, float]) -> tuple[int, int] | None:
    """Get the x and y bin numbers for an (x,y)-coordinate value

    Parameters
    ----------
    coords: tuple[float, float]
        The (x,y) corrdinate for which we want to find the bin numbers

    Returns
    -------
    tuple[int, int] | None
        Returns the (x bin, y bin) numbers or None if out of range
    """
    if (coords[0] < self.x_bins.min() or coords[0] > self.x_bins.max()) or (
        coords[1] < self.y_bins.min() or coords[1] > self.y_bins.max()
    ):
        return None

    y_bin = int(floor((coords[1] - self.y_bins[0]) / self.y_bin_width))
    x_bin = int(floor((coords[0] - self.x_bins[0]) / self.x_bin_width))
    return (x_bin, y_bin)

get_subrange(xrange, yrange)

Get a subrange of the histogram

Parameters:

Name Type Description Default
xrange tuple[float, float]

the subrange of the histogram (min, max) in x-coordinates

required
yrange tuple[float, float]

the subrange of the histogram (min, max) in y-coordinates

required

Returns:

Type Description
tuple[ndarray, ndarray, ndarray]

the subrange (x bin edges, y bin edges, counts)

Source code in src/spyral_utils/plot/histogram.py
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def get_subrange(
    self, xrange: tuple[float, float], yrange: tuple[float, float]
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """Get a subrange of the histogram

    Parameters
    ----------
    xrange: tuple[float, float]
        the subrange of the histogram (min, max) in x-coordinates
    yrange: tuple[float, float]
        the subrange of the histogram (min, max) in y-coordinates

    Returns
    -------
    tuple[ndarray, ndarray, ndarray]
        the subrange (x bin edges, y bin edges, counts)
    """
    x_mask = np.logical_and(self.x_bins >= xrange[0], self.x_bins < xrange[1])
    y_mask = np.logical_and(self.y_bins >= yrange[0], self.y_bins < yrange[1])
    bin_mesh = np.ix_(y_mask, x_mask)
    return (self.x_bins[x_mask], self.y_bins[y_mask], self.counts[bin_mesh])

stats_for_range(xrange, yrange)

Get some statistics for a histogram subrange

Calculates the mean in x and y, integral, and standard deviation in x and y of the sub-range

Parameters:

Name Type Description Default
xrange tuple[float, float]

the subrange of the histogram (min, max) in x-coordinates

required
yrange tuple[float, float]

the subrange of the histogram (min, max) in y-coordinates

required

Returns:

Type Description
tuple[float, float, float, float, float] | None

Returns a tuple of (integral, x mean, y mean, x std. dev., y std. dev.) for the subrange, or None if the subrange is not within the histogram bounds

Source code in src/spyral_utils/plot/histogram.py
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
240
241
242
243
def stats_for_range(
    self, xrange: tuple[float, float], yrange: tuple[float, float]
) -> tuple[float, float, float, float, float] | None:
    """Get some statistics for a histogram subrange

    Calculates the mean in x and y, integral, and standard deviation in x and y of the sub-range

    Parameters
    ----------
    xrange: tuple[float, float]
        the subrange of the histogram (min, max) in x-coordinates
    yrange: tuple[float, float]
        the subrange of the histogram (min, max) in y-coordinates

    Returns
    -------
    tuple[float, float, float, float, float] | None
        Returns a tuple of (integral, x mean, y mean, x std. dev., y std. dev.) for the subrange, or None if the subrange is not within the histogram bounds

    """
    clamped_x_range = clamp_range(xrange, (self.x_bins.min(), self.x_bins.max()))
    clamped_y_range = clamp_range(yrange, (self.y_bins.min(), self.y_bins.max()))
    bin_min = self.get_bin((clamped_x_range[0], clamped_y_range[0]))
    bin_max = self.get_bin((clamped_x_range[1], clamped_y_range[1]))
    if bin_min is None or bin_max is None:
        return None

    x_bin_range = np.arange(start=bin_min[0], stop=bin_max[0], step=1)
    y_bin_range = np.arange(start=bin_min[1], stop=bin_max[1], step=1)
    bin_mesh = np.ix_(y_bin_range, x_bin_range)

    integral = np.sum(self.counts[bin_mesh])
    mean_x = np.average(
        self.x_bins[bin_min[0] : bin_max[0]],
        weights=np.sum(self.counts.T[bin_min[0] : bin_max[0]], 1),
    )
    mean_y = np.average(
        self.y_bins[bin_min[1] : bin_max[1]],
        weights=np.sum(self.counts[bin_min[1] : bin_max[1]], 1),
    )
    var_x = np.average(
        (self.x_bins[bin_min[0] : bin_max[0]] - mean_x) ** 2.0,
        weights=np.sum(self.counts.T[bin_min[0] : bin_max[0]], 1),
    )
    var_y = np.average(
        (self.y_bins[bin_min[1] : bin_max[1]] - mean_y) ** 2.0,
        weights=np.sum(self.counts[bin_min[1] : bin_max[1]], 1),
    )
    return (integral, mean_x, mean_y, np.sqrt(var_x), np.sqrt(var_y))  # type: ignore

Histogrammer

Histogrammer is a wrapper around a dictionary of str->Hist1D|Hist2D that interfaces with matplotlib

A new histogram can be added to the dictionary using the add_hist1d/add_hist2d methods. The name passed to these methods is used as the key for the dictionary. To add data to the histograms use the fill_hist1d/fill_hist2d methods. The fill methods accept arrays of data, and this is by intent. It would not be efficient to fill the histograms point by point. Rather, prefer passing entire data sets (like dataframe columns). Finally, to retrieve a histogram (for plotting, etc), use the get_hist1d/get_hist2d methods. Prefer the getters over direct access to the underlying dictionary as the getters perfom some error checking.

Attributes:

Name Type Description
histograms dict[str, Hist1D | Hist2D]

the histograms held by the Histogrammer, mapped by name

Methods:

Name Description
add_hist1d

add a Hist1D

add_hist2d

add a Hist2D

fill_hist1d

fill an existing Hist1D with some data

fill_hist2d

fill an existing Hist2D with some data

get_hist1d

get a Hist1D by name

get_hist2d

get a Hist2D by name

Source code in src/spyral_utils/plot/histogram.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
class Histogrammer:
    """Histogrammer is a wrapper around a dictionary of str->Hist1D|Hist2D that interfaces with matplotlib

    A new histogram can be added to the dictionary using the add_hist1d/add_hist2d methods. The name passed to
    these methods is used as the key for the dictionary. To add data to the histograms use the fill_hist1d/fill_hist2d methods.
    The fill methods accept arrays of data, and this is by intent. It would not be efficient to fill the histograms point by point. Rather, prefer
    passing entire data sets (like dataframe columns). Finally, to retrieve a histogram (for plotting, etc), use the get_hist1d/get_hist2d methods.
    Prefer the getters over direct access to the underlying dictionary as the getters perfom some error checking.

    Attributes
    ----------
    histograms: dict[str, Hist1D | Hist2D]
        the histograms held by the Histogrammer, mapped by name

    Methods
    -------
    add_hist1d(name: str, bins: int, range: tuple[float, float])
        add a Hist1D
    add_hist2d(name: str, bins: tuple[int, int], ranges: tuple[tuple[float, float], tuple[float, float]])
        add a Hist2D
    fill_hist1d(self, name: str, data: ndarray) -> bool
        fill an existing Hist1D with some data
    fill_hist2d(self, name: str, x_data: ndarray, y_data: ndarray) -> bool
        fill an existing Hist2D with some data
    get_hist1d(name: str) -> Hist1D | None
        get a Hist1D by name
    get_hist2d(name: str) -> Hist2D | None
        get a Hist2D by name
    """

    def __init__(self):
        self.histograms: dict[str, Hist1D | Hist2D] = {}

    def add_hist1d(self, name: str, bins: int, range: tuple[float, float]):
        """Add a Hist1D to the Histogrammer

        Parameters
        ----------
        name: str
            The name of the histogram, it should be unqiue
        bins: int
            The number of bins
        range: tuple[float, float]
            The x-range of the histogram in x-axis coordinates
        """
        if name in self.histograms:
            print(f"Overwriting histogram named {name} in Histogrammer.add_histogram!")

        hist = Hist1D(
            name, np.empty(0), np.empty(0), np.abs(range[0] - range[1]) / float(bins)
        )
        hist.counts, hist.bins = np.histogram(a=[], bins=bins, range=range)
        self.histograms[name] = hist

    def add_hist2d(
        self,
        name: str,
        bins: tuple[int, int],
        ranges: tuple[tuple[float, float], tuple[float, float]],
    ):
        """Add a Hist2D to the Histogrammer

        Parameters
        ----------
        name: str
            The name of the histogram, it should be unqiue
        bins: tuple[int, int]
            The number of (x bins, y bins)
        ranges: tuple[tuple[float, float], tuple[float, float]]
            The range of the histogram ((min x, max x), (min y, max y))
        """
        if name in self.histograms:
            print(f"Overwriting histogram named {name} in Histogrammer.add_histogram!")

        hist = Hist2D(
            name,
            np.empty(0),
            np.empty(0),
            np.empty(0),
            np.abs(ranges[0][0] - ranges[0][1]) / float(bins[0]),
            np.abs(ranges[1][0] - ranges[1][1]) / float(bins[1]),
        )
        hist.counts, hist.x_bins, hist.y_bins = np.histogram2d(
            x=[], y=[], bins=bins, range=ranges
        )
        hist.counts = hist.counts.T
        self.histograms[name] = hist

    def fill_hist1d(self, name: str, data: np.ndarray) -> bool:
        """Fill a Hist1D with some data

        Parameters
        ----------
        name: str
            The name of the Hist1D
        data: ndarray
            The data to fill the histogram with. Should be a 1-D array

        Returns
        -------
        bool
            Indicates if data was successfully added to the histogram

        """
        if name not in self.histograms:
            return False

        hist = self.histograms[name]
        if type(hist) is not Hist1D:
            return False

        hist.counts = hist.counts + np.histogram(a=data, bins=hist.bins)[0]
        return True

    def fill_hist2d(self, name: str, x_data: np.ndarray, y_data: np.ndarray) -> bool:
        """Fill a Hist1D with some data

        The parameters x_data and y_data should have the same length.

        Parameters
        ----------
        name: str
            The name of the Hist1D
        x_data: ndarray
            The x coordinates of the data. Should be a 1-D array.
        y_data: ndarray
            The y coordinates of the data. Should be a 1-D array.

        Returns
        -------
        bool
            Indicates if data was successfully added to the histogram

        """
        if name not in self.histograms:
            return False

        hist = self.histograms[name]
        if type(hist) is not Hist2D:
            return False
        counts, _, _ = np.histogram2d(
            x_data.flatten(),
            y_data.flatten(),
            bins=(hist.x_bins, hist.y_bins),  # type: ignore
        )
        hist.counts += counts.T
        return True

    def get_hist1d(self, name: str) -> Hist1D | None:
        """Retrieve a Hist1D by name

        Parameters
        ----------
        name: str
            The name of the histogram

        Returns
        -------
        Hist1D | None
            Returns Hist1D if a Hist1D exists with the given name. Otherwise returns None.

        """
        if name not in self.histograms:
            return None

        hist = self.histograms[name]
        if type(hist) is not Hist1D:
            return None
        else:
            return hist

    def get_hist2d(self, name: str) -> Hist2D | None:
        """Retrieve a Hist2D by name

        Parameters
        ----------
        name: str
            The name of the histogram

        Returns
        -------
        Hist2D | None
            Returns Hist2D if a Hist2D exists with the given name. Otherwise returns None.

        """
        if name not in self.histograms:
            return None

        hist = self.histograms[name]
        if type(hist) is not Hist2D:
            return None
        else:
            return hist

add_hist1d(name, bins, range)

Add a Hist1D to the Histogrammer

Parameters:

Name Type Description Default
name str

The name of the histogram, it should be unqiue

required
bins int

The number of bins

required
range tuple[float, float]

The x-range of the histogram in x-axis coordinates

required
Source code in src/spyral_utils/plot/histogram.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
def add_hist1d(self, name: str, bins: int, range: tuple[float, float]):
    """Add a Hist1D to the Histogrammer

    Parameters
    ----------
    name: str
        The name of the histogram, it should be unqiue
    bins: int
        The number of bins
    range: tuple[float, float]
        The x-range of the histogram in x-axis coordinates
    """
    if name in self.histograms:
        print(f"Overwriting histogram named {name} in Histogrammer.add_histogram!")

    hist = Hist1D(
        name, np.empty(0), np.empty(0), np.abs(range[0] - range[1]) / float(bins)
    )
    hist.counts, hist.bins = np.histogram(a=[], bins=bins, range=range)
    self.histograms[name] = hist

add_hist2d(name, bins, ranges)

Add a Hist2D to the Histogrammer

Parameters:

Name Type Description Default
name str

The name of the histogram, it should be unqiue

required
bins tuple[int, int]

The number of (x bins, y bins)

required
ranges tuple[tuple[float, float], tuple[float, float]]

The range of the histogram ((min x, max x), (min y, max y))

required
Source code in src/spyral_utils/plot/histogram.py
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
def add_hist2d(
    self,
    name: str,
    bins: tuple[int, int],
    ranges: tuple[tuple[float, float], tuple[float, float]],
):
    """Add a Hist2D to the Histogrammer

    Parameters
    ----------
    name: str
        The name of the histogram, it should be unqiue
    bins: tuple[int, int]
        The number of (x bins, y bins)
    ranges: tuple[tuple[float, float], tuple[float, float]]
        The range of the histogram ((min x, max x), (min y, max y))
    """
    if name in self.histograms:
        print(f"Overwriting histogram named {name} in Histogrammer.add_histogram!")

    hist = Hist2D(
        name,
        np.empty(0),
        np.empty(0),
        np.empty(0),
        np.abs(ranges[0][0] - ranges[0][1]) / float(bins[0]),
        np.abs(ranges[1][0] - ranges[1][1]) / float(bins[1]),
    )
    hist.counts, hist.x_bins, hist.y_bins = np.histogram2d(
        x=[], y=[], bins=bins, range=ranges
    )
    hist.counts = hist.counts.T
    self.histograms[name] = hist

fill_hist1d(name, data)

Fill a Hist1D with some data

Parameters:

Name Type Description Default
name str

The name of the Hist1D

required
data ndarray

The data to fill the histogram with. Should be a 1-D array

required

Returns:

Type Description
bool

Indicates if data was successfully added to the histogram

Source code in src/spyral_utils/plot/histogram.py
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
def fill_hist1d(self, name: str, data: np.ndarray) -> bool:
    """Fill a Hist1D with some data

    Parameters
    ----------
    name: str
        The name of the Hist1D
    data: ndarray
        The data to fill the histogram with. Should be a 1-D array

    Returns
    -------
    bool
        Indicates if data was successfully added to the histogram

    """
    if name not in self.histograms:
        return False

    hist = self.histograms[name]
    if type(hist) is not Hist1D:
        return False

    hist.counts = hist.counts + np.histogram(a=data, bins=hist.bins)[0]
    return True

fill_hist2d(name, x_data, y_data)

Fill a Hist1D with some data

The parameters x_data and y_data should have the same length.

Parameters:

Name Type Description Default
name str

The name of the Hist1D

required
x_data ndarray

The x coordinates of the data. Should be a 1-D array.

required
y_data ndarray

The y coordinates of the data. Should be a 1-D array.

required

Returns:

Type Description
bool

Indicates if data was successfully added to the histogram

Source code in src/spyral_utils/plot/histogram.py
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
def fill_hist2d(self, name: str, x_data: np.ndarray, y_data: np.ndarray) -> bool:
    """Fill a Hist1D with some data

    The parameters x_data and y_data should have the same length.

    Parameters
    ----------
    name: str
        The name of the Hist1D
    x_data: ndarray
        The x coordinates of the data. Should be a 1-D array.
    y_data: ndarray
        The y coordinates of the data. Should be a 1-D array.

    Returns
    -------
    bool
        Indicates if data was successfully added to the histogram

    """
    if name not in self.histograms:
        return False

    hist = self.histograms[name]
    if type(hist) is not Hist2D:
        return False
    counts, _, _ = np.histogram2d(
        x_data.flatten(),
        y_data.flatten(),
        bins=(hist.x_bins, hist.y_bins),  # type: ignore
    )
    hist.counts += counts.T
    return True

get_hist1d(name)

Retrieve a Hist1D by name

Parameters:

Name Type Description Default
name str

The name of the histogram

required

Returns:

Type Description
Hist1D | None

Returns Hist1D if a Hist1D exists with the given name. Otherwise returns None.

Source code in src/spyral_utils/plot/histogram.py
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
def get_hist1d(self, name: str) -> Hist1D | None:
    """Retrieve a Hist1D by name

    Parameters
    ----------
    name: str
        The name of the histogram

    Returns
    -------
    Hist1D | None
        Returns Hist1D if a Hist1D exists with the given name. Otherwise returns None.

    """
    if name not in self.histograms:
        return None

    hist = self.histograms[name]
    if type(hist) is not Hist1D:
        return None
    else:
        return hist

get_hist2d(name)

Retrieve a Hist2D by name

Parameters:

Name Type Description Default
name str

The name of the histogram

required

Returns:

Type Description
Hist2D | None

Returns Hist2D if a Hist2D exists with the given name. Otherwise returns None.

Source code in src/spyral_utils/plot/histogram.py
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
def get_hist2d(self, name: str) -> Hist2D | None:
    """Retrieve a Hist2D by name

    Parameters
    ----------
    name: str
        The name of the histogram

    Returns
    -------
    Hist2D | None
        Returns Hist2D if a Hist2D exists with the given name. Otherwise returns None.

    """
    if name not in self.histograms:
        return None

    hist = self.histograms[name]
    if type(hist) is not Hist2D:
        return None
    else:
        return hist

deserialize_cut(filepath)

Deserialize the JSON representation of a Cut2D

Parameters:

Name Type Description Default
filepath Path

Path at which cut should be read from

required

Returns:

Type Description
Cut2D | None

Returns a Cut2D on success, None on failure

Source code in src/spyral_utils/plot/cut.py
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
def deserialize_cut(filepath: Path) -> Cut2D | None:
    """Deserialize the JSON representation of a Cut2D

    Parameters
    ----------
    filepath: Path
        Path at which cut should be read from

    Returns
    -------
    Cut2D | None
        Returns a Cut2D on success, None on failure
    """
    try:
        with open(filepath, "r") as input:
            buffer = input.read()
            cut_dict = json.loads(buffer)
            if not ("name" in cut_dict and "vertices" in cut_dict):
                print(
                    f"Data in file {filepath} is not the right format for Cut2D, could not load"
                )
                return None
            xaxis = DEFAULT_CUT_AXIS
            yaxis = DEFAULT_CUT_AXIS
            if "xaxis" in cut_dict and "yaxis" in cut_dict:
                xaxis = cut_dict["xaxis"]
                yaxis = cut_dict["yaxis"]
            return Cut2D(
                cut_dict["name"],
                cut_dict["vertices"],
                x_axis=xaxis,
                y_axis=yaxis,
            )
    except Exception as error:
        print(
            f"An error occurred reading trying to read a cut from file {filepath}: {error}"
        )
        return None

serialize_cut(cut, filepath)

Serialize the cut to JSON and write to a file file

Parameters:

Name Type Description Default
cut Cut2D

Cut to serialize

required
filepath Path

Path at which cut should be written

required

Returns:

Type Description
bool

True on success, False on failure

Source code in src/spyral_utils/plot/cut.py
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
def serialize_cut(cut: Cut2D, filepath: Path) -> bool:
    """Serialize the cut to JSON and write to a file file

    Parameters
    ----------
    cut: Cut2D
        Cut to serialize
    filepath: Path
        Path at which cut should be written

    Returns
    -------
    bool
        True on success, False on failure
    """
    json_str = cut.serialize_json()
    try:
        with open(filepath, "w") as output:
            output.write(json_str)
            return True
    except OSError as error:
        print(f"An error occurred writing cut {cut.name} to file {filepath}: {error}")
        return False