Skip to content

himena.standards.roi

Standard ROI (Region of Interest) classes for images.

CircleRoi

ROI that represents a circle.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
x int | float

X-coordinate of the center.

required
y int | float

Y-coordinate of the center.

required
radius int | float

Radius of the circle.

required
Source code in src\himena\standards\roi\core.py
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
class CircleRoi(Roi2D):
    """ROI that represents a circle."""

    x: Scalar = Field(..., description="X-coordinate of the center.")
    y: Scalar = Field(..., description="Y-coordinate of the center.")
    radius: Scalar = Field(..., description="Radius of the circle.")

    def shifted(self, dx: float, dy: float) -> CircleRoi:
        return self.model_copy(update={"x": self.x + dx, "y": self.y + dy})

    def area(self) -> float:
        return math.pi * self.radius**2

    def circumference(self) -> float:
        return 2 * math.pi * self.radius

    def bbox(self) -> Rect[float]:
        return Rect(
            self.x - self.radius,
            self.y - self.radius,
            2 * self.radius,
            2 * self.radius,
        )

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        _yy, _xx = np.indices(shape[-2:])
        comp_a = (_yy - self.y) / self.radius
        comp_b = (_xx - self.x) / self.radius
        return comp_a**2 + comp_b**2 <= 1

EllipseRoi

ROI that represents an ellipse.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
x int | float

X-coordinate of the left boundary.

required
y int | float

Y-coordinate of the top boundary.

required
width int | float

Diameter along the x-axis.

required
height int | float

Diameter along the y-axis.

required
Source code in src\himena\standards\roi\core.py
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
class EllipseRoi(Roi2D):
    """ROI that represents an ellipse."""

    x: Scalar = Field(..., description="X-coordinate of the left boundary.")
    y: Scalar = Field(..., description="Y-coordinate of the top boundary.")
    width: Scalar = Field(..., description="Diameter along the x-axis.")
    height: Scalar = Field(..., description="Diameter along the y-axis.")

    def center(self) -> tuple[float, float]:
        return self.x + self.width / 2, self.y + self.height / 2

    def shifted(self, dx: float, dy: float) -> EllipseRoi:
        return self.model_copy(update={"x": self.x + dx, "y": self.y + dy})

    def area(self) -> float:
        return math.pi * self.width * self.height / 4

    def eccentricity(self) -> float:
        """Eccentricity of the ellipse."""
        return _utils.eccentricity(self.width / 2, self.height / 2)

    def bbox(self) -> Rect[float]:
        return Rect(self.x, self.y, self.width, self.height)

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        _yy, _xx = np.indices(shape[-2:])
        cx, cy = self.center()
        if self.height == 0 or self.width == 0:
            return np.zeros(shape, dtype=bool)
        comp_a = (_yy - cy) / self.height * 2
        comp_b = (_xx - cx) / self.width * 2
        return comp_a**2 + comp_b**2 <= 1
eccentricity()

Eccentricity of the ellipse.

Source code in src\himena\standards\roi\core.py
165
166
167
def eccentricity(self) -> float:
    """Eccentricity of the ellipse."""
    return _utils.eccentricity(self.width / 2, self.height / 2)

LineRoi

A 2D line ROI.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
start tuple[float, float]

(X, Y) coordinate of the start point.

required
end tuple[float, float]

(X, Y) coordinate of the end point.

required
Source code in src\himena\standards\roi\core.py
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
class LineRoi(Roi2D):
    """A 2D line ROI."""

    start: tuple[float, float] = Field(..., description="(X, Y) coordinate of the start point.")  # fmt: skip
    end: tuple[float, float] = Field(..., description="(X, Y) coordinate of the end point.")  # fmt: skip

    @property
    def x1(self) -> float:
        return self.start[0]

    @property
    def y1(self) -> float:
        return self.start[1]

    @property
    def x2(self) -> float:
        return self.end[0]

    @property
    def y2(self) -> float:
        return self.end[1]

    def shifted(self, dx: float, dy: float) -> LineRoi:
        """Shift the line by the given amount."""
        return LineRoi(
            start=(self.x1 + dx, self.y1 + dy),
            end=(self.x2 + dx, self.y2 + dy),
        )

    def length(self) -> float:
        """Length of the line."""
        dx = self.x2 - self.x1
        dy = self.y2 - self.y1
        return math.hypot(dx, dy)

    def angle(self) -> float:
        """Angle in degrees."""
        return math.degrees(self.angle_radian())

    def angle_radian(self) -> float:
        dx = self.x2 - self.x1
        dy = self.y1 - self.y2  # NOTE: invert y so that angle is CCW
        return math.atan2(dy, dx)

    def linspace(self, num: int) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
        """Return a tuple of x and y coordinates of np.linspace along the line."""
        return np.linspace(self.x1, self.x2, num), np.linspace(self.y1, self.y2, num)

    def arange(
        self, step: float = 1.0
    ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
        """Return a tuple of x and y coordinates of np.arange along the line."""
        radian = -self.angle_radian()
        num, rem = divmod(self.length(), step)
        xrem = rem * math.cos(radian)
        yrem = rem * math.sin(radian)
        return (
            np.linspace(self.x1, self.x2 - xrem, int(num) + 1),
            np.linspace(self.y1, self.y2 - yrem, int(num) + 1),
        )

    def bbox(self) -> Rect[float]:
        xmin, xmax = min(self.x1, self.x2), max(self.x1, self.x2)
        ymin, ymax = min(self.y1, self.y2), max(self.y1, self.y2)
        return Rect(xmin, ymin, xmax - xmin, ymax - ymin)

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        arr = np.zeros(shape, dtype=bool)
        xs, ys = self.linspace(int(self.length() + 1))
        xs = xs.round().astype(int)
        ys = ys.round().astype(int)
        arr[ys, xs] = True
        return arr
angle()

Angle in degrees.

Source code in src\himena\standards\roi\core.py
327
328
329
def angle(self) -> float:
    """Angle in degrees."""
    return math.degrees(self.angle_radian())
arange(step=1.0)

Return a tuple of x and y coordinates of np.arange along the line.

Source code in src\himena\standards\roi\core.py
340
341
342
343
344
345
346
347
348
349
350
351
def arange(
    self, step: float = 1.0
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
    """Return a tuple of x and y coordinates of np.arange along the line."""
    radian = -self.angle_radian()
    num, rem = divmod(self.length(), step)
    xrem = rem * math.cos(radian)
    yrem = rem * math.sin(radian)
    return (
        np.linspace(self.x1, self.x2 - xrem, int(num) + 1),
        np.linspace(self.y1, self.y2 - yrem, int(num) + 1),
    )
length()

Length of the line.

Source code in src\himena\standards\roi\core.py
321
322
323
324
325
def length(self) -> float:
    """Length of the line."""
    dx = self.x2 - self.x1
    dy = self.y2 - self.y1
    return math.hypot(dx, dy)
linspace(num)

Return a tuple of x and y coordinates of np.linspace along the line.

Source code in src\himena\standards\roi\core.py
336
337
338
def linspace(self, num: int) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
    """Return a tuple of x and y coordinates of np.linspace along the line."""
    return np.linspace(self.x1, self.x2, num), np.linspace(self.y1, self.y2, num)
shifted(dx, dy)

Shift the line by the given amount.

Source code in src\himena\standards\roi\core.py
314
315
316
317
318
319
def shifted(self, dx: float, dy: float) -> LineRoi:
    """Shift the line by the given amount."""
    return LineRoi(
        start=(self.x1 + dx, self.y1 + dy),
        end=(self.x2 + dx, self.y2 + dy),
    )

PointRoi1D

ROI that represents a point in 1D space.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
x float

X-coordinate of the point.

required
Source code in src\himena\standards\roi\core.py
28
29
30
31
32
33
34
class PointRoi1D(Roi1D):
    """ROI that represents a point in 1D space."""

    x: float = Field(..., description="X-coordinate of the point.")

    def shifted(self, dx: float) -> PointRoi1D:
        return PointRoi1D(x=self.x + dx)

PointRoi2D

ROI that represents a single point.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
x float

X-coordinate of the point.

required
y float

Y-coordinate of the point.

required
Source code in src\himena\standards\roi\core.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
class PointRoi2D(Roi2D):
    """ROI that represents a single point."""

    x: float = Field(..., description="X-coordinate of the point.")
    y: float = Field(..., description="Y-coordinate of the point.")

    def shifted(self, dx: float, dy: float) -> PointRoi2D:
        return self.model_copy(update={"x": self.x + dx, "y": self.y + dy})

    def bbox(self) -> Rect[float]:
        return Rect(self.x, self.y, 0, 0)

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        arr = np.zeros(shape, dtype=bool)
        arr[..., int(round(self.y)), int(round(self.x))] = True
        return arr

PointsRoi1D

ROI that represents a set of points in 1D space.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
xs Any

List of x-coordinates.

required
Source code in src\himena\standards\roi\core.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class PointsRoi1D(Roi1D):
    """ROI that represents a set of points in 1D space."""

    xs: Any = Field(..., description="List of x-coordinates.")

    @field_validator("xs")
    def _validate_np_array(cls, v) -> NDArray[np.number]:
        out = np.asarray(v)
        if out.dtype.kind not in "if":
            raise ValueError("Must be a numerical array.")
        return out

    def shifted(self, dx: float) -> PointsRoi1D:
        return PointsRoi1D(xs=self.xs + dx)

    def model_dump_typed(self) -> dict[str, Any]:
        out = super().model_dump_typed()
        out["xs"] = self.xs.tolist()
        return out

PointsRoi2D

ROI that represents a set of points.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
xs Any

List of x-coordinates.

required
ys Any

List of y-coordinates.

required
Source code in src\himena\standards\roi\core.py
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
class PointsRoi2D(Roi2D):
    """ROI that represents a set of points."""

    xs: Any = Field(..., description="List of x-coordinates.")
    ys: Any = Field(..., description="List of y-coordinates.")

    @field_validator("xs", "ys")
    def _validate_np_arrays(cls, v) -> NDArray[np.number]:
        out = np.asarray(v)
        if out.dtype.kind not in "if":
            raise ValueError("Must be a numerical array.")
        return out

    def model_dump_typed(self) -> dict[str, Any]:
        out = super().model_dump_typed()
        out["xs"] = self.xs.tolist()
        out["ys"] = self.ys.tolist()
        return out

    def shifted(self, dx: float, dy: float) -> PointsRoi2D:
        return self.model_copy(update={"xs": self.xs + dx, "ys": self.ys + dy})

    def bbox(self) -> Rect[float]:
        xmin, xmax = np.min(self.xs), np.max(self.xs)
        ymin, ymax = np.min(self.ys), np.max(self.ys)
        return Rect(xmin, ymin, xmax - xmin, ymax - ymin)

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        arr = np.zeros(shape, dtype=bool)
        xs = np.asarray(self.xs).round().astype(int)
        ys = np.asarray(self.ys).round().astype(int)
        arr[..., ys, xs] = True
        return arr

PolygonRoi

ROI that represents a closed polygon.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
xs Any

List of x-coordinates.

required
ys Any

List of y-coordinates.

required
Source code in src\himena\standards\roi\core.py
404
405
406
407
408
409
410
411
412
413
class PolygonRoi(SegmentedLineRoi):
    """ROI that represents a closed polygon."""

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        return _utils.polygon_mask(shape, np.column_stack((self.ys, self.xs)))

    def area(self) -> float:
        dot_xy = np.dot(self.xs, np.roll(self.ys, 1))
        dot_yx = np.dot(self.ys, np.roll(self.xs, 1))
        return np.abs(dot_xy - dot_yx) / 2

RectangleRoi

ROI that represents a rectangle.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
x int | float

X-coordinate of the top-left corner.

required
y int | float

Y-coordinate of the top-left corner.

required
width int | float

Width of the rectangle.

required
height int | float

Height of the rectangle.

required
Source code in src\himena\standards\roi\core.py
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
class RectangleRoi(Roi2D):
    """ROI that represents a rectangle."""

    x: Scalar = Field(..., description="X-coordinate of the top-left corner.")
    y: Scalar = Field(..., description="Y-coordinate of the top-left corner.")
    width: Scalar = Field(..., description="Width of the rectangle.")
    height: Scalar = Field(..., description="Height of the rectangle.")

    def shifted(self, dx: float, dy: float) -> RectangleRoi:
        """Return a new rectangle shifted by the given amount."""
        return self.model_copy(update={"x": self.x + dx, "y": self.y + dy})

    def area(self) -> float:
        """Return the area of the rectangle."""
        return self.width * self.height

    def bbox(self) -> Rect[float]:
        """Return the bounding box of the rectangle."""
        return Rect(self.x, self.y, self.width, self.height)

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        bb = self.bbox().adjust_to_int("inner")
        arr = np.zeros(shape, dtype=bool)
        arr[..., bb.top : bb.bottom, bb.left : bb.right] = True
        return arr
area()

Return the area of the rectangle.

Source code in src\himena\standards\roi\core.py
70
71
72
def area(self) -> float:
    """Return the area of the rectangle."""
    return self.width * self.height
bbox()

Return the bounding box of the rectangle.

Source code in src\himena\standards\roi\core.py
74
75
76
def bbox(self) -> Rect[float]:
    """Return the bounding box of the rectangle."""
    return Rect(self.x, self.y, self.width, self.height)
shifted(dx, dy)

Return a new rectangle shifted by the given amount.

Source code in src\himena\standards\roi\core.py
66
67
68
def shifted(self, dx: float, dy: float) -> RectangleRoi:
    """Return a new rectangle shifted by the given amount."""
    return self.model_copy(update={"x": self.x + dx, "y": self.y + dy})

Roi1D

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
Source code in src\himena\standards\roi\_base.py
56
57
58
59
class Roi1D(RoiModel):
    def shifted(self, dx: float) -> Self:
        """Return a new 1D ROI translated by the given amount."""
        raise NotImplementedError
shifted(dx)

Return a new 1D ROI translated by the given amount.

Source code in src\himena\standards\roi\_base.py
57
58
59
def shifted(self, dx: float) -> Self:
    """Return a new 1D ROI translated by the given amount."""
    raise NotImplementedError

Roi2D

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
Source code in src\himena\standards\roi\_base.py
62
63
64
65
66
67
68
69
class Roi2D(RoiModel):
    def bbox(self) -> Rect[float]:
        """Return the bounding box of the ROI."""
        raise NotImplementedError

    def shifted(self, dx: float, dy: float) -> Self:
        """Return a new 2D ROI translated by the given amount."""
        raise NotImplementedError
bbox()

Return the bounding box of the ROI.

Source code in src\himena\standards\roi\_base.py
63
64
65
def bbox(self) -> Rect[float]:
    """Return the bounding box of the ROI."""
    raise NotImplementedError
shifted(dx, dy)

Return a new 2D ROI translated by the given amount.

Source code in src\himena\standards\roi\_base.py
67
68
69
def shifted(self, dx: float, dy: float) -> Self:
    """Return a new 2D ROI translated by the given amount."""
    raise NotImplementedError

Roi3D

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
Source code in src\himena\standards\roi\_base.py
72
73
74
75
class Roi3D(RoiModel):
    def shifted(self, dx: float, dy: float, dz: float) -> Self:
        """Return a new 3D ROI translated by the given amount."""
        raise NotImplementedError
shifted(dx, dy, dz)

Return a new 3D ROI translated by the given amount.

Source code in src\himena\standards\roi\_base.py
73
74
75
def shifted(self, dx: float, dy: float, dz: float) -> Self:
    """Return a new 3D ROI translated by the given amount."""
    raise NotImplementedError

RoiListModel dataclass

List of ROIs, with useful methods.

Parameters:

Name Type Description Default
items NDArray[object_]
array([], dtype=object)
indices NDArray[int32]
array([], shape=(0, 0), dtype=int32)
axis_names list[str]

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

<dynamic>
Source code in src\himena\standards\roi\_list.py
 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
class RoiListModel(NDObjectCollection[RoiModel]):
    """List of ROIs, with useful methods."""

    def model_dump_typed(self) -> dict:
        return {
            "rois": [roi.model_dump_typed() for roi in self],
            "indices": self.indices.tolist() if self.indices is not None else None,
            "axis_names": self.axis_names,
        }

    @classmethod
    def construct(cls, dict_: dict) -> RoiListModel:
        """Construct an instance from a dictionary."""
        rois = []
        for roi_dict in dict_["rois"]:
            if not isinstance(roi_dict, dict):
                raise ValueError(f"Expected a dictionary for 'rois', got: {roi_dict!r}")
            roi_type = roi_dict.pop("type")
            roi = RoiModel.construct(roi_type, roi_dict)
            rois.append(roi)
        return cls(
            items=rois,
            indices=np.array(dict_["indices"], dtype=np.int32),
            axis_names=dict_["axis_names"],
        )

    @classmethod
    def model_validate_json(cls, text: str) -> RoiListModel:
        """Validate the json string and return an instance."""
        js = json.loads(text)
        return cls.construct(js)
construct(dict_) classmethod

Construct an instance from a dictionary.

Source code in src\himena\standards\roi\_list.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@classmethod
def construct(cls, dict_: dict) -> RoiListModel:
    """Construct an instance from a dictionary."""
    rois = []
    for roi_dict in dict_["rois"]:
        if not isinstance(roi_dict, dict):
            raise ValueError(f"Expected a dictionary for 'rois', got: {roi_dict!r}")
        roi_type = roi_dict.pop("type")
        roi = RoiModel.construct(roi_type, roi_dict)
        rois.append(roi)
    return cls(
        items=rois,
        indices=np.array(dict_["indices"], dtype=np.int32),
        axis_names=dict_["axis_names"],
    )
model_validate_json(text) classmethod

Validate the json string and return an instance.

Source code in src\himena\standards\roi\_list.py
35
36
37
38
39
@classmethod
def model_validate_json(cls, text: str) -> RoiListModel:
    """Validate the json string and return an instance."""
    js = json.loads(text)
    return cls.construct(js)

RoiModel

Base class for ROIs (Region of Interest) in images.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
Source code in src\himena\standards\roi\_base.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class RoiModel(BaseModel):
    """Base class for ROIs (Region of Interest) in images."""

    name: str | None = Field(None, description="Name of the ROI.")

    def model_dump_typed(self) -> dict:
        return {
            "type": _strip_roi_suffix(type(self).__name__.lower()),
            **self.model_dump(),
        }

    @classmethod
    def construct(cls, typ: str, dict_: dict) -> RoiModel:
        """Construct an instance from a dictionary."""
        model_type = pick_roi_model(typ)
        return model_type.model_validate(dict_)

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        raise NotImplementedError
construct(typ, dict_) classmethod

Construct an instance from a dictionary.

Source code in src\himena\standards\roi\_base.py
26
27
28
29
30
@classmethod
def construct(cls, typ: str, dict_: dict) -> RoiModel:
    """Construct an instance from a dictionary."""
    model_type = pick_roi_model(typ)
    return model_type.model_validate(dict_)

RotatedEllipseRoi

ROI that represents a rotated ellipse.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
start tuple[float, float]

(X, Y) coordinate of the start point.

required
end tuple[float, float]

(X, Y) coordinate of the end point.

required
width float

Width of the ROI.

required
Source code in src\himena\standards\roi\core.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
class RotatedEllipseRoi(RotatedRoi2D):
    """ROI that represents a rotated ellipse."""

    def area(self) -> float:
        return self.length() * self.width * math.pi / 4

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        _yy, _xx = np.indices(shape[-2:])
        start_x, start_y = self.start
        end_x, end_y = self.end
        length = math.hypot(end_x - start_x, end_y - start_y)
        cx, cy = (start_x + end_x) / 2, (start_y + end_y) / 2
        angle = self.angle_radian()
        comp_a = (_yy - cy) / length * 2
        comp_b = (_xx - cx) / self.width * 2
        comp_a, comp_b = (
            comp_a * math.cos(angle) - comp_b * math.sin(angle),
            comp_a * math.sin(angle) + comp_b * math.cos(angle),
        )
        return comp_a**2 + comp_b**2 <= 1

    def eccentricity(self) -> float:
        """Eccentricity of the ellipse."""
        return _utils.eccentricity(self.length() / 2, self.width / 2)
eccentricity()

Eccentricity of the ellipse.

Source code in src\himena\standards\roi\core.py
203
204
205
def eccentricity(self) -> float:
    """Eccentricity of the ellipse."""
    return _utils.eccentricity(self.length() / 2, self.width / 2)

RotatedRectangleRoi

ROI that represents a rotated rectangle.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
start tuple[float, float]

(X, Y) coordinate of the start point.

required
end tuple[float, float]

(X, Y) coordinate of the end point.

required
width float

Width of the ROI.

required
Source code in src\himena\standards\roi\core.py
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
class RotatedRectangleRoi(RotatedRoi2D):
    """ROI that represents a rotated rectangle."""

    def area(self) -> float:
        return self.length() * self.width

    def bbox(self) -> Rect[float]:
        p00, p01, p11, p10 = self._get_vertices()
        xmin = min(p00[0], p01[0], p10[0], p11[0])
        xmax = max(p00[0], p01[0], p10[0], p11[0])
        ymin = min(p00[1], p01[1], p10[1], p11[1])
        ymax = max(p00[1], p01[1], p10[1], p11[1])
        return Rect(xmin, ymin, xmax - xmin, ymax - ymin)

    def _get_vertices(self):
        start_x, start_y = self.start
        end_x, end_y = self.end
        vx, vy = self._get_vx_vy()
        center = np.array([start_x + end_x, start_y + end_y]) / 2
        p00 = center - vx / 2 - vy / 2
        p01 = center - vx / 2 + vy / 2
        p10 = center + vx / 2 - vy / 2
        p11 = center + vx / 2 + vy / 2
        return p00, p01, p11, p10

    def to_mask(self, shape: tuple[int, ...]):
        vertices = np.stack(self._get_vertices(), axis=0)
        return _utils.polygon_mask(shape, vertices[:, ::-1])

SegmentedLineRoi

ROI that represents a segmented line.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
xs Any

List of x-coordinates.

required
ys Any

List of y-coordinates.

required
Source code in src\himena\standards\roi\core.py
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
class SegmentedLineRoi(PointsRoi2D):
    """ROI that represents a segmented line."""

    def length(self) -> np.float64:
        return np.sum(self.lengths())

    def lengths(self) -> NDArray[np.float64]:
        return np.hypot(np.diff(self.xs), np.diff(self.ys))

    def linspace(self, num: int) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
        """Return a tuple of x and y coordinates of np.linspace along the line."""
        tnots = np.cumsum(np.concatenate([[0], self.lengths()], dtype=np.float64))
        teval = np.linspace(0, tnots[-1], num)
        xi = np.interp(teval, tnots, self.xs)
        yi = np.interp(teval, tnots, self.ys)
        return xi, yi

    def arange(
        self, step: float = 1.0
    ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
        tnots = np.cumsum(np.concatenate([[0], self.lengths()], dtype=np.float64))
        length = tnots[-1]
        num, rem = divmod(length, step)
        teval = np.linspace(0, length - rem, int(num + 1))
        xi = np.interp(teval, tnots, self.xs)
        yi = np.interp(teval, tnots, self.ys)
        return xi, yi

    def to_mask(self, shape: tuple[int, ...]) -> NDArray[np.bool_]:
        arr = np.zeros(shape, dtype=bool)
        xs, ys = self.linspace(int(math.ceil(self.length())))
        xs = xs.round().astype(int)
        ys = ys.round().astype(int)
        arr[ys, xs] = True
        return arr
linspace(num)

Return a tuple of x and y coordinates of np.linspace along the line.

Source code in src\himena\standards\roi\core.py
376
377
378
379
380
381
382
def linspace(self, num: int) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
    """Return a tuple of x and y coordinates of np.linspace along the line."""
    tnots = np.cumsum(np.concatenate([[0], self.lengths()], dtype=np.float64))
    teval = np.linspace(0, tnots[-1], num)
    xi = np.interp(teval, tnots, self.xs)
    yi = np.interp(teval, tnots, self.ys)
    return xi, yi

SpanRoi

ROI that represents a span in 1D space.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
start float

Start of the span.

required
end float

End of the span.

required
Source code in src\himena\standards\roi\core.py
15
16
17
18
19
20
21
22
23
24
25
class SpanRoi(Roi1D):
    """ROI that represents a span in 1D space."""

    start: float = Field(..., description="Start of the span.")
    end: float = Field(..., description="End of the span.")

    def shifted(self, dx: float) -> SpanRoi:
        return SpanRoi(start=self.start + dx, end=self.end + dx)

    def width(self) -> float:
        return self.end - self.start

SplineRoi

ROI that represents a spline curve.

Parameters:

Name Type Description Default
name str | None

Name of the ROI.

None
degree int

Degree of the spline curve.

3
Source code in src\himena\standards\roi\core.py
416
417
418
419
class SplineRoi(Roi2D):
    """ROI that represents a spline curve."""

    degree: int = Field(3, description="Degree of the spline curve.", ge=1)

default_roi_label(nth)

Return a default label for the n-th ROI.

Source code in src\himena\standards\roi\_base.py
51
52
53
def default_roi_label(nth: int) -> str:
    """Return a default label for the n-th ROI."""
    return f"ROI-{nth}"

pick_roi_model(typ) cached

Pick an ROI model class from the given type string

Source code in src\himena\standards\roi\_base.py
36
37
38
39
40
41
42
@cache
def pick_roi_model(typ: str) -> type[RoiModel]:
    """Pick an ROI model class from the given type string"""
    for sub in iter_subclasses(RoiModel):
        if _strip_roi_suffix(sub.__name__.lower()) == typ:
            return sub
    raise ValueError(f"Unknown ROI type: {typ!r}")