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.







