Files changed (1) hide show
  1. pipeline/rules.py +37 -0
pipeline/rules.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional
3
+ import statistics, math
4
+
5
+ @dataclass
6
+ class FrameFeatures:
7
+ b_count: int
8
+ b_area_frac: float = 0.0
9
+ has_consolidation: bool = False
10
+ quality: float = 1.0
11
+
12
+ @dataclass
13
+ class ZoneFeatures:
14
+ b_count: int
15
+ b_area_frac: float
16
+ has_consolidation: bool
17
+
18
+ def aggregate_zone(frames: List[FrameFeatures],
19
+ min_quality: float = 0.5, min_frames: int = 3,
20
+ coalescent_percentile: float = 0.90, consolidation_majority: float = 0.25) -> Optional[ZoneFeatures]:
21
+ kept=[f for f in frames if f.quality>=min_quality]
22
+ if len(kept)<min_frames: return None
23
+ b_counts=[f.b_count for f in kept]
24
+ area_vals=sorted(f.b_area_frac for f in kept)
25
+ p_idx=max(0,min(len(area_vals)-1,math.floor(coalescent_percentile*(len(area_vals)-1))))
26
+ b_area_agg=area_vals[p_idx]
27
+ has_consol=(sum(1 for f in kept if f.has_consolidation)/len(kept))>=consolidation_majority
28
+ return ZoneFeatures(int(round(statistics.median(b_counts))), float(b_area_agg), bool(has_consol))
29
+
30
+ def zone_lus_score(z: ZoneFeatures, b_count_threshold: int = 3, coalescent_area_threshold: float = 0.40) -> int:
31
+ if z.has_consolidation: return 3
32
+ if z.b_area_frac >= coalescent_area_threshold: return 2
33
+ if z.b_count >= b_count_threshold: return 1
34
+ return 0
35
+
36
+ def patient_lus_score(zones: List[Optional[ZoneFeatures]]) -> int:
37
+ return sum(zone_lus_score(z) for z in zones if z is not None)