Skip to content

Cut Module

Import the cut utilities from spyral_utils.plot.

Module conatining methods for creating, saving, and using graphical Cuts on data

Classes:

Name Description
CutHandler

Handler to recieve vertices from a matplotlib or plotly selection event.

Cut2D

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

Functions:

Name Description
serialize_cut

Serialize cut to JSON and write to a file

deserialize_cut

Deserialize the JSON representation of a Cut2D from a file

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]

    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) -> np.ndarray:
        """Get the cut vertices

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

        """
        return tuple(self.polygon.exterior.coords)

    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) -> np.ndarray:
    """Get the cut vertices

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

    """
    return tuple(self.polygon.exterior.coords)

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]

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))
    )

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