Spaces:
Sleeping
Sleeping
Upload landmarkdiff/clinical.py with huggingface_hub
Browse files- landmarkdiff/clinical.py +73 -9
landmarkdiff/clinical.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
| 1 |
-
"""Clinical edge
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
from __future__ import annotations
|
|
@@ -16,7 +19,10 @@ from landmarkdiff.landmarks import FaceLandmarks
|
|
| 16 |
|
| 17 |
@dataclass
|
| 18 |
class ClinicalFlags:
|
| 19 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
vitiligo: bool = False
|
| 22 |
bells_palsy: bool = False
|
|
@@ -35,7 +41,20 @@ def detect_vitiligo_patches(
|
|
| 35 |
l_threshold: float = 85.0,
|
| 36 |
min_patch_area: int = 200,
|
| 37 |
) -> np.ndarray:
|
| 38 |
-
"""Detect depigmented (vitiligo) patches on face using LAB luminance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
h, w = image.shape[:2]
|
| 40 |
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB).astype(np.float32)
|
| 41 |
|
|
@@ -83,7 +102,19 @@ def adjust_mask_for_vitiligo(
|
|
| 83 |
vitiligo_patches: np.ndarray,
|
| 84 |
preservation_factor: float = 0.3,
|
| 85 |
) -> np.ndarray:
|
| 86 |
-
"""Reduce mask intensity over vitiligo patches to preserve them.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
patches_f = vitiligo_patches.astype(np.float32) / 255.0
|
| 88 |
reduction = patches_f * preservation_factor
|
| 89 |
return np.clip(mask - reduction, 0.0, 1.0)
|
|
@@ -92,7 +123,14 @@ def adjust_mask_for_vitiligo(
|
|
| 92 |
def get_bells_palsy_side_indices(
|
| 93 |
side: str,
|
| 94 |
) -> dict[str, list[int]]:
|
| 95 |
-
"""Get landmark indices for the affected side in Bell's palsy.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
if side == "left":
|
| 97 |
return {
|
| 98 |
"eye": [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246],
|
|
@@ -116,7 +154,21 @@ def get_keloid_exclusion_mask(
|
|
| 116 |
height: int,
|
| 117 |
margin_px: int = 10,
|
| 118 |
) -> np.ndarray:
|
| 119 |
-
"""Generate mask of keloid-prone regions to exclude from aggressive compositing.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
from landmarkdiff.landmarks import LANDMARK_REGIONS
|
| 121 |
|
| 122 |
mask = np.zeros((height, width), dtype=np.float32)
|
|
@@ -145,7 +197,19 @@ def adjust_mask_for_keloid(
|
|
| 145 |
keloid_mask: np.ndarray,
|
| 146 |
reduction_factor: float = 0.5,
|
| 147 |
) -> np.ndarray:
|
| 148 |
-
"""Soften mask transitions in keloid-prone areas.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
# Reduce mask intensity in keloid-prone areas
|
| 150 |
keloid_reduction = keloid_mask * reduction_factor
|
| 151 |
modified = mask * (1.0 - keloid_reduction)
|
|
|
|
| 1 |
+
"""Clinical edge case handling for pathological conditions.
|
| 2 |
|
| 3 |
+
Implements special-case logic for:
|
| 4 |
+
- Vitiligo: preserve depigmented patches (don't blend over them)
|
| 5 |
+
- Bell's palsy: disable bilateral symmetry in deformation vectors
|
| 6 |
+
- Keloid: flag keloid-prone areas to reduce aggressive compositing
|
| 7 |
+
- Ehlers-Danlos: wider influence radii for hypermobile tissue
|
| 8 |
"""
|
| 9 |
|
| 10 |
from __future__ import annotations
|
|
|
|
| 19 |
|
| 20 |
@dataclass
|
| 21 |
class ClinicalFlags:
|
| 22 |
+
"""Clinical condition flags that modify pipeline behavior.
|
| 23 |
+
|
| 24 |
+
Set flags to True to enable condition-specific handling.
|
| 25 |
+
"""
|
| 26 |
|
| 27 |
vitiligo: bool = False
|
| 28 |
bells_palsy: bool = False
|
|
|
|
| 41 |
l_threshold: float = 85.0,
|
| 42 |
min_patch_area: int = 200,
|
| 43 |
) -> np.ndarray:
|
| 44 |
+
"""Detect depigmented (vitiligo) patches on face using LAB luminance.
|
| 45 |
+
|
| 46 |
+
Vitiligo patches appear as high-L, low-saturation regions that deviate
|
| 47 |
+
significantly from surrounding skin tone.
|
| 48 |
+
|
| 49 |
+
Args:
|
| 50 |
+
image: BGR face image.
|
| 51 |
+
face: Extracted landmarks for face ROI.
|
| 52 |
+
l_threshold: Luminance threshold (patches brighter than surrounding skin).
|
| 53 |
+
min_patch_area: Minimum contour area in pixels to count as a patch.
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
Binary mask (uint8, 0/255) of detected vitiligo patches.
|
| 57 |
+
"""
|
| 58 |
h, w = image.shape[:2]
|
| 59 |
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB).astype(np.float32)
|
| 60 |
|
|
|
|
| 102 |
vitiligo_patches: np.ndarray,
|
| 103 |
preservation_factor: float = 0.3,
|
| 104 |
) -> np.ndarray:
|
| 105 |
+
"""Reduce mask intensity over vitiligo patches to preserve them.
|
| 106 |
+
|
| 107 |
+
Instead of full blending over depigmented patches, we reduce the
|
| 108 |
+
mask weight so the original vitiligo pattern shows through.
|
| 109 |
+
|
| 110 |
+
Args:
|
| 111 |
+
mask: Float32 surgical mask [0-1].
|
| 112 |
+
vitiligo_patches: Binary mask of vitiligo regions (0/255 uint8).
|
| 113 |
+
preservation_factor: How much to reduce blending (0=full blend, 1=fully preserve).
|
| 114 |
+
|
| 115 |
+
Returns:
|
| 116 |
+
Modified mask with reduced intensity over vitiligo patches.
|
| 117 |
+
"""
|
| 118 |
patches_f = vitiligo_patches.astype(np.float32) / 255.0
|
| 119 |
reduction = patches_f * preservation_factor
|
| 120 |
return np.clip(mask - reduction, 0.0, 1.0)
|
|
|
|
| 123 |
def get_bells_palsy_side_indices(
|
| 124 |
side: str,
|
| 125 |
) -> dict[str, list[int]]:
|
| 126 |
+
"""Get landmark indices for the affected side in Bell's palsy.
|
| 127 |
+
|
| 128 |
+
In Bell's palsy, one side of the face is paralyzed. We should NOT
|
| 129 |
+
apply bilateral symmetric deformations — only deform the healthy side.
|
| 130 |
+
|
| 131 |
+
Returns:
|
| 132 |
+
Dict mapping region names to landmark indices on the affected side.
|
| 133 |
+
"""
|
| 134 |
if side == "left":
|
| 135 |
return {
|
| 136 |
"eye": [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246],
|
|
|
|
| 154 |
height: int,
|
| 155 |
margin_px: int = 10,
|
| 156 |
) -> np.ndarray:
|
| 157 |
+
"""Generate mask of keloid-prone regions to exclude from aggressive compositing.
|
| 158 |
+
|
| 159 |
+
Keloid patients should have reduced blending intensity and no sharp
|
| 160 |
+
boundary transitions in prone areas (typically jawline, ears, chest).
|
| 161 |
+
|
| 162 |
+
Args:
|
| 163 |
+
face: Extracted landmarks.
|
| 164 |
+
regions: List of region names prone to keloids.
|
| 165 |
+
width: Image width.
|
| 166 |
+
height: Image height.
|
| 167 |
+
margin_px: Extra margin around keloid regions.
|
| 168 |
+
|
| 169 |
+
Returns:
|
| 170 |
+
Float32 mask [0-1] where 1 = keloid-prone area.
|
| 171 |
+
"""
|
| 172 |
from landmarkdiff.landmarks import LANDMARK_REGIONS
|
| 173 |
|
| 174 |
mask = np.zeros((height, width), dtype=np.float32)
|
|
|
|
| 197 |
keloid_mask: np.ndarray,
|
| 198 |
reduction_factor: float = 0.5,
|
| 199 |
) -> np.ndarray:
|
| 200 |
+
"""Soften mask transitions in keloid-prone areas.
|
| 201 |
+
|
| 202 |
+
Reduces the mask gradient steepness to prevent hard boundaries
|
| 203 |
+
that could trigger keloid formation in real surgical planning.
|
| 204 |
+
|
| 205 |
+
Args:
|
| 206 |
+
mask: Float32 surgical mask [0-1].
|
| 207 |
+
keloid_mask: Float32 keloid region mask [0-1].
|
| 208 |
+
reduction_factor: How much to reduce mask intensity in keloid areas.
|
| 209 |
+
|
| 210 |
+
Returns:
|
| 211 |
+
Modified mask with gentler transitions in keloid regions.
|
| 212 |
+
"""
|
| 213 |
# Reduce mask intensity in keloid-prone areas
|
| 214 |
keloid_reduction = keloid_mask * reduction_factor
|
| 215 |
modified = mask * (1.0 - keloid_reduction)
|