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