A radiologist spends 20–40 minutes analyzing a single CT study with hundreds of slices. With a load of 30 studies per day, fatigue accumulates and small nodules on slices get missed. We develop AI systems that take over routine tasks: organ segmentation, nodule detection, volume measurement. Our experience: 10+ years in medical CV, 30+ deployed projects. The result — a 50% reduction in analysis time and significant cost savings for the clinic. We offer a turnkey solution from data collection to PACS integration.
Why AI for CT Is Harder than for X-Ray
Computed tomography produces three-dimensional data: a stack of 200–600 slices with thickness 0.5–5 mm. The key feature is Hounsfield units (HU), a quantitative measure of tissue density. AI must work in 3D, account for voxel anisotropy and HU ranges that differ by task (lungs: -1200..600 HU, soft tissues: -150..250 HU). Additionally, scanner and protocol variability require robust preprocessing.
How We Solve 3D Segmentation Problems
Stack: PyTorch, MONAI, nnU-Net. For preprocessing we use MONAI transforms — NIfTI loading, RAS orientation reorientation, resampling to isotropic spacing (1.5×1.5×2.0 mm), windowing by HU. The base architecture is a 3D U-Net with residual blocks.
import numpy as np
import torch
import nibabel as nib
from monai.transforms import (
Compose,
LoadImaged,
AddChanneld,
Orientationd,
Spacingd,
ScaleIntensityRanged,
CropForegroundd,
ResizeWithPadOrCropd,
ToTensord
)
class CTAnalysisSystem:
def __init__(self, model_path: str, task: str = 'lung_nodule'):
self.preprocessing = self._build_preprocessing(task)
self.model = self._load_model(model_path)
self.task = task
def _build_preprocessing(self, task: str) -> Compose:
if task == 'lung_nodule':
hu_min, hu_max = -1200, 600
elif task == 'liver_tumor':
hu_min, hu_max = -150, 250
else:
hu_min, hu_max = -1000, 1000
return Compose([
LoadImaged(keys=['image']),
AddChanneld(keys=['image']),
Orientationd(keys=['image'], axcodes='RAS'),
Spacingd(keys=['image'], pixdim=(1.5, 1.5, 2.0), mode='bilinear'),
ScaleIntensityRanged(
keys=['image'],
a_min=hu_min,
a_max=hu_max,
b_min=0.0,
b_max=1.0,
clip=True
),
ToTensord(keys=['image'])
])
def analyze(self, nifti_path: str) -> dict:
data = {'image': nifti_path}
data = self.preprocessing(data)
volume = data['image'].unsqueeze(0)
with torch.no_grad():
prediction = self.model(volume)
if self.task == 'lung_nodule':
return self._process_nodule_detection(prediction, data)
elif self.task == 'organ_segmentation':
return self._process_segmentation(prediction) MONAI and nnU-Net: Industry Standard
MONAI is a framework for medical CV from NVIDIA and King's College. nnU-Net is a self-configuring method: it automatically determines the optimal architecture and preprocessing for each dataset. Using MONAI reduces preprocessing code by a factor of 2 and increases Dice by 5% compared to manual implementation. We use nnU-Net as a baseline for organ segmentation.
from monai.networks.nets import UNet
from monai.losses import DiceCELoss
from monai.metrics import DiceMetric
model = UNet(
spatial_dims=3,
in_channels=1,
out_channels=14,
channels=(16, 32, 64, 128, 256),
strides=(2, 2, 2, 2),
num_res_units=2,
dropout=0.1
)
criterion = DiceCELoss(
include_background=False,
to_onehot_y=True,
softmax=True
)The pretrained TotalSegmentator model segments 104 anatomical structures on CT. We fine-tune it for specific client tasks (e.g., pancreas segmentation accounting for positional variability).
from totalsegmentator.python_api import totalsegmentator
totalsegmentator(
input='ct_scan.nii.gz',
output='segmentations/',
task='total',
fast=False
) Lung Nodule Detection: From LUNA16 to Production
Lung nodules are the first sign of lung cancer. The task: find nodules > 3 mm in a 3D volume. We build a pipeline: lung segmentation → detection (3D Retina U-Net) → postprocessing with clustering.
class NoduleDetector:
def __init__(self, model_path: str, min_nodule_mm: float = 3.0, confidence_threshold: float = 0.5):
self.model = load_nodule_model(model_path)
self.min_size = min_nodule_mm
self.threshold = confidence_threshold
def detect(self, ct_volume: np.ndarray, voxel_spacing: tuple) -> list[dict]:
lung_mask = self._segment_lung(ct_volume)
nodule_mask = self.model.predict(ct_volume * lung_mask)
nodules = self._extract_nodules(nodule_mask, voxel_spacing)
return [n for n in nodules if n['diameter_mm'] >= self.min_size and n['confidence'] >= self.threshold] What Does Quantitative Analysis Provide?
After segmentation, we measure organ and nodule volumes in ml, diameter per RECIST. This allows tracking tumor dynamics and evaluating therapy response.
def measure_volume_ml(mask: np.ndarray, voxel_spacing: tuple) -> float:
voxel_volume_mm3 = np.prod(voxel_spacing)
volume_mm3 = mask.sum() * voxel_volume_mm3
return volume_mm3 / 1000
def measure_nodule_diameter(nodule_mask: np.ndarray, voxel_spacing: tuple) -> dict:
coords = np.where(nodule_mask)
from scipy.spatial import ConvexHull
points = np.column_stack(coords) * np.array(voxel_spacing)
if len(points) < 4:
return {'diameter_mm': 0}
hull = ConvexHull(points)
max_dist = 0
hull_pts = points[hull.vertices]
for i in range(len(hull_pts)):
for j in range(i+1, len(hull_pts)):
d = np.linalg.norm(hull_pts[i] - hull_pts[j])
max_dist = max(max_dist, d)
return {'diameter_mm': round(max_dist, 2)} What Metrics Guarantee Quality?
On public datasets, our model achieves the following results:
| Task | Dataset | Metric | Value |
|---|---|---|---|
| Lung segmentation | LUNA16 | Dice | 0.98 |
| Nodule detection | LUNA16 | FROC | 0.89 |
| Liver segmentation | LiTS | Dice | 0.96 |
| Liver tumor segmentation | LiTS | Dice | 0.75 |
| Multi-organ | BTCV | Dice | 0.88 |
In production, we guarantee Dice no less than 0.95 for large organs and FROC > 0.85 for nodules. Each project is accompanied by a model card with metrics on stratified subgroups (age, sex, scanner type).
Process of Implementing an AI Module
| Stage | Duration | Result |
|---|---|---|
| Data analysis | 3–5 days | Data quality report, annotation recommendations |
| Preprocessing and augmentation | 5–7 days | Loading and normalization pipeline |
| Model selection and training | 2–4 weeks | Baseline with metrics, final architecture selection |
| Validation on independent test set | 1 week | Model card, metrics report |
| Deployment and integration | 1–2 weeks | Docker image, Triton Inference Server, DICOM gateway |
| Post-release monitoring | 3 months | Logging, alerts, weekly reports |
What Is Included in the Work
- Development of a preprocessing pipeline specific to the scanner and protocol.
- Selection and customization of architecture (nnU-Net, Retina U-Net, TotalSegmentator).
- Training and validation with metrics tracking.
- Containerization and deployment with Triton Inference Server.
- Integration with PACS via DICOM gateway and HL7/FHIR.
- Documentation: model card, operation manual, test report.
- Client team training (2-3 hour workshop).
- 3 months of post-release monitoring and support.
Our Experience and Guarantees
5+ years in the medical AI solutions market, 30+ completed projects in Russia and CIS. We guarantee quality: if metrics on the test set are below agreed thresholds, we refine for free. We provide a certificate of compliance with medical data processing standards. Development cost includes team training and 3 months of post-release monitoring.
Contact us — we will evaluate your data in 2 days, propose an architecture and realistic timelines. Order a pilot project: segmentation of one organ on 50 scans in 4 weeks.







