organscan / preprocessor.json
vectorsense's picture
Publish OrganScan
c0abae0 verified
Raw
History Blame Contribute Delete
4.62 kB
{
"schema_version": "1.0.0",
"note": "The single source of truth for pixel handling. scripts/build_frame_dataset.py, scripts/train.py, src/organscan/preprocess.py and the exported ONNX graph all read this file, and scripts/export_onnx.py copies it into models/ so the shipped artifact carries its own contract. If training and serving ever disagree about normalisation, the model silently loses several points and nothing errors - hence one file, not four constants.",
"input_name": "pixel_values",
"dtype": "float32",
"dtype_note": "float32 in, even for the INT8 model. Static quantization inserts QuantizeLinear at the graph head; handing it uint8 is a silent accuracy loss.",
"layout": "NCHW",
"channels": 3,
"channels_note": "Always 3. Grayscale B-mode replicates across channels; colour Doppler uses them for real.",
"image_size": 224,
"dynamic_axes": { "batch": 0 },
"spatial_dims_fixed": true,
"spatial_dims_note": "H and W are fixed at 224. Dynamic spatial dims block ORT/NNAPI kernel selection and cost real latency on edge hardware.",
"pipeline": [
{ "step": 1, "op": "decode", "detail": "Honour TransferSyntaxUID. Multi-frame ultrasound is sampled by temporal stride, never frame-by-frame." },
{ "step": 2, "op": "photometric", "detail": "Invert when PhotometricInterpretation == MONOCHROME1." },
{ "step": 3, "op": "crop_scan_region", "detail": "Ultrasound: crop to SequenceOfUltrasoundRegions (0018,6011) RegionLocationMinX0/MinY0/MaxX1/MaxY1. Non-negotiable - vendor UI text often reads the organ name outright." },
{ "step": 4, "op": "intensity", "detail": "US is already 8-bit. CT/MR apply RescaleSlope/RescaleIntercept, then the fixed window below." },
{ "step": 5, "op": "resize", "detail": "Shortest side to 224, bilinear, antialias on." },
{ "step": 6, "op": "center_crop", "detail": "224 x 224." },
{ "step": 7, "op": "to_float", "detail": "Divide by 255 into [0, 1]." },
{ "step": 8, "op": "normalize", "detail": "Subtract mean, divide by std." },
{ "step": 9, "op": "layout", "detail": "HWC -> CHW, stack to [B, 3, 224, 224]." }
],
"resize": {
"shortest_side": 224,
"interpolation": "bilinear",
"antialias": true
},
"center_crop": 224,
"rescale_factor": 0.00392156862745098,
"image_mean": [0.485, 0.456, 0.406],
"image_std": [0.229, 0.224, 0.225],
"normalization_note": "ImageNet statistics, because the student is ImageNet-initialised. scripts/export_onnx.py re-reads the chosen timm config at export time and fails if it disagrees with these values rather than letting them drift.",
"ct_windows": {
"note": "Fixed window per region, applied to Hounsfield units after rescale. `pseudo_rgb` stacks three windows into the three channels instead of replicating one - free accuracy on CT, and the reason channels is 3 rather than 1.",
"default": { "center": 40, "width": 400 },
"abdomen": { "center": 40, "width": 400 },
"chest": { "center": -600, "width": 1500 },
"head": { "center": 40, "width": 80 },
"extremity": { "center": 300, "width": 1500 },
"pseudo_rgb": {
"enabled": true,
"channels": [
{ "center": 40, "width": 400, "label": "soft_tissue" },
{ "center": -600, "width": 1500, "label": "lung" },
{ "center": 300, "width": 1500, "label": "bone" }
]
}
},
"mr_intensity": {
"note": "MR has no absolute scale, so a fixed window is meaningless. Percentile clip then min-max into [0,1].",
"clip_percentiles": [0.5, 99.5]
},
"ultrasound": {
"require_region_crop": true,
"require_region_crop_note": "A US frame without a successful region crop is rejected by scripts/build_frame_dataset.py rather than used. Burned-in annotation is a label leak that produces a 99% model which fails on every other scanner (plan section 8.1).",
"frames_per_loop": { "min": 5, "max": 20, "selection": "temporal_stride" }
},
"train_augmentation": {
"note": "Deliberately mild. Anatomy classification is left/right sensitive - a horizontal flip turns KIDNEY_LEFT into KIDNEY_RIGHT, so it is off everywhere.",
"horizontal_flip": 0.0,
"horizontal_flip_note": "Zero, permanently. Enabling it silently destroys every laterality label in the corpus.",
"vertical_flip": 0.0,
"random_resized_crop": { "enabled": true, "scale": [0.7, 1.0], "ratio": [0.85, 1.18] },
"rotation_degrees": 10,
"brightness": 0.2,
"contrast": 0.2,
"gamma": [0.8, 1.25],
"gaussian_noise_std": 0.02,
"speckle_noise": { "enabled": true, "modalities": ["US"], "std": 0.05 },
"random_erasing": 0.1
}
}