File size: 3,284 Bytes
69591a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import attrs
import numpy as np
from typing import Tuple
from dnafiber.postprocess.skan import trace_skeleton

@attrs.define
class Bbox:
    x: int
    y: int
    width: int
    height: int

    @property
    def bbox(self) -> Tuple[int, int, int, int]:
        return (self.x, self.y, self.width, self.height)

    @bbox.setter
    def bbox(self, value: Tuple[int, int, int, int]):
        self.x, self.y, self.width, self.height = value


@attrs.define
class Fiber:
    bbox: Bbox
    data: np.ndarray


@attrs.define
class FiberProps:
    fiber: Fiber
    fiber_id: int
    red_pixels: int = None
    green_pixels: int = None
    category: str = None

    @property
    def bbox(self):
        return self.fiber.bbox.bbox

    @bbox.setter
    def bbox(self, value):
        self.fiber.bbox = value

    @property
    def data(self):
        return self.fiber.data

    @data.setter
    def data(self, value):
        self.fiber.data = value

    @property
    def red(self):
        if self.red_pixels is None:
            self.red_pixels, self.green_pixels = self.counts
        return self.red_pixels

    @property
    def green(self):
        if self.green_pixels is None:
            self.red_pixels, self.green_pixels = self.counts
        return self.green_pixels

    @property
    def length(self):
        return sum(self.counts)

    @property
    def counts(self):
        if self.red_pixels is None or self.green_pixels is None:
            self.red_pixels = np.sum(self.data == 1)
            self.green_pixels = np.sum(self.data == 2)
        return self.red_pixels, self.green_pixels

    @property
    def fiber_type(self):
        if self.category is not None:
            return self.category
        red_pixels, green_pixels = self.counts
        if red_pixels == 0 or green_pixels == 0:
            self.category = "single"
        else:
            self.category = estimate_fiber_category(self.data)
        return self.category

    @property
    def ratio(self):
        return self.green / self.red

    @property
    def is_valid(self):
        return (
            self.fiber_type == "double"
            or self.fiber_type == "one-two-one"
            or self.fiber_type == "two-one-two"
        )
    
    def scaled_coordinates(self, scale: float) -> Tuple[int, int]:
        """

        Scale down the coordinates of the fiber's bounding box.

        """
        x, y, width, height = self.bbox
        return (
            int(x * scale),
            int(y * scale),
            int(width * scale),
            int(height * scale),
        )


def estimate_fiber_category(fiber: np.ndarray) -> str:
    """

    Estimate the fiber category based on the number of red and green pixels.

    """
    coordinates = trace_skeleton(fiber > 0)
    coordinates = np.asarray(coordinates)
    values = fiber[coordinates[:, 0], coordinates[:, 1]]
    diff = np.diff(values)
    jump = np.sum(diff != 0)
    n_ccs = jump + 1
    if n_ccs == 2:
        return "double"
    elif n_ccs == 3:
        if values[0] == 1:
            return "one-two-one"
        else:
            return "two-one-two"
    else:
        return "multiple"