Input Image (256×256 RGB)
│
▼
┌─────────────────────────┐
│ MobileViT-S │ Vision Encoder
│ apple/mobilevit-small │ 640-dim features
│ (~5.6M params) │
└───────────┬─────────────┘
│
┌──────┴──────┐
▼ ▼
┌─────────┐ ┌─────────────┐
│ Pooled │ │ Sequence │
│Features │ │ Features │
│ (640) │ │ (N×640) │
└────┬────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────┐ ┌─────────────┐
│Projector│ │ Seg Decoder │
│ (256) │ │(128→64→32) │
└────┬────┘ └──────┬──────┘
│ │
┌──┴──┐ │
▼ ▼ ▼
┌────┐┌────┐ ┌──────────┐
│Cls ││Reg │ │ Seg Mask │
│Head││Head│ │(3×H×W) │
└────┘└────┘ └──────────┘
│ │ │
▼ ▼ ▼
Plane AoP, Symphysis,
Logits HSD Head Masks
(6) (2) (3×256×256)
1 # ONNX outputs (in order)
2 outputs = session . run ( None , { "image" : img } )
3 plane_logits = outputs [ 0 ] # (1, 6) - Plane classification
4 seg_masks = outputs [ 1 ] # (1, 3, 256, 256) - Segmentation
5 labor_params = outputs [ 2 ] # (1, 2) - [AoP, HSD]
1 import onnxruntime as ort
2 import numpy as np
3 from PIL import Image
4
5 # Load model
6 session = ort . InferenceSession ( "laborview_mobilevit.onnx" )
7
8 # Preprocess (256x256, ImageNet normalization)
9 image = Image . open ( "ultrasound.png" ) . convert ( "RGB" ) . resize ( ( 256 , 256 ) )
10 img = np . array ( image ) . astype ( np . float32 ) / 255.0
11 img = ( img - [ 0.485 , 0.456 , 0.406 ] ) / [ 0.229 , 0.224 , 0.225 ]
12 img = img . transpose ( 2 , 0 , 1 ) [ np . newaxis , . . . ] . astype ( np . float32 )
13
14 # Multi-task inference
15 plane_logits , seg_masks , labor_params = session . run ( None , { "image" : img } )
16
17 # Parse outputs
18 plane = [ "transperineal" , "transabdominal" , "oblique" ,
19 "sagittal" , "axial" , "other" ] [ np . argmax ( plane_logits ) ]
20 mask = np . argmax ( seg_masks , axis = 1 ) [ 0 ]
21 aop , hsd = labor_params [ 0 ]
22
23 print ( f"Plane: { plane } " )
24 print ( f"AoP: { aop : .1f } °, HSD: { hsd : .1f } px" )
1 import 'package:flutter_onnxruntime/flutter_onnxruntime.dart' ;
2
3 class LaborViewService extends ChangeNotifier {
4 OrtSession ? _session ;
5
6 Future < void > loadModel ( ) async {
7 final options = OrtSessionOptions ( ) ;
8 _session = await OrtSession . fromAsset (
9 'assets/models/laborview_mobilevit.onnx' ,
10 options ,
11 ) ;
12 }
13
14 Future < LaborViewResult > analyze ( Uint8List imageBytes ) async {
15 // Preprocess to 256x256, ImageNet normalize
16 final input = _preprocessImage ( imageBytes ) ;
17
18 // Run multi-task inference
19 final outputs = await _session ! . run ( [ input ] ) ;
20
21 return LaborViewResult (
22 planeClass : _argmax ( outputs [ 0 ] ) ,
23 segMask : _argmax2D ( outputs [ 1 ] ) ,
24 aop : outputs [ 2 ] [ 0 ] ,
25 hsd : outputs [ 2 ] [ 1 ] ,
26 ) ;
27 }
28 }
1 import CoreML
2 import Vision
3
4 class LaborViewAnalyzer {
5 private let model : VNCoreMLModel
6
7 init ( ) throws {
8 let config = MLModelConfiguration ( )
9 config . computeUnits = . cpuAndNeuralEngine
10 let laborview = try LaborView ( configuration : config )
11 model = try VNCoreMLModel ( for : laborview . model )
12 }
13
14 func analyze ( image : CGImage ) async throws -> LaborViewResult {
15 let request = VNCoreMLRequest ( model : model )
16 let handler = VNImageRequestHandler ( cgImage : image )
17 try handler . perform ( [ request ] )
18
19 guard let results = request . results as ? [ VNCoreMLFeatureValueObservation ] else {
20 throw AnalysisError . noResults
21 }
22
23 return LaborViewResult (
24 planeLogits : results [ 0 ] . featureValue . multiArrayValue ! ,
25 segMask : results [ 1 ] . featureValue . multiArrayValue ! ,
26 laborParams : results [ 2 ] . featureValue . multiArrayValue !
27 )
28 }
29 }
1 class LaborViewInterpreter ( context : Context ) {
2 private val interpreter : Interpreter
3
4 init {
5 val options = Interpreter . Options ( ) . apply {
6 setNumThreads ( 4 )
7 addDelegate ( GpuDelegate ( ) )
8 }
9 val model = FileUtil . loadMappedFile ( context , "laborview.tflite" )
10 interpreter = Interpreter ( model , options )
11 }
12
13 fun analyze ( bitmap : Bitmap ) : LaborViewResult {
14 val input = preprocessBitmap ( bitmap ) // 1x3x256x256
15
16 val planeLogits = Array ( 1 ) { FloatArray ( 6 ) }
17 val segMask = Array ( 1 ) { Array ( 3 ) { Array ( 256 ) { FloatArray ( 256 ) } } }
18 val laborParams = Array ( 1 ) { FloatArray ( 2 ) }
19
20 val outputs = mapOf (
21 0 to planeLogits ,
22 1 to segMask ,
23 2 to laborParams
24 )
25
26 interpreter . runForMultipleInputsOutputs ( arrayOf ( input ) , outputs )
27
28 return LaborViewResult (
29 plane = planeLogits [ 0 ] . argmax ( ) ,
30 mask = segMask [ 0 ] . argmax2D ( ) ,
31 aop = laborParams [ 0 ] [ 0 ] ,
32 hsd = laborParams [ 0 ] [ 1 ]
33 )
34 }
35 }
1 from clinical_metrics import compute_all_metrics
2
3 # Get comprehensive clinical assessment from segmentation
4 metrics = compute_all_metrics (
5 segmentation_mask = mask ,
6 symphysis_class = 1 ,
7 head_class = 2
8 )
9
10 # Full output
11 print ( f"=== Clinical Assessment ===" )
12 print ( f"AoP: { metrics . aop : .1f } ° - { metrics . aop_interpretation } " )
13 print ( f"HSD: { metrics . hsd : .1f } px - { metrics . hsd_interpretation } " )
14 print ( f"Head Circumference: { metrics . head_circumference : .0f } px" )
15 print ( f"Head Area: { metrics . head_area : .0f } px²" )
16 print ( f"Quality: { metrics . segmentation_quality } ( { metrics . confidence : .0% } )" )
17 print ( f"Progress: { metrics . labor_progress . upper ( ) } " )
18 print ( f"Recommendation: { metrics . recommendation } " )
1 @software{laborview_edge_2024,
2 title = {LaborView Ultrasound: Edge Multi-Task Model for Labor Monitoring},
3 author = {Samuel},
4 year = {2024},
5 url = {https://huggingface.co/samwell/laborview-ultrasound},
6 note = {Multi-task MobileViT: segmentation + classification + regression}
7 }