Skip to content

Classes Modules

Define custom classes for TopoStats.

DisorderedTrace

Dataclass for storing the disordered tracing data.

Attributes:

Name Type Description
images dict[str:(NDArray)]

Dictionary of images generated during disordered tracing, should include ''pruned_skeleton''.

grain_endpoints int

Number of Grain endpoints.

grain_junctions int

Number of Grain junctions.

total_branch_length float

Total branch length in nanometres.

grain_width_mean float

Mean grain width in nanometres.

stats dict[int, Any]

Dictionary of stats for each branch of a grain.

Source code in topostats\classes.py
@dataclass(
    repr=True, eq=True, config=ConfigDict(arbitrary_types_allowed=True, validate_assignment=True), validate_on_init=True
)
class DisorderedTrace:
    """
    Dataclass for storing the disordered tracing data.

    Attributes
    ----------
    images : dict[str: npt.NDArray]
        Dictionary of images generated during disordered tracing, should include ''pruned_skeleton''.
    grain_endpoints : int
        Number of Grain endpoints.
    grain_junctions : int
        Number of Grain junctions.
    total_branch_length : float
        Total branch length in nanometres.
    grain_width_mean : float
        Mean grain width in nanometres.
    stats : dict[int, Any]
        Dictionary of stats for each branch of a grain.
    """

    images: dict[str, npt.NDArray] | None = None
    grain_endpoints: int | None = None
    grain_junctions: int | None = None
    total_branch_length: float | None = None
    grain_width_mean: float | None = None
    stats: dict[int, Any] | None = None

    def __str__(self) -> str:
        """
        Representation function for useful statistics for the class.

        Returns
        -------
        str
            Set of formatted statistics on matched branches.
        """
        image_gens = ", ".join(self.images.keys())
        return (
            f"generated images : {image_gens}\n"
            f"grain endpoints : {self.grain_endpoints}\n"
            f"grain junctions : {self.grain_junctions}\n"
            f"total branch length (nm) : {self.total_branch_length}\n"
            f"mean grain width (nm) : {self.grain_width_mean}\n"
            f"number of branches : {len(self.stats)}"
        )

__str__() -> str

Representation function for useful statistics for the class.

Returns:

Type Description
str

Set of formatted statistics on matched branches.

Source code in topostats\classes.py
def __str__(self) -> str:
    """
    Representation function for useful statistics for the class.

    Returns
    -------
    str
        Set of formatted statistics on matched branches.
    """
    image_gens = ", ".join(self.images.keys())
    return (
        f"generated images : {image_gens}\n"
        f"grain endpoints : {self.grain_endpoints}\n"
        f"grain junctions : {self.grain_junctions}\n"
        f"total branch length (nm) : {self.total_branch_length}\n"
        f"mean grain width (nm) : {self.grain_width_mean}\n"
        f"number of branches : {len(self.stats)}"
    )

GrainCrop

Class for storing the crops of grains.

Parameters:

Name Type Description Default
image NDArray[float32]

2-D Numpy array of the cropped image.

required
mask NDArray[bool_]

3-D Numpy tensor of the cropped mask.

required
padding int

Padding added to the bounding box of the grain during cropping.

required
bbox tuple[int, int, int, int]

Bounding box of the crop including padding.

required
pixel_to_nm_scaling float

Pixel to nanometre scaling factor for the crop.

required
thresholds list[float]

Thresholds used to find the grain.

required
filename str

Filename of the image from which the crop was taken.

required
threshold str

Direction of the molecule from the threshold (above / below).

None
skeleton NDArray[bool_]

3-D Numpy tensor of the skeletonised mask.

None
convolved_skeleton npt.NDArray[np.int32] | None = None

2-D Numpy array of the convolved skeleton.

None
height_profiles dict[int, dict[int, NDArray[float32]]] | None

Nested dictionary height profiles.

None
stats dict[int, dict[int, Any]] | None

Dictionary of grain statistics.

None
disordered_trace DisorderedTrace

A disordered trace for the grain.

None
nodes dict[str, Node]

Dictionary of grain nodes.

None
ordered_trace OrderedTrace

An ordered trace for the grain.

None
threshold_method str

Threshold method used to find grains.

None
Source code in topostats\classes.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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
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
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
class GrainCrop:
    """
    Class for storing the crops of grains.

    Parameters
    ----------
    image : npt.NDArray[np.float32]
        2-D Numpy array of the cropped image.
    mask : npt.NDArray[np.bool_]
        3-D Numpy tensor of the cropped mask.
    padding : int
        Padding added to the bounding box of the grain during cropping.
    bbox : tuple[int, int, int, int]
        Bounding box of the crop including padding.
    pixel_to_nm_scaling : float
        Pixel to nanometre scaling factor for the crop.
    thresholds : list[float]
        Thresholds used to find the grain.
    filename : str
        Filename of the image from which the crop was taken.
    threshold : str
        Direction of the molecule from the threshold (above / below).
    skeleton : npt.NDArray[np.bool_]
        3-D Numpy tensor of the skeletonised mask.
    convolved_skeleton : npt.NDArray[np.int32] | None = None
        2-D Numpy array of the convolved skeleton.
    height_profiles : dict[int, dict[int, npt.NDArray[np.float32]]] | None
        Nested dictionary  height profiles.
    stats : dict[int, dict[int, Any]] | None
        Dictionary of grain statistics.
    disordered_trace : DisorderedTrace
        A disordered trace for the grain.
    nodes : dict[str, Node]
        Dictionary of grain nodes.
    ordered_trace : OrderedTrace
        An ordered trace for the grain.
    threshold_method : str
        Threshold method used to find grains.
    """

    def __init__(  # pylint: disable=too-many-locals
        self,
        image: npt.NDArray[np.float32],
        mask: npt.NDArray[np.bool_],
        padding: int,
        bbox: tuple[int, int, int, int],
        pixel_to_nm_scaling: float,
        thresholds: list[float],
        filename: str,
        threshold: str | None = None,
        skeleton: npt.NDArray[np.bool_] | None = None,
        convolved_skeleton: npt.NDArray[np.int32] | None = None,
        height_profiles: dict[int, dict[int, npt.NDArray[np.float32]]] | None = None,
        stats: dict[int, dict[int, Any]] | None = None,
        disordered_trace: DisorderedTrace | None = None,
        nodes: dict[str, Node] | None = None,
        ordered_trace: OrderedTrace | None = None,
        threshold_method: str | None = None,
    ):
        """
        Initialise the class.

        Parameters
        ----------
        image : npt.NDArray[np.float32]
            2-D Numpy array of the cropped image.
        mask : npt.NDArray[np.bool_]
            3-D Numpy tensor of the cropped mask.
        padding : int
            Padding added to the bounding box of the grain during cropping.
        bbox : tuple[int, int, int, int]
            Bounding box of the crop including padding.
        pixel_to_nm_scaling : float
            Pixel to nanometre scaling factor for the crop.
        thresholds : list[float]
            A list of thresholds used to identify the grain.
        filename : str
            Filename of the image from which the crop was taken.
        threshold : str
            Direction of the molecule from the threshold (above / below).
        skeleton : npt.NDArray[np.bool_]
            3-D Numpy tensor of the skeletonised mask.
        convolved_skeleton : npt.NDArray[np.int32] | None = None
            2-D Numpy array of the convolved skeleton.
        height_profiles : dict[int, dict[int, npt.NDArray[np.float32]]] | None
            3-D Numpy tensor of the height profiles.
        stats : dict[str, int | float] | None
            Dictionary of grain statistics.
        disordered_trace : DisorderedTrace
            A disordered trace for the current grain.
        nodes : dict[int, Node] | None
            Grain nodes.
        ordered_trace : OrderedTrace | None
            An ordered trace for the grain.
        threshold_method : str
            Threshold method used to find grains.
        """
        self.padding = padding
        self.image = image
        # This part of the constructor must go after padding since the setter
        # for mask requires the padding.
        self.mask = mask
        self.bbox = bbox
        self.pixel_to_nm_scaling = pixel_to_nm_scaling
        self.thresholds = thresholds
        self.filename = filename
        self.threshold: str | None = threshold
        self.height_profiles: dict[int, dict[int, npt.NDArray[np.float32]]] | None = height_profiles
        self.stats: dict[str, Any] | None = {} if stats is None else stats
        self.skeleton: npt.NDArray[np.bool_] | None = skeleton
        self.convolved_skeleton: npt.NDArray[np.int32] | None = convolved_skeleton
        self.disordered_trace: DisorderedTrace | None = disordered_trace
        self.nodes: dict[int, Node] | None = nodes
        self.ordered_trace: OrderedTrace | None = ordered_trace
        self.threshold_method: str | None = threshold_method

    def __eq__(self, other: object) -> bool:
        """
        Check if two GrainCrop objects are equal.

        Parameters
        ----------
        other : object
            Object to compare to.

        Returns
        -------
        bool
            True if the objects are equal, False otherwise.
        """
        if not isinstance(other, GrainCrop):
            return False
        return (
            np.array_equal(self.image, other.image)
            and np.array_equal(self.mask, other.mask)
            and self.padding == other.padding
            and self.bbox == other.bbox
            and self.pixel_to_nm_scaling == other.pixel_to_nm_scaling
            and self.thresholds == other.thresholds
            and self.filename == other.filename
            and self.threshold == other.threshold
            and self.height_profiles == other.height_profiles
            and self.stats == other.stats
            and np.array_equal(self.skeleton, other.skeleton)
            and self.convolved_skeleton == other.convolved_skeleton
            and self.disordered_trace == other.disordered_trace
            and self.nodes == other.nodes
            and self.ordered_trace == other.ordered_trace
            and self.threshold_method == other.threshold_method
        )

    def __str__(self) -> str:
        """
        Representation function for useful statistics for the class.

        Returns
        -------
        str
            Set of formatted statistics on matched branches.
        """
        return (
            f"filename : {self.filename}\n"
            f"image shape : {self.image.shape}\n"
            f"skeleton shape : {self.skeleton.shape}\n"
            f"mask shape : {self.mask.shape}\n"
            f"padding : {self.padding}\n"
            f"thresholds : {self.thresholds}\n"
            f"threshold method : {self.threshold_method}\n"
            f"bounding box coords : {self.bbox}\n"
            f"pixel to nm scaling : {self.pixel_to_nm_scaling}\n"
            f"number of nodes : {len(self.nodes)}"
        )

    @property
    def image(self) -> npt.NDArray[float]:
        """
        Getter for the ``image`` attribute.

        Returns
        -------
        npt.NDArray
            Numpy array of the image.
        """
        return self._image

    @image.setter
    def image(self, value: npt.NDArray[float]):
        """
        Setter for the ``image`` attribute.

        Parameters
        ----------
        value : npt.NDArray
            Numpy array of the image.

        Raises
        ------
        ValueError
            If the image is not square.
        """
        if value.shape[0] != value.shape[1]:
            raise ValueError(f"Image is not square: {value.shape}")
        self._image = value

    @property
    def mask(self) -> npt.NDArray[np.bool_]:
        """
        Getter for the ``mask`` attribute.

        Returns
        -------
        npt.NDArray[np.bool_]
            Numpy array of the mask.
        """
        return self._mask

    @mask.setter
    def mask(self, value: npt.NDArray[np.bool_]) -> None:
        """
        Setter for the ``mask`` attribute.

        Parameters
        ----------
        value : npt.NDArray
            Numpy array of the ``mask`` attribute.

        Raises
        ------
        ValueError
            If the mask dimensions do not match the image.
        """
        if value.shape[0] != self.image.shape[0] or value.shape[1] != self.image.shape[1]:
            raise ValueError(f"Mask dimensions do not match image: {value.shape} vs {self.image.shape}")
        # Ensure that the padding region is blank, set it to be blank if not
        try:
            for class_index in range(1, value.shape[2]):
                class_mask = value[:, :, class_index]

                padded_region_top = class_mask[: self.padding, :]
                padded_region_bottom = class_mask[-self.padding :, :]
                padded_region_left = class_mask[:, : self.padding]
                padded_region_right = class_mask[:, -self.padding :]
                if (
                    np.any(padded_region_top)
                    or np.any(padded_region_bottom)
                    or np.any(padded_region_left)
                    or np.any(padded_region_right)
                ):
                    LOGGER.warning("Padding region is not blank, setting to blank")
                    value[: self.padding, :, class_index] = 0
                    value[-self.padding :, :, class_index] = 0
                    value[:, : self.padding, class_index] = 0
                    value[:, -self.padding :, class_index] = 0
        except IndexError as e:
            LOGGER.error(f"[{self.filename}] : Error mask is missing layers.", exc_info=e)

        # Update background class in case the mask has been edited
        value = update_background_class(value)
        self._mask: npt.NDArray[np.bool_] = value

    @property
    def padding(self) -> int:
        """
        Getter for the ``padding`` attribute.

        Returns
        -------
        int
            The padding amount.
        """
        return self._padding

    @padding.setter
    def padding(self, value: int) -> None:
        """
        Setter for the ``padding`` attribute.

        Parameters
        ----------
        value : int
            Padding amount.

        Raises
        ------
        ValueError
            If the padding is not an integer or is less than 1.
        """
        if not isinstance(value, int):
            raise ValueError(f"Padding must be an integer, but is {value}")
        if value < 1:
            raise ValueError(f"Padding must be >= 1, but is {value}")
        self._padding = value

    @property
    def bbox(self) -> tuple[int, int, int, int]:
        """
        Getter for the bounding box attribute.

        Returns
        -------
        tuple
            Bounding box of the crop.

        Raises
        ------
        ValueError
            If the bounding box is not square.
        """
        return self._bbox

    @bbox.setter
    def bbox(self, value: tuple[int, int, int, int]) -> None:
        """
        Setter for the bounding box attribute.

        Parameters
        ----------
        value : tuple[int, int, int, int]
            Bounding box of the crop.
        """
        if len(value) != 4:
            raise ValueError(f"Bounding box must have 4 elements, but has {len(value)}")
        if value[2] - value[0] != value[3] - value[1]:
            raise ValueError(
                f"Bounding box is not square: {value}, size: {value[2] - value[0]} x {value[3] - value[1]}"
            )
        self._bbox = value

    @property
    def pixel_to_nm_scaling(self) -> float:
        """
        Getter for the pixel to nanometre scaling factor attribute.

        Returns
        -------
        float
            Pixel to nanometre scaling factor.
        """
        return self._pixel_to_nm_scaling

    @pixel_to_nm_scaling.setter
    def pixel_to_nm_scaling(self, value: float) -> None:
        """
        Setter for the pixel to nanometre scaling factor attribute.

        Parameters
        ----------
        value : float
            Pixel to nanometre scaling factor.
        """
        self._pixel_to_nm_scaling = value

    @property
    def thresholds(self) -> list[float]:
        """
        Getter for the ``thresholds`` attribute.

        Returns
        -------
        list[float]
            Returns the value of ``thresholds``.
        """
        return self._thresholds

    @thresholds.setter
    def thresholds(self, value: list[float]) -> None:
        """
        Setter for the ``thresholds`` attribute.

        Parameters
        ----------
        value : list[float]
            Value to set for ``thresholds``.
        """
        self._thresholds = value

    @property
    def filename(self) -> str:
        """
        Getter for the ``filename`` attribute.

        Returns
        -------
        str
            The image ``filename`` attribute.
        """
        return self._filename

    @filename.setter
    def filename(self, value: str) -> None:
        """
        Setter for the ``filename`` attribute.

        Parameters
        ----------
        value : str
            Image ``filename`` attribute.
        """
        self._filename = value

    @property
    def skeleton(self) -> npt.NDArray:
        """
        Getter for the ``skeleton`` attribute.

        Returns
        -------
        npt.NDArray
            Returns the value of ``skeleton``.
        """
        return self._skeleton

    @skeleton.setter
    def skeleton(self, value: npt.NDArray) -> None:
        """
        Setter for the ``skeleton`` attribute.

        Parameters
        ----------
        value : npt.NDArray
            Value to set for ``skeleton``.
        """
        self._skeleton = value

    @property
    def height_profiles(self) -> npt.NDArray:
        """
        Getter for the ``height_profile`` attribute.

        Returns
        -------
        str
            The image height_profile.
        """
        return self._height_profiles

    @height_profiles.setter
    def height_profiles(self, value: npt.NDArray) -> None:
        """
        Setter for the ``height_profile`` attribute.

        Parameters
        ----------
        value : str
            Image ``height_profile``.
        """
        self._height_profiles = value

    @property
    def stats(self) -> dict[str, Any]:
        """
        Getter for the stats.

        Returns
        -------
        str
            Dictionary of image statistics.
        """
        return self._stats

    @stats.setter
    def stats(self, value: dict[str, Any]) -> None:
        """
        Setter for the stats.

        Parameters
        ----------
        value : dict[str, Any]
            Image stats.
        """
        self._stats = value

    @property
    def disordered_trace(self) -> DisorderedTrace:
        """
        Getter for the ``disordered_trace`` attribute.

        Returns
        -------
        dict[str: Any]
            Returns the value of ``disordered_trace``.
        """
        return self._disordered_trace

    @disordered_trace.setter
    def disordered_trace(self, value: DisorderedTrace) -> None:
        """
        Setter for the ``disordered_trace`` attribute.

        Parameters
        ----------
        value : dict[str: Any]
            Value to set for ``disordered_trace``.
        """
        self._disordered_trace = value

    @property
    def nodes(self) -> dict[int, Node]:
        """
        Getter for the ``nodes`` attribute.

        Returns
        -------
        dict[int, Nodes]
            Returns ``nodes``, a dictionary of Nodes.
        """
        return self._nodes

    @nodes.setter
    def nodes(self, value: Node) -> None:
        """
        Setter for the ``nodes`` attribute.

        Parameters
        ----------
        value : Nodes
            Value to set for ``nodes``.
        """
        self._nodes = value

    @property
    def ordered_trace(self) -> OrderedTrace:
        """
        Getter for the ``ordered_trace`` attribute.

        Returns
        -------
        dict[str: Any]
            Returns the value of ``ordered_trace``.
        """
        return self._ordered_trace

    @ordered_trace.setter
    def ordered_trace(self, value: OrderedTrace) -> None:
        """
        Setter for the ``ordered_trace`` attribute.

        Parameters
        ----------
        value : dict[str: Any]
            Value to set for ``ordered_trace``.
        """
        self._ordered_trace = value

    @property
    def threshold_method(self) -> list[float]:
        """
        The ``threshold_method`` used to find the grain.

        Returns
        -------
        list[float]
            Returns the value of ``threshold_method``.
        """
        return self._threshold_method

    @threshold_method.setter
    def threshold_method(self, value: list[float]) -> None:
        """
        Setter for the ``threshold_method`` attribute.

        Parameters
        ----------
        value : list[float]
            Value to set for ``threshold_method``.
        """
        self._threshold_method = value

    def debug_locate_difference(self, other: object) -> None:  # noqa: C901 # pylint: disable=too-many-branches
        """
        Debug function to find the culprit when two GrainCrop objects are not equal.

        Parameters
        ----------
        other : object
            Object to compare to.

        Raises
        ------
        ValueError
            If the objects are not equal
        """
        if not isinstance(other, GrainCrop):
            raise ValueError(f"Cannot compare GrainCrop with {type(other)}")
        if not np.array_equal(self.image, other.image):
            raise ValueError(f"Image is different\n self.image  : {self.image}\n other.image : {other.image}")
        if not np.array_equal(self.mask, other.mask):
            raise ValueError(f"Mask is different\n self.mask  : {self.mask}\n other.mask : {other.mask}")
        if self.padding != other.padding:
            raise ValueError(f"Padding is different\n self.padding  : {self.padding}\n other.padding : {other.padding}")
        if self.bbox != other.bbox:
            raise ValueError(f"Bounding box is different\n self.bbox  : {self.bbox}\n other.bbox : {other.bbox}")
        if self.pixel_to_nm_scaling != other.pixel_to_nm_scaling:
            raise ValueError(
                "Pixel to nm scaling is different\n"
                f" self.pixel_to_nm_scaling  : {self.pixel_to_nm_scaling}\n"
                f" other.pixel_to_nm_scaling : {other.pixel_to_nm_scaling}"
            )
        if self.thresholds != other.thresholds:
            raise ValueError(
                f"Thresholds differ\n self.thresholds  : {self.thresholds}\n other.thresholds : {other.thresholds}"
            )
        if self.filename != other.filename:
            raise ValueError(
                f"Filename is different\n self.filename  : {self.filename}\n other.filename : {other.filename}"
            )
        if self.height_profiles != other.height_profiles:
            raise ValueError(
                "Height profiles are different\n"
                f" self.height_profiles  : {self.height_profiles}\n"
                f" other.height_profiles : {other.height_profiles}"
            )
        if self.skeleton != other.skeleton:
            raise ValueError(
                f"Skeleton is different\n self.skeleton  : {self.skeleton}\n other.skeleton : {other.skeleton}"
            )
        if self.disordered_trace != other.disordered_trace:
            raise ValueError(
                "Disordered traces are different\n"
                f" self.disordered_trace  : {self.disordered_trace}\n"
                f" other.disordered_trace : {other.disordered_trace}"
            )
        if self.nodes != other.nodes:
            raise ValueError(f"Nodes are different\n self.nodes  : {self.nodes}\n other.nodes : {other.nodes}")
        if self.threshold_method != other.threshold_method:
            raise ValueError(
                "Threshold Method is different\n"
                f" self.threshold_method  : {self.threshold_method}\n"
                f" other.threshold_method : {other.threshold_method}"
            )
        LOGGER.info("Cannot find difference between graincrops")

bbox: tuple[int, int, int, int] property writable

Getter for the bounding box attribute.

Returns:

Type Description
tuple

Bounding box of the crop.

Raises:

Type Description
ValueError

If the bounding box is not square.

disordered_trace: DisorderedTrace property writable

Getter for the disordered_trace attribute.

Returns:

Type Description
dict[str:Any]

Returns the value of disordered_trace.

filename: str property writable

Getter for the filename attribute.

Returns:

Type Description
str

The image filename attribute.

height_profiles: npt.NDArray property writable

Getter for the height_profile attribute.

Returns:

Type Description
str

The image height_profile.

image: npt.NDArray[float] property writable

Getter for the image attribute.

Returns:

Type Description
NDArray

Numpy array of the image.

mask: npt.NDArray[np.bool_] property writable

Getter for the mask attribute.

Returns:

Type Description
NDArray[bool_]

Numpy array of the mask.

nodes: dict[int, Node] property writable

Getter for the nodes attribute.

Returns:

Type Description
dict[int, Nodes]

Returns nodes, a dictionary of Nodes.

ordered_trace: OrderedTrace property writable

Getter for the ordered_trace attribute.

Returns:

Type Description
dict[str:Any]

Returns the value of ordered_trace.

padding: int property writable

Getter for the padding attribute.

Returns:

Type Description
int

The padding amount.

pixel_to_nm_scaling: float property writable

Getter for the pixel to nanometre scaling factor attribute.

Returns:

Type Description
float

Pixel to nanometre scaling factor.

skeleton: npt.NDArray property writable

Getter for the skeleton attribute.

Returns:

Type Description
NDArray

Returns the value of skeleton.

stats: dict[str, Any] property writable

Getter for the stats.

Returns:

Type Description
str

Dictionary of image statistics.

threshold_method: list[float] property writable

The threshold_method used to find the grain.

Returns:

Type Description
list[float]

Returns the value of threshold_method.

thresholds: list[float] property writable

Getter for the thresholds attribute.

Returns:

Type Description
list[float]

Returns the value of thresholds.

__eq__(other: object) -> bool

Check if two GrainCrop objects are equal.

Parameters:

Name Type Description Default
other object

Object to compare to.

required

Returns:

Type Description
bool

True if the objects are equal, False otherwise.

Source code in topostats\classes.py
def __eq__(self, other: object) -> bool:
    """
    Check if two GrainCrop objects are equal.

    Parameters
    ----------
    other : object
        Object to compare to.

    Returns
    -------
    bool
        True if the objects are equal, False otherwise.
    """
    if not isinstance(other, GrainCrop):
        return False
    return (
        np.array_equal(self.image, other.image)
        and np.array_equal(self.mask, other.mask)
        and self.padding == other.padding
        and self.bbox == other.bbox
        and self.pixel_to_nm_scaling == other.pixel_to_nm_scaling
        and self.thresholds == other.thresholds
        and self.filename == other.filename
        and self.threshold == other.threshold
        and self.height_profiles == other.height_profiles
        and self.stats == other.stats
        and np.array_equal(self.skeleton, other.skeleton)
        and self.convolved_skeleton == other.convolved_skeleton
        and self.disordered_trace == other.disordered_trace
        and self.nodes == other.nodes
        and self.ordered_trace == other.ordered_trace
        and self.threshold_method == other.threshold_method
    )

__init__(image: npt.NDArray[np.float32], mask: npt.NDArray[np.bool_], padding: int, bbox: tuple[int, int, int, int], pixel_to_nm_scaling: float, thresholds: list[float], filename: str, threshold: str | None = None, skeleton: npt.NDArray[np.bool_] | None = None, convolved_skeleton: npt.NDArray[np.int32] | None = None, height_profiles: dict[int, dict[int, npt.NDArray[np.float32]]] | None = None, stats: dict[int, dict[int, Any]] | None = None, disordered_trace: DisorderedTrace | None = None, nodes: dict[str, Node] | None = None, ordered_trace: OrderedTrace | None = None, threshold_method: str | None = None)

Initialise the class.

Parameters:

Name Type Description Default
image NDArray[float32]

2-D Numpy array of the cropped image.

required
mask NDArray[bool_]

3-D Numpy tensor of the cropped mask.

required
padding int

Padding added to the bounding box of the grain during cropping.

required
bbox tuple[int, int, int, int]

Bounding box of the crop including padding.

required
pixel_to_nm_scaling float

Pixel to nanometre scaling factor for the crop.

required
thresholds list[float]

A list of thresholds used to identify the grain.

required
filename str

Filename of the image from which the crop was taken.

required
threshold str

Direction of the molecule from the threshold (above / below).

None
skeleton NDArray[bool_]

3-D Numpy tensor of the skeletonised mask.

None
convolved_skeleton npt.NDArray[np.int32] | None = None

2-D Numpy array of the convolved skeleton.

None
height_profiles dict[int, dict[int, NDArray[float32]]] | None

3-D Numpy tensor of the height profiles.

None
stats dict[str, int | float] | None

Dictionary of grain statistics.

None
disordered_trace DisorderedTrace

A disordered trace for the current grain.

None
nodes dict[int, Node] | None

Grain nodes.

None
ordered_trace OrderedTrace | None

An ordered trace for the grain.

None
threshold_method str

Threshold method used to find grains.

None
Source code in topostats\classes.py
def __init__(  # pylint: disable=too-many-locals
    self,
    image: npt.NDArray[np.float32],
    mask: npt.NDArray[np.bool_],
    padding: int,
    bbox: tuple[int, int, int, int],
    pixel_to_nm_scaling: float,
    thresholds: list[float],
    filename: str,
    threshold: str | None = None,
    skeleton: npt.NDArray[np.bool_] | None = None,
    convolved_skeleton: npt.NDArray[np.int32] | None = None,
    height_profiles: dict[int, dict[int, npt.NDArray[np.float32]]] | None = None,
    stats: dict[int, dict[int, Any]] | None = None,
    disordered_trace: DisorderedTrace | None = None,
    nodes: dict[str, Node] | None = None,
    ordered_trace: OrderedTrace | None = None,
    threshold_method: str | None = None,
):
    """
    Initialise the class.

    Parameters
    ----------
    image : npt.NDArray[np.float32]
        2-D Numpy array of the cropped image.
    mask : npt.NDArray[np.bool_]
        3-D Numpy tensor of the cropped mask.
    padding : int
        Padding added to the bounding box of the grain during cropping.
    bbox : tuple[int, int, int, int]
        Bounding box of the crop including padding.
    pixel_to_nm_scaling : float
        Pixel to nanometre scaling factor for the crop.
    thresholds : list[float]
        A list of thresholds used to identify the grain.
    filename : str
        Filename of the image from which the crop was taken.
    threshold : str
        Direction of the molecule from the threshold (above / below).
    skeleton : npt.NDArray[np.bool_]
        3-D Numpy tensor of the skeletonised mask.
    convolved_skeleton : npt.NDArray[np.int32] | None = None
        2-D Numpy array of the convolved skeleton.
    height_profiles : dict[int, dict[int, npt.NDArray[np.float32]]] | None
        3-D Numpy tensor of the height profiles.
    stats : dict[str, int | float] | None
        Dictionary of grain statistics.
    disordered_trace : DisorderedTrace
        A disordered trace for the current grain.
    nodes : dict[int, Node] | None
        Grain nodes.
    ordered_trace : OrderedTrace | None
        An ordered trace for the grain.
    threshold_method : str
        Threshold method used to find grains.
    """
    self.padding = padding
    self.image = image
    # This part of the constructor must go after padding since the setter
    # for mask requires the padding.
    self.mask = mask
    self.bbox = bbox
    self.pixel_to_nm_scaling = pixel_to_nm_scaling
    self.thresholds = thresholds
    self.filename = filename
    self.threshold: str | None = threshold
    self.height_profiles: dict[int, dict[int, npt.NDArray[np.float32]]] | None = height_profiles
    self.stats: dict[str, Any] | None = {} if stats is None else stats
    self.skeleton: npt.NDArray[np.bool_] | None = skeleton
    self.convolved_skeleton: npt.NDArray[np.int32] | None = convolved_skeleton
    self.disordered_trace: DisorderedTrace | None = disordered_trace
    self.nodes: dict[int, Node] | None = nodes
    self.ordered_trace: OrderedTrace | None = ordered_trace
    self.threshold_method: str | None = threshold_method

__str__() -> str

Representation function for useful statistics for the class.

Returns:

Type Description
str

Set of formatted statistics on matched branches.

Source code in topostats\classes.py
def __str__(self) -> str:
    """
    Representation function for useful statistics for the class.

    Returns
    -------
    str
        Set of formatted statistics on matched branches.
    """
    return (
        f"filename : {self.filename}\n"
        f"image shape : {self.image.shape}\n"
        f"skeleton shape : {self.skeleton.shape}\n"
        f"mask shape : {self.mask.shape}\n"
        f"padding : {self.padding}\n"
        f"thresholds : {self.thresholds}\n"
        f"threshold method : {self.threshold_method}\n"
        f"bounding box coords : {self.bbox}\n"
        f"pixel to nm scaling : {self.pixel_to_nm_scaling}\n"
        f"number of nodes : {len(self.nodes)}"
    )

debug_locate_difference(other: object) -> None

Debug function to find the culprit when two GrainCrop objects are not equal.

Parameters:

Name Type Description Default
other object

Object to compare to.

required

Raises:

Type Description
ValueError

If the objects are not equal

Source code in topostats\classes.py
def debug_locate_difference(self, other: object) -> None:  # noqa: C901 # pylint: disable=too-many-branches
    """
    Debug function to find the culprit when two GrainCrop objects are not equal.

    Parameters
    ----------
    other : object
        Object to compare to.

    Raises
    ------
    ValueError
        If the objects are not equal
    """
    if not isinstance(other, GrainCrop):
        raise ValueError(f"Cannot compare GrainCrop with {type(other)}")
    if not np.array_equal(self.image, other.image):
        raise ValueError(f"Image is different\n self.image  : {self.image}\n other.image : {other.image}")
    if not np.array_equal(self.mask, other.mask):
        raise ValueError(f"Mask is different\n self.mask  : {self.mask}\n other.mask : {other.mask}")
    if self.padding != other.padding:
        raise ValueError(f"Padding is different\n self.padding  : {self.padding}\n other.padding : {other.padding}")
    if self.bbox != other.bbox:
        raise ValueError(f"Bounding box is different\n self.bbox  : {self.bbox}\n other.bbox : {other.bbox}")
    if self.pixel_to_nm_scaling != other.pixel_to_nm_scaling:
        raise ValueError(
            "Pixel to nm scaling is different\n"
            f" self.pixel_to_nm_scaling  : {self.pixel_to_nm_scaling}\n"
            f" other.pixel_to_nm_scaling : {other.pixel_to_nm_scaling}"
        )
    if self.thresholds != other.thresholds:
        raise ValueError(
            f"Thresholds differ\n self.thresholds  : {self.thresholds}\n other.thresholds : {other.thresholds}"
        )
    if self.filename != other.filename:
        raise ValueError(
            f"Filename is different\n self.filename  : {self.filename}\n other.filename : {other.filename}"
        )
    if self.height_profiles != other.height_profiles:
        raise ValueError(
            "Height profiles are different\n"
            f" self.height_profiles  : {self.height_profiles}\n"
            f" other.height_profiles : {other.height_profiles}"
        )
    if self.skeleton != other.skeleton:
        raise ValueError(
            f"Skeleton is different\n self.skeleton  : {self.skeleton}\n other.skeleton : {other.skeleton}"
        )
    if self.disordered_trace != other.disordered_trace:
        raise ValueError(
            "Disordered traces are different\n"
            f" self.disordered_trace  : {self.disordered_trace}\n"
            f" other.disordered_trace : {other.disordered_trace}"
        )
    if self.nodes != other.nodes:
        raise ValueError(f"Nodes are different\n self.nodes  : {self.nodes}\n other.nodes : {other.nodes}")
    if self.threshold_method != other.threshold_method:
        raise ValueError(
            "Threshold Method is different\n"
            f" self.threshold_method  : {self.threshold_method}\n"
            f" other.threshold_method : {other.threshold_method}"
        )
    LOGGER.info("Cannot find difference between graincrops")

MatchedBranch

Class for storing matched branches data and attributes.

Attributes:

Name Type Description
ordered_coords NDArray[int32]

Numpy array of ordered coordinates.

heights NDArray[number]

Numpy array of heights.

distances NDArray[number]

Numpy array of distances.

fwhm float

Full-width half maximum.

fwhm_half_maxs list[float]

Half-maximums from a matched branch.

fwhm_peaks list[int | float]

Peaks from a matched branch.

angles float

Angle between branches.

branch_statistics dict[str, float | int | list[Any] | str]

Dictionary of branch statistics, fwhm, fwhm_half_maxs and fwhm_peaks.

Source code in topostats\classes.py
@dataclass(
    repr=True, eq=True, config=ConfigDict(arbitrary_types_allowed=True, validate_assignment=True), validate_on_init=True
)
class MatchedBranch:
    """
    Class for storing matched branches data and attributes.

    Attributes
    ----------
    ordered_coords : npt.NDArray[np.int32]
        Numpy array of ordered coordinates.
    heights : npt.NDArray[np.number]
        Numpy array of heights.
    distances : npt.NDArray[np.number]
        Numpy array of distances.
    fwhm : float
        Full-width half maximum.
    fwhm_half_maxs : list[float]
        Half-maximums from a matched branch.
    fwhm_peaks : list[int | float]
        Peaks from a matched branch.
    angles : float
        Angle between branches.
    branch_statistics : dict[str, float | int | list[Any] | str]
        Dictionary of branch statistics, ``fwhm``, ``fwhm_half_maxs`` and ``fwhm_peaks``.
    """

    ordered_coords: npt.NDArray[np.int32] | None = None
    heights: npt.NDArray[np.number] | None = None
    distances: npt.NDArray[np.number] | None = None
    fwhm: float | None = None
    fwhm_half_maxs: list[float] | None = None
    fwhm_peaks: list[float] | None = None
    angles: float | list[float] | None = None
    branch_statistics: dict[str, float | int | list[Any] | str] | None = None

    def __str__(self) -> str:
        """
        Representation function for useful statistics for the class.

        Returns
        -------
        str
            Set of formatted and user-friendly statistics.
        """
        return (
            f"number of coords : {self.ordered_coords.shape[0]}\n"
            f"full width half maximum : {self.fwhm}\n"
            f"full width half maximum maximums : {self.fwhm_half_maxs}\n"
            f"full width half maximum peaks : {self.fwhm_peaks}\n"
            f"angles : {self.angles}"
        )

    def collate_branch_statistics(self, image: str, basename: str) -> dict[str, float | int | list[Any] | str]:
        """
        Collate matched branch statistics to dictionary.

        Parameters
        ----------
        image : str
            Image being processed, added to dictionary for subsequent aggregation, typically ``TopoStats.filename``.
        basename : str
            Base image path, added to dictionary for subsequent aggregation, typically ``TopoStats.img_path``.

        Returns
        -------
        dict[str, float | int | list[Any] | str]
            Dictionary of ``fwhm``, ``fwhm_half_maxs`` and ``fwhm_peaks`` along with ``image`` and ``basename``.
        """
        self.branch_statistics = {
            "image": image,
            "fwhm": self.fwhm,
            "fwhm_half_maxs": self.fwhm_half_maxs,
            "fwhm_peaks": self.fwhm_peaks,
            "basename": str(basename),
        }
        return self.branch_statistics

__str__() -> str

Representation function for useful statistics for the class.

Returns:

Type Description
str

Set of formatted and user-friendly statistics.

Source code in topostats\classes.py
def __str__(self) -> str:
    """
    Representation function for useful statistics for the class.

    Returns
    -------
    str
        Set of formatted and user-friendly statistics.
    """
    return (
        f"number of coords : {self.ordered_coords.shape[0]}\n"
        f"full width half maximum : {self.fwhm}\n"
        f"full width half maximum maximums : {self.fwhm_half_maxs}\n"
        f"full width half maximum peaks : {self.fwhm_peaks}\n"
        f"angles : {self.angles}"
    )

collate_branch_statistics(image: str, basename: str) -> dict[str, float | int | list[Any] | str]

Collate matched branch statistics to dictionary.

Parameters:

Name Type Description Default
image str

Image being processed, added to dictionary for subsequent aggregation, typically TopoStats.filename.

required
basename str

Base image path, added to dictionary for subsequent aggregation, typically TopoStats.img_path.

required

Returns:

Type Description
dict[str, float | int | list[Any] | str]

Dictionary of fwhm, fwhm_half_maxs and fwhm_peaks along with image and basename.

Source code in topostats\classes.py
def collate_branch_statistics(self, image: str, basename: str) -> dict[str, float | int | list[Any] | str]:
    """
    Collate matched branch statistics to dictionary.

    Parameters
    ----------
    image : str
        Image being processed, added to dictionary for subsequent aggregation, typically ``TopoStats.filename``.
    basename : str
        Base image path, added to dictionary for subsequent aggregation, typically ``TopoStats.img_path``.

    Returns
    -------
    dict[str, float | int | list[Any] | str]
        Dictionary of ``fwhm``, ``fwhm_half_maxs`` and ``fwhm_peaks`` along with ``image`` and ``basename``.
    """
    self.branch_statistics = {
        "image": image,
        "fwhm": self.fwhm,
        "fwhm_half_maxs": self.fwhm_half_maxs,
        "fwhm_peaks": self.fwhm_peaks,
        "basename": str(basename),
    }
    return self.branch_statistics

Molecule

Class for Molecules identified during ordered tracing.

threshold : str Direction from threshold of molecule (above / below) molecule_number : int Index of the molecule (per grain) circular : str, bool, optional Whether the molecule is circular or linear. processing : str Which processing type was used, topostats or nodestats. topology : str, optional Topological classification of the molecule. topology_flip : Any, optional Unknown? ordered_coords : npt.NDArray, optional Ordered coordinates for the molecule. splined_coords : npt.NDArray, optional Smoothed (aka splined) coordinates for the molecule. contour_length : float Length of the molecule. end_to_end_distance : float Distance between ends of molecule. Will be 0.0 for circular molecules which don't have ends. heights : npt.NDArray Height along molecule. distances : npt.NDArray Distance between points on the molecule. curvature_stats : npt.NDArray, optional Angle changes along molecule. NB - These are always positive due to use of np.abs() during calculation. bbox : tuple[int, int, int, int], optional Bounding box.

Source code in topostats\classes.py
@dataclass(
    repr=True, eq=True, config=ConfigDict(arbitrary_types_allowed=True, validate_assignment=True), validate_on_init=True
)
class Molecule:
    """
    Class for Molecules identified during ordered tracing.

    threshold : str
        Direction from threshold of molecule (above / below)
    molecule_number : int
        Index of the molecule (per grain)
    circular : str, bool, optional
        Whether the molecule is circular or linear.
    processing : str
        Which processing type was used, topostats or nodestats.
    topology : str, optional
        Topological classification of the molecule.
    topology_flip : Any, optional
        Unknown?
    ordered_coords : npt.NDArray, optional
        Ordered coordinates for the molecule.
    splined_coords : npt.NDArray, optional
        Smoothed (aka splined) coordinates for the molecule.
    contour_length : float
        Length of the molecule.
    end_to_end_distance : float
        Distance between ends of molecule. Will be ``0.0`` for circular molecules which don't have ends.
    heights : npt.NDArray
        Height along molecule.
    distances : npt.NDArray
        Distance between points on the molecule.
    curvature_stats : npt.NDArray, optional
        Angle changes along molecule. NB - These are always positive due to use of ``np.abs()`` during calculation.
    bbox : tuple[int, int, int, int], optional
        Bounding box.
    """

    threshold: str | None = None
    molecule_number: int | None = None
    circular: str | bool | None = None
    processing: str | None = None
    topology: str | None = None
    topology_flip: Any | None = None
    ordered_coords: npt.NDArray | None = None
    splined_coords: npt.NDArray | None = None
    contour_length: float | None = None
    end_to_end_distance: float | None = None
    heights: npt.NDArray | None = None
    distances: npt.NDArray | None = None
    curvature_stats: npt.NDArray | None = None
    bbox: tuple[int, int, int, int] | None = None
    molecule_statistics: dict[str, bool | str | float | None] | None = None

    def __str__(self) -> str:
        """
        Representation function for useful statistics for the class.

        Returns
        -------
        str
            Set of formatted and user-friendly statistics.
        """
        return (
            f"circular : {self.circular}\n"
            f"topology : {self.topology}\n"
            f"topology flip : {self.topology_flip}\n"
            f"number of ordered coords : {self.ordered_coords.shape[0]}\n"
            f"number of spline coords : {self.splined_coords.shape[0]}\n"
            f"contour length : {self.contour_length}\n"
            f"end to end distance : {self.end_to_end_distance}\n"
            f"bounding box coords : {self.bbox}"
        )

    def collate_molecule_statistics(self) -> dict[str, bool | str | float | None]:
        """
        Collate the molecule statsistics to a dictionary.

        Returns
        -------
        dict[str, bool | str | float | None]
            Dataframe of the classes attributes and their data.
        """
        self.molecule_statistics = {
            "circular": self.circular,
            "topology": self.topology,
            "topology_flip": self.topology_flip,
            "contour_length": self.contour_length,
            "end_to_end_distance": self.end_to_end_distance,
        }
        return self.molecule_statistics

__str__() -> str

Representation function for useful statistics for the class.

Returns:

Type Description
str

Set of formatted and user-friendly statistics.

Source code in topostats\classes.py
def __str__(self) -> str:
    """
    Representation function for useful statistics for the class.

    Returns
    -------
    str
        Set of formatted and user-friendly statistics.
    """
    return (
        f"circular : {self.circular}\n"
        f"topology : {self.topology}\n"
        f"topology flip : {self.topology_flip}\n"
        f"number of ordered coords : {self.ordered_coords.shape[0]}\n"
        f"number of spline coords : {self.splined_coords.shape[0]}\n"
        f"contour length : {self.contour_length}\n"
        f"end to end distance : {self.end_to_end_distance}\n"
        f"bounding box coords : {self.bbox}"
    )

collate_molecule_statistics() -> dict[str, bool | str | float | None]

Collate the molecule statsistics to a dictionary.

Returns:

Type Description
dict[str, bool | str | float | None]

Dataframe of the classes attributes and their data.

Source code in topostats\classes.py
def collate_molecule_statistics(self) -> dict[str, bool | str | float | None]:
    """
    Collate the molecule statsistics to a dictionary.

    Returns
    -------
    dict[str, bool | str | float | None]
        Dataframe of the classes attributes and their data.
    """
    self.molecule_statistics = {
        "circular": self.circular,
        "topology": self.topology,
        "topology_flip": self.topology_flip,
        "contour_length": self.contour_length,
        "end_to_end_distance": self.end_to_end_distance,
    }
    return self.molecule_statistics

Node

Class for storing Node data and attributes.

Attributes:

Name Type Description
error bool

Whether an error occurred calculating statistics for this node.

pixel_to_nm_scaling float64

Pixel to nanometre scaling.

branch_stats dict[int, MatchedBranch]

Dictionary of MatchedBranch objects.

unmatched_branch_stats dict[int, UnMatchedBranch]

Dictionary of UnmatchedBranch objects.

node_coords NDArray[int32]

Numpy array of node coordinates.

confidence float64

Normalised confidence of the crossing in the range of 0-1.

reduced_node_area NDArray[int32]

The molecule skeleton, with all branches removed that are not connected to the current node.

node_area_skeleton NDArray[int32]

Numpy array of skeleton.

node_branch_mask NDArray[int32]

Numpy array of branch mask.

node_avg_mask NDArray[int32]

Numpy array of averaged mask.

writhe str

Writhe type for the node.

Source code in topostats\classes.py
@dataclass(
    repr=True, eq=True, config=ConfigDict(arbitrary_types_allowed=True, validate_assignment=True), validate_on_init=True
)
class Node:
    """
    Class for storing Node data and attributes.

    Attributes
    ----------
    error : bool
        Whether an error occurred calculating statistics for this node.
    pixel_to_nm_scaling : np.float64
        Pixel to nanometre scaling.
    branch_stats : dict[int, MatchedBranch]
        Dictionary of ``MatchedBranch`` objects.
    unmatched_branch_stats : dict[int, UnMatchedBranch]
        Dictionary of ``UnmatchedBranch`` objects.
    node_coords : npt.NDArray[np.int32]
        Numpy array of node coordinates.
    confidence : np.float64
        Normalised confidence of the crossing in the range of ``0-1``.
    reduced_node_area : npt.NDArray[np.int32]
        The molecule skeleton, with all branches removed that are not connected to the current node.
    node_area_skeleton : npt.NDArray[np.int32]
        Numpy array of skeleton.
    node_branch_mask : npt.NDArray[np.int32]
        Numpy array of branch mask.
    node_avg_mask : npt.NDArray[np.int32]
        Numpy array of averaged mask.
    writhe : str
        Writhe type for the node.
    """

    error: bool | None = None
    pixel_to_nm_scaling: float | None = None
    branch_stats: dict[int, MatchedBranch] | None = None
    unmatched_branch_stats: dict[int, UnMatchedBranch] | None = None
    node_coords: npt.NDArray[np.int32] | None = None
    confidence: float | None = None
    reduced_node_area: npt.NDArray[np.int32] | None = None
    node_area_skeleton: npt.NDArray[np.int32] | None = None
    node_branch_mask: npt.NDArray[np.int32] | None = None
    node_avg_mask: npt.NDArray[np.int32] | None = None
    writhe: str | None = None

    def __str__(self) -> str:
        """
        Representation function for useful statistics for the class.

        Returns
        -------
        str
            Set of formatted and user-friendly statistics.
        """
        return (
            f"error : {self.error}\n"
            f"pixel to nm scaling (nm) : {self.pixel_to_nm_scaling}\n"
            f"number of matched branches : {len(self.branch_stats)}\n"
            f"number of unmatched branches : {len(self.unmatched_branch_stats)}\n"
            f"number of coords : {self.node_coords.shape[0]}\n"
            f"confidence : {self.confidence}\n"
            f"reduced node area : {self.reduced_node_area}"
        )

__str__() -> str

Representation function for useful statistics for the class.

Returns:

Type Description
str

Set of formatted and user-friendly statistics.

Source code in topostats\classes.py
def __str__(self) -> str:
    """
    Representation function for useful statistics for the class.

    Returns
    -------
    str
        Set of formatted and user-friendly statistics.
    """
    return (
        f"error : {self.error}\n"
        f"pixel to nm scaling (nm) : {self.pixel_to_nm_scaling}\n"
        f"number of matched branches : {len(self.branch_stats)}\n"
        f"number of unmatched branches : {len(self.unmatched_branch_stats)}\n"
        f"number of coords : {self.node_coords.shape[0]}\n"
        f"confidence : {self.confidence}\n"
        f"reduced node area : {self.reduced_node_area}"
    )

OrderedTrace

Class for Ordered Trace data and attributes.

molecule_data : dict[int, Molecule] Dictionary of Molecule objects indexed by molecule number. tracing_stats : dict | None Tracing statistics. grain_molstats : Any | None Grain molecule statistics. molecules : int Number of molecules within the grain. writhe : str The writhe sign, can be either +, - or 0 for positive, negative or no writhe. pixel_to_nm_scaling: np.float64 | None Pixel to nm scaling. images: dict[str, npt.NDArray] | None Diagnostic images produced during processing. error: bool | None Errors encountered? molecule_statistics : dict[int, dict[str, bool | str | float | None]] | None Dictionary of molecule statistics, with one entry for each molecule.

Source code in topostats\classes.py
@dataclass(
    repr=True, eq=True, config=ConfigDict(arbitrary_types_allowed=True, validate_assignment=True), validate_on_init=True
)
class OrderedTrace:
    """
    Class for Ordered Trace data and attributes.

    molecule_data : dict[int, Molecule]
        Dictionary of ``Molecule`` objects indexed by molecule number.
    tracing_stats : dict | None
        Tracing statistics.
    grain_molstats : Any | None
        Grain molecule statistics.
    molecules : int
        Number of molecules within the grain.
    writhe : str
        The writhe sign, can be either `+`, `-` or `0` for positive, negative or no writhe.
    pixel_to_nm_scaling: np.float64 | None
        Pixel to nm scaling.
    images: dict[str, npt.NDArray] | None
        Diagnostic images produced during processing.
    error: bool | None
        Errors encountered?
    molecule_statistics : dict[int, dict[str, bool | str | float | None]] | None
        Dictionary of molecule statistics, with one entry for each molecule.
    """

    molecule_data: dict[int, Molecule] | None = None
    tracing_stats: dict | None = None
    grain_molstats: Any | None = None
    molecules: int | None = None
    writhe: str | None = None
    pixel_to_nm_scaling: float | None = None
    images: dict[str, npt.NDArray] | None = None
    error: bool | None = None
    molecule_statistics: dict[int, dict[str, bool | str | float | None]] | None = None

    def __str__(self) -> str:
        """
        Representation function for useful statistics for the class.

        Returns
        -------
        str
            Set of formatted and user-friendly statistics.
        """
        writhe = {"+": "positive", "-": "negative", "0": "no writhe"}.get(self.writhe)
        return (
            f"number of molecules : {self.molecules}\n"
            f"number of images : {len(self.images)}\n"
            f"writhe : {writhe}\n"
            f"pixel to nm scaling : {self.pixel_to_nm_scaling}\n"
            f"error : {self.error}"
        )

    def collate_molecule_statistics(self) -> dict[int, dict[str, bool | int | str | None]]:
        """
        Collate molecule statistics for all molecules to dictionary.

        The resulting dictionary can be easily converted to Pandas Dataframes for writing to CSV.

        Returns
        -------
        dict[int, dict[str, bool | int | str | None]]
            Dictionary, indexed by molecule where the value is the molecules statistics for the given molecule.
        """
        self.molecule_statistics = {
            molecule_number: molecule.collate_molecule_statistics()
            for molecule_number, molecule in self.molecule_data.items()
        }
        return self.molecule_statistics

__str__() -> str

Representation function for useful statistics for the class.

Returns:

Type Description
str

Set of formatted and user-friendly statistics.

Source code in topostats\classes.py
def __str__(self) -> str:
    """
    Representation function for useful statistics for the class.

    Returns
    -------
    str
        Set of formatted and user-friendly statistics.
    """
    writhe = {"+": "positive", "-": "negative", "0": "no writhe"}.get(self.writhe)
    return (
        f"number of molecules : {self.molecules}\n"
        f"number of images : {len(self.images)}\n"
        f"writhe : {writhe}\n"
        f"pixel to nm scaling : {self.pixel_to_nm_scaling}\n"
        f"error : {self.error}"
    )

collate_molecule_statistics() -> dict[int, dict[str, bool | int | str | None]]

Collate molecule statistics for all molecules to dictionary.

The resulting dictionary can be easily converted to Pandas Dataframes for writing to CSV.

Returns:

Type Description
dict[int, dict[str, bool | int | str | None]]

Dictionary, indexed by molecule where the value is the molecules statistics for the given molecule.

Source code in topostats\classes.py
def collate_molecule_statistics(self) -> dict[int, dict[str, bool | int | str | None]]:
    """
    Collate molecule statistics for all molecules to dictionary.

    The resulting dictionary can be easily converted to Pandas Dataframes for writing to CSV.

    Returns
    -------
    dict[int, dict[str, bool | int | str | None]]
        Dictionary, indexed by molecule where the value is the molecules statistics for the given molecule.
    """
    self.molecule_statistics = {
        molecule_number: molecule.collate_molecule_statistics()
        for molecule_number, molecule in self.molecule_data.items()
    }
    return self.molecule_statistics

TopoStats

Class for storing TopoStats objects.

Attributes:

Name Type Description
grain_crops dict[int, GrainCrop] | None

Dictionary of GrainCrop objects.

filename str | None

Filename.

image_name str | None

Filename without its extension.

pixel_to_nm_scaling str | None

Pixel to nanometre scaling.

img_path str | None

Original path to image.

image NDArray | None

Flattened image (post Filter()).

image_original NDArray | None

Original image.

image_statistics DataFrame | None

Pandas dataframe of

full_mask_tensor NDArray

Tensor mask for the full image.

topostats_version str | None

TopoStats version.

config dict[str, Any] | None

Configuration used when processing the grain.

full_image_plots dict[str, NDArray[float64]]

Dictionary of numpy arrays where disordered tracing, nodestats and ordered tracing skeletons are mapped back to the original image.

basename str

Basename of image locations.

Source code in topostats\classes.py
@dataclass(
    repr=True, eq=True, config=ConfigDict(arbitrary_types_allowed=True, validate_assignment=True), validate_on_init=True
)
class TopoStats:
    """
    Class for storing TopoStats objects.

    Attributes
    ----------
    grain_crops : dict[int, GrainCrop] | None
        Dictionary of ``GrainCrop`` objects.
    filename : str | None
        Filename.
    image_name : str | None
        Filename without its extension.
    pixel_to_nm_scaling : str | None
        Pixel to nanometre scaling.
    img_path : str | None
        Original path to image.
    image : npt.NDArray | None
        Flattened image (post ``Filter()``).
    image_original : npt.NDArray | None
        Original image.
    image_statistics : pd.DataFrame | None
        Pandas dataframe of
    full_mask_tensor : npt.NDArray
        Tensor mask for the full image.
    topostats_version : str | None
        TopoStats version.
    config : dict[str, Any] | None
        Configuration used when processing the grain.
    full_image_plots : dict[str, npt.NDArray[np.float64]]
        Dictionary of numpy arrays where disordered tracing, nodestats and ordered tracing skeletons are mapped back to
        the original image.
    basename : str
        Basename of image locations.
    """

    grain_crops: dict[int, GrainCrop] | None = None
    filename: str | None = None
    image_name: str | None = None
    pixel_to_nm_scaling: float | None = None
    img_path: Path | str | None = None
    image: npt.NDArray | None = None
    image_original: npt.NDArray | None = None
    image_statistics: dict[str, int | float | str] | None = None
    full_mask_tensor: npt.NDArray | None = None
    topostats_version: str | None = None
    config: dict[str, Any] | None = None
    full_image_plots: dict[str, npt.NDArray[np.float64]] | None = None
    basename: str | None = None

    def __str__(self) -> str:
        """
        Representation function for useful statistics for the class.

        Returns
        -------
        str
            Set of formatted and user-friendly statistics.
        """
        return (
            f"number of grain crops : {len(self.grain_crops)}\n"
            f"filename : {self.filename}\n"
            f"pixel to nm scaling : {self.pixel_to_nm_scaling}\n"
            f"image shape (px) : {self.image.shape}\n"
            f"image path : {self.img_path}\n"
            f"TopoStats version : {self.topostats_version}\n"
            f"Basename : {self.basename}"
        )

    def __eq__(self, other: object) -> bool:
        """
        Check if two ``TopoStats`` objects are equal.

        Parameters
        ----------
        other : object
            Other ``TopoStats`` object to compare to.

        Returns
        -------
        bool
            ``True`` if the objects are equal, ``False`` otherwise.
        """
        if not isinstance(other, TopoStats):
            return False
        return (
            self.grain_crops == other.grain_crops
            and self.filename == other.filename
            and self.pixel_to_nm_scaling == other.pixel_to_nm_scaling
            and self.img_path == other.img_path
            and self.topostats_version == other.topostats_version
            and self.config == other.config
            and np.array_equal(self.image, other.image)
            and np.array_equal(self.image_original, other.image_original)
            and np.array_equal(self.full_mask_tensor, other.full_mask_tensor)
        )

    def calculate_image_statistics(self) -> dict[str, int | float]:
        """
        Calculate the image statistics via ``statistics.image_statistics()``.

        Returns
        -------
        dict[str, int | float]
            Dictionary of image size and area in both metres and pixels, the number of grains, density and the
            root mean square of the image roughness.
        """
        self.image_statistics = image_statistics(
            image=self.image,
            filename=self.filename,
            pixel_to_nm_scaling=self.pixel_to_nm_scaling,
            n_grains=len(self.grain_crops) if self.grain_crops is not None and len(self.grain_crops) > 0 else 0,
        )
        return self.image_statistics

__eq__(other: object) -> bool

Check if two TopoStats objects are equal.

Parameters:

Name Type Description Default
other object

Other TopoStats object to compare to.

required

Returns:

Type Description
bool

True if the objects are equal, False otherwise.

Source code in topostats\classes.py
def __eq__(self, other: object) -> bool:
    """
    Check if two ``TopoStats`` objects are equal.

    Parameters
    ----------
    other : object
        Other ``TopoStats`` object to compare to.

    Returns
    -------
    bool
        ``True`` if the objects are equal, ``False`` otherwise.
    """
    if not isinstance(other, TopoStats):
        return False
    return (
        self.grain_crops == other.grain_crops
        and self.filename == other.filename
        and self.pixel_to_nm_scaling == other.pixel_to_nm_scaling
        and self.img_path == other.img_path
        and self.topostats_version == other.topostats_version
        and self.config == other.config
        and np.array_equal(self.image, other.image)
        and np.array_equal(self.image_original, other.image_original)
        and np.array_equal(self.full_mask_tensor, other.full_mask_tensor)
    )

__str__() -> str

Representation function for useful statistics for the class.

Returns:

Type Description
str

Set of formatted and user-friendly statistics.

Source code in topostats\classes.py
def __str__(self) -> str:
    """
    Representation function for useful statistics for the class.

    Returns
    -------
    str
        Set of formatted and user-friendly statistics.
    """
    return (
        f"number of grain crops : {len(self.grain_crops)}\n"
        f"filename : {self.filename}\n"
        f"pixel to nm scaling : {self.pixel_to_nm_scaling}\n"
        f"image shape (px) : {self.image.shape}\n"
        f"image path : {self.img_path}\n"
        f"TopoStats version : {self.topostats_version}\n"
        f"Basename : {self.basename}"
    )

calculate_image_statistics() -> dict[str, int | float]

Calculate the image statistics via statistics.image_statistics().

Returns:

Type Description
dict[str, int | float]

Dictionary of image size and area in both metres and pixels, the number of grains, density and the root mean square of the image roughness.

Source code in topostats\classes.py
def calculate_image_statistics(self) -> dict[str, int | float]:
    """
    Calculate the image statistics via ``statistics.image_statistics()``.

    Returns
    -------
    dict[str, int | float]
        Dictionary of image size and area in both metres and pixels, the number of grains, density and the
        root mean square of the image roughness.
    """
    self.image_statistics = image_statistics(
        image=self.image,
        filename=self.filename,
        pixel_to_nm_scaling=self.pixel_to_nm_scaling,
        n_grains=len(self.grain_crops) if self.grain_crops is not None and len(self.grain_crops) > 0 else 0,
    )
    return self.image_statistics

UnMatchedBranch

Class for storing matched branches data and attributes.

Attributes:

Name Type Description
angles float

Angle between branches.

Source code in topostats\classes.py
@dataclass(
    repr=True, eq=True, config=ConfigDict(arbitrary_types_allowed=True, validate_assignment=True), validate_on_init=True
)
class UnMatchedBranch:
    """
    Class for storing matched branches data and attributes.

    Attributes
    ----------
    angles : float
        Angle between branches.
    """

    angles: float | list[float] | None = None

    def __str__(self) -> str:
        """
        Representation function for useful statistics for the class.

        Returns
        -------
        str
            Set of formatted and user-friendly statistics.
        """
        return f"angles : {self.angles}"

__str__() -> str

Representation function for useful statistics for the class.

Returns:

Type Description
str

Set of formatted and user-friendly statistics.

Source code in topostats\classes.py
def __str__(self) -> str:
    """
    Representation function for useful statistics for the class.

    Returns
    -------
    str
        Set of formatted and user-friendly statistics.
    """
    return f"angles : {self.angles}"

convert_to_dict(to_convert: Any) -> dict[str, Any]

Convert a class to a dictionary representation of itself.

Classes that are part of the topostats package (TopoStats, GrainCrop, DisorderedTrace, Node, OrderedTrace, MatchedBranch, UnMatchedBranch and Molecule) can be converted to dictionaries along with any other class that has the __dict__ attribute.

The keys are the attribute names (with the _ prefix removed) and the values are the stored values of the attribute. Values of type str, int, float, bool, tuple, np.ndarray and pathlib.Path are returned as is. If a value is itself a dictionary (e.g. TopoStats.grain_crops Node.branch_stats or OrderedTrace.molecule_data which are all dictionaries of the objects) then the object is recursed.

If lists or dictionaries are provided a TypeError is raised and any object that doesn't have the __dict__ attribute then an AttributeError is raised.

Parameters:

Name Type Description Default
to_convert dict[str, Any]

An object to be converted to a dictionary / dictionary item.

required

Returns:

Type Description
Any

Input parameter as a dictionary / dictionary item.

Source code in topostats\classes.py
def convert_to_dict(to_convert: Any) -> dict[str, Any]:
    """
    Convert a class to a dictionary representation of itself.

    Classes that are part of the ``topostats`` package (``TopoStats``, ``GrainCrop``, ``DisorderedTrace``, ``Node``,
    ``OrderedTrace``, ``MatchedBranch``, ``UnMatchedBranch`` and ``Molecule``) can be converted to dictionaries along
    with any other class that has the ``__dict__`` attribute.

    The keys are the attribute names (with the ``_`` prefix removed) and the values are the stored values of the
    attribute. Values of type ``str``, ``int``, ``float``, ``bool``, ``tuple``, ``np.ndarray`` and ``pathlib.Path`` are
    returned as is. If a value is itself a dictionary (e.g. ``TopoStats.grain_crops`` ``Node.branch_stats`` or
    ``OrderedTrace.molecule_data`` which are all dictionaries of the objects) then the object is recursed.

    If lists or dictionaries are provided a ``TypeError`` is raised and any object that doesn't have the ``__dict__``
    attribute then an ``AttributeError`` is raised.

    Parameters
    ----------
    to_convert : dict[str, Any]
        An object to be converted to a dictionary / dictionary item.

    Returns
    -------
    Any
        Input parameter as a dictionary / dictionary item.
    """
    if isinstance(to_convert, (int | float | str | Path | bool | np.ndarray | tuple | None)):
        return to_convert
    if isinstance(to_convert, list):
        return [convert_to_dict(item) for item in to_convert]
    if isinstance(to_convert, dict):
        return {key: convert_to_dict(value) for key, value in to_convert.items()}
    if hasattr(to_convert, "__dict__"):
        return {re.sub(r"^_", "", key): convert_to_dict(value) for key, value in to_convert.__dict__.items()}

    return to_convert

validate_full_mask_tensor_shape(array: npt.NDArray[np.bool_]) -> npt.NDArray[np.bool_]

Validate the shape of the full mask tensor.

Parameters:

Name Type Description Default
array NDArray

Numpy array to validate.

required

Returns:

Type Description
NDArray

Numpy array if valid.

Source code in topostats\classes.py
def validate_full_mask_tensor_shape(array: npt.NDArray[np.bool_]) -> npt.NDArray[np.bool_]:
    """
    Validate the shape of the full mask tensor.

    Parameters
    ----------
    array : npt.NDArray
        Numpy array to validate.

    Returns
    -------
    npt.NDArray
        Numpy array if valid.
    """
    if len(array.shape) != 3 or array.shape[2] < 2:
        raise ValueError(f"Full mask tensor must be WxHxC with C >= 2 but has shape {array.shape}")
    return array