Update README.md
Browse files
README.md
CHANGED
|
@@ -8,7 +8,9 @@ tags:
|
|
| 8 |
- resnet50
|
| 9 |
- fpn
|
| 10 |
- medical
|
|
|
|
| 11 |
- pytorch
|
|
|
|
| 12 |
pipeline_tag: image-segmentation
|
| 13 |
---
|
| 14 |
|
|
@@ -18,41 +20,62 @@ pipeline_tag: image-segmentation
|
|
| 18 |
|
| 19 |
# MediSeG — Mask R-CNN for Medicine Box Instance Segmentation
|
| 20 |
|
| 21 |
-
Custom **Mask R-CNN** (ResNet-50 + FPN + RPN + ROI Align, implemented from scratch in PyTorch) that detects and segments medicine
|
| 22 |
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
- **Neck**: Feature Pyramid Network (FPN), levels P2–P5, 256 channels.
|
| 27 |
-
- **Region proposal**: RPN with 3 anchor ratios per level (0.5, 1.0, 2.0), one fixed anchor scale per level (32/64/128/256).
|
| 28 |
-
- **ROI heads**: shared two-layer MLP trunk → classification head + box regression head (7×7 ROI Align) and a 4-conv + deconv mask head (14×14 → 28×28 ROI Align).
|
| 29 |
-
- **Classes**: 2 (`background`, `Med-Box`).
|
| 30 |
-
- **Input size**: 640×640 RGB.
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
## Intended use
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
|
| 38 |
## Training data
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
## Training procedure
|
| 45 |
|
| 46 |
-
-
|
| 47 |
-
-
|
| 48 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
## Evaluation
|
| 51 |
|
| 52 |
-
|
| 53 |
|
| 54 |
```bash
|
| 55 |
-
python -m
|
| 56 |
```
|
| 57 |
|
| 58 |
| Metric | Value |
|
|
@@ -62,34 +85,100 @@ python -m app.evaluate
|
|
| 62 |
|
| 63 |
## How to use
|
| 64 |
|
|
|
|
|
|
|
| 65 |
```python
|
| 66 |
import torch
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
|
|
|
| 69 |
model = MaskRCNN(num_classes=2, pretrained_backbone=False)
|
| 70 |
-
|
| 71 |
-
model.load_state_dict(checkpoint["model_state_dict"])
|
| 72 |
model.eval()
|
| 73 |
|
| 74 |
-
image =
|
| 75 |
with torch.no_grad():
|
| 76 |
-
detections = model(image) #
|
|
|
|
|
|
|
|
|
|
| 77 |
```
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
## Limitations
|
| 80 |
|
| 81 |
-
- Single class (`
|
| 82 |
-
-
|
| 83 |
-
-
|
|
|
|
|
|
|
| 84 |
|
| 85 |
## Citation
|
| 86 |
|
| 87 |
-
If you use this model, please cite the repository:
|
| 88 |
-
|
| 89 |
```
|
| 90 |
@misc{mediseg2026,
|
| 91 |
title = {MediSeG: Mask R-CNN for Medicine Box Instance Segmentation},
|
| 92 |
year = {2026},
|
| 93 |
-
url = {
|
| 94 |
}
|
| 95 |
```
|
|
|
|
| 8 |
- resnet50
|
| 9 |
- fpn
|
| 10 |
- medical
|
| 11 |
+
- ocr
|
| 12 |
- pytorch
|
| 13 |
+
- safetensors
|
| 14 |
pipeline_tag: image-segmentation
|
| 15 |
---
|
| 16 |
|
|
|
|
| 20 |
|
| 21 |
# MediSeG — Mask R-CNN for Medicine Box Instance Segmentation
|
| 22 |
|
| 23 |
+
Custom **Mask R-CNN** (ResNet-50 + FPN + RPN + ROI Align, implemented from scratch in PyTorch — no `torchvision.models.detection`) that detects and segments medicine packages (`medicine_box`) in photos.
|
| 24 |
|
| 25 |
+
The model is the first stage of the MediSeG pipeline: **detection + mask → masked crop → OCR (French + Arabic) → LLM identification → web search → annotated summary**.
|
| 26 |
+
|
| 27 |
+
<p align="center">
|
| 28 |
+
<img src="assets/examples/01_multi_box_detections.jpg" alt="Mask R-CNN detections on three medicine boxes" width="49%">
|
| 29 |
+
<img src="assets/examples/01_multi_box_annotated.jpg" alt="Full pipeline output: each box identified with name, dosage, form and active ingredient" width="49%">
|
| 30 |
+
</p>
|
| 31 |
+
<p align="center"><em>Left: Mask R-CNN output (boxes + masks + scores). Right: full pipeline output (OCR + LLM + web search).</em></p>
|
| 32 |
|
| 33 |
+
## Model description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
- **Backbone**: ResNet-50 (ImageNet-pretrained), feature maps C2–C5; the top 3 ResNet stages are fine-tuned, BatchNorm stays frozen.
|
| 36 |
+
- **Neck**: Feature Pyramid Network (FPN), 5 levels, 256 channels.
|
| 37 |
+
- **Region proposal**: RPN, one anchor size per level (32 / 64 / 128 / 256 / 512) × 3 aspect ratios (0.5, 1.0, 2.0); NMS 0.7, 300 proposals kept at test time.
|
| 38 |
+
- **ROI heads**: two-layer MLP trunk → classification head + class-specific box regression head (7×7 ROI Align); 4-conv + deconv mask head (14×14 ROI Align → 28×28 masks).
|
| 39 |
+
- **Classes**: 2 (`0 = background`, `1 = medicine_box`).
|
| 40 |
+
- **Input**: RGB, letterboxed to 640×640 (aspect ratio preserved, black padding, top-left aligned).
|
| 41 |
+
- **Weights**: `checkpoints/best_model.safetensors` in this repository.
|
| 42 |
|
| 43 |
## Intended use
|
| 44 |
|
| 45 |
+
Locate and segment medicine packages (boxes, tubes, sachets) in photos, typically to crop them for OCR — e.g. pharmacy tooling or assistive apps that read a medicine's label.
|
| 46 |
+
|
| 47 |
+
**Not** validated for clinical or safety-critical use. The downstream LLM identification can be wrong (see [Limitations](#limitations)); never rely on it to decide what medicine to take.
|
| 48 |
|
| 49 |
## Training data
|
| 50 |
|
| 51 |
+
Built from two public sources merged into one COCO instance-segmentation dataset (full report: `data/report.md` in the source repository):
|
| 52 |
+
|
| 53 |
+
| Split | Images | Instances |
|
| 54 |
+
|---|---:|---:|
|
| 55 |
+
| train | 540 | 806 |
|
| 56 |
+
| val | 68 | 121 |
|
| 57 |
+
| test | 68 | 100 |
|
| 58 |
+
|
| 59 |
+
- Sources: `main_ar_fr` (French/Arabic packages, 4-point polygons) and `medicine_packv2` (polygons). A third source with bounding boxes only was excluded.
|
| 60 |
+
- Split 80/10/10 with seed 42, **grouped by source photo** (near-duplicates merged by perceptual hash) to avoid train/test leakage — the original splits leaked 128 images.
|
| 61 |
+
- Single class: `medicine_box`.
|
| 62 |
|
| 63 |
## Training procedure
|
| 64 |
|
| 65 |
+
- SGD, `lr=0.01`, `momentum=0.9`, `weight_decay=1e-4`, batch size 8, 30 epochs.
|
| 66 |
+
- Linear warmup over 200 iterations, LR ×0.1 at epochs 20 and 26, gradient clipping at 10, mixed precision on GPU.
|
| 67 |
+
- Losses (equal weights): RPN objectness (BCE) + RPN box (smooth-L1), ROI classification (cross-entropy) + ROI box (smooth-L1), mask (BCE on 28×28).
|
| 68 |
+
- Sampling: RPN anchors positive at IoU ≥ 0.7 / negative < 0.3 (256 per image, 50 % positive); ROI proposals positive at IoU ≥ 0.5 (512 per image, 25 % positive).
|
| 69 |
+
- Augmentation: random horizontal flip (p = 0.5).
|
| 70 |
+
|
| 71 |
+
All hyperparameters live in `model_rcnn_scratch/config.py`.
|
| 72 |
|
| 73 |
## Evaluation
|
| 74 |
|
| 75 |
+
Dice and IoU on the held-out test split (union of predicted masks with score ≥ 0.5 vs. union of ground-truth masks, per image):
|
| 76 |
|
| 77 |
```bash
|
| 78 |
+
python -m model_rcnn_scratch.evaluate
|
| 79 |
```
|
| 80 |
|
| 81 |
| Metric | Value |
|
|
|
|
| 85 |
|
| 86 |
## How to use
|
| 87 |
|
| 88 |
+
### Weights only
|
| 89 |
+
|
| 90 |
```python
|
| 91 |
import torch
|
| 92 |
+
import torchvision.transforms.functional as TF
|
| 93 |
+
from huggingface_hub import hf_hub_download
|
| 94 |
+
from PIL import Image
|
| 95 |
+
from safetensors.torch import load_file
|
| 96 |
+
|
| 97 |
+
from model_rcnn_scratch.dataset import letterbox
|
| 98 |
+
from model_rcnn_scratch.model_architecture.mask_rcnn_full import MaskRCNN
|
| 99 |
|
| 100 |
+
weights = hf_hub_download("ApyHTML19/MediSeg-Mask-RCNN", "checkpoints/best_model.safetensors")
|
| 101 |
model = MaskRCNN(num_classes=2, pretrained_backbone=False)
|
| 102 |
+
model.load_state_dict(load_file(weights, device="cpu"))
|
|
|
|
| 103 |
model.eval()
|
| 104 |
|
| 105 |
+
image, scale = letterbox(Image.open("photo.jpg").convert("RGB")) # 640x640, aspect ratio kept
|
| 106 |
with torch.no_grad():
|
| 107 |
+
detections = model(TF.to_tensor(image).unsqueeze(0))[0] # {boxes, labels, scores, masks}
|
| 108 |
+
|
| 109 |
+
keep = detections["scores"] >= 0.5
|
| 110 |
+
boxes = detections["boxes"][keep] / scale # back to original image coordinates
|
| 111 |
```
|
| 112 |
|
| 113 |
+
The model code lives in the source repository (`model_rcnn_scratch/`); `model_rcnn_scratch.hub.load_model()` does the download + loading above in one call.
|
| 114 |
+
|
| 115 |
+
### Command line
|
| 116 |
+
|
| 117 |
+
```bash
|
| 118 |
+
python -m model_rcnn_scratch.predict path/to/photo.jpg # detection only -> outputs_results/<name>_pred.png
|
| 119 |
+
python -m harness.pipeline path/to/photo.jpg # detection + masked crop + OCR, no API key needed
|
| 120 |
+
python -m operation.run path/to/photo.jpg # full pipeline (needs OPENAI_API_KEY + TAVILY_API_KEY)
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
Full pipeline outputs, in `outputs_results/pipeline/`: `<name>_detections.jpg`, `<name>_box<i>.png` (masked crop), `<name>_analysis.json`, `<name>_annotated.jpg`.
|
| 124 |
+
|
| 125 |
+
## Examples
|
| 126 |
+
|
| 127 |
+
All images below are real outputs of the model and pipeline, stored in [`assets/examples/`](assets/examples/).
|
| 128 |
+
|
| 129 |
+
### Several boxes, French labels
|
| 130 |
+
|
| 131 |
+
<p align="center">
|
| 132 |
+
<img src="assets/examples/01_multi_box_crop0.png" alt="Masked crop of the Doliprane box" width="35%">
|
| 133 |
+
</p>
|
| 134 |
+
|
| 135 |
+
Masked crop (`<name>_box0.png`) sent to OCR. The three boxes are detected with score ≈ 1.00; the pipeline identifies **Doliprane 1000 mg**, **Ibuprofène Mylan 200 mg** and **Efferalgan 500 mg**, each with its active ingredient (paracetamol / ibuprofen).
|
| 136 |
+
|
| 137 |
+
### Bilingual French / Arabic tube and box
|
| 138 |
+
|
| 139 |
+
<p align="center">
|
| 140 |
+
<img src="assets/examples/02_bilingual_tube_annotated.jpg" alt="Aureomycine tube and box, French and Arabic labels" width="80%">
|
| 141 |
+
</p>
|
| 142 |
+
|
| 143 |
+
The French tube (#0) and the Arabic box (#1) are both identified as **Auréomycine 1 %** (chlortetracycline, ophthalmic ointment). Box #2 is a duplicate partial detection of the tube's label.
|
| 144 |
+
|
| 145 |
+
### Arabic-only label
|
| 146 |
+
|
| 147 |
+
<p align="center">
|
| 148 |
+
<img src="assets/examples/03_arabic_box_annotated.jpg" alt="Vermox box with an Arabic label" width="60%">
|
| 149 |
+
</p>
|
| 150 |
+
|
| 151 |
+
Arabic OCR reads **فيرموكسمجم** ("Vermox mg", words merged), Latin OCR reads **500** and **janssen**, and the LLM resolves it to **Vermox 500 mg** (mebendazole).
|
| 152 |
+
|
| 153 |
+
### Failure cases
|
| 154 |
+
|
| 155 |
+
<p align="center">
|
| 156 |
+
<img src="assets/examples/04_failure_false_positives_annotated.jpg" alt="False positives on a glass and a text block" width="60%">
|
| 157 |
+
</p>
|
| 158 |
+
|
| 159 |
+
Advertising image: the Propalgina Plus box (#0) is correct, but the model also fires on a **block of text** (#1, score 0.78) and a **glass** (#2, score 0.50). The LLM then "identifies" the text block as paracetamol 500 mg from the words it contains.
|
| 160 |
+
|
| 161 |
+
<p align="center">
|
| 162 |
+
<img src="assets/examples/05_failure_llm_misid_detections.jpg" alt="Two overlapping Colocalm boxes, correctly segmented" width="49%">
|
| 163 |
+
<img src="assets/examples/05_failure_llm_misid_annotated.jpg" alt="LLM misidentification of one box as Lokelma" width="49%">
|
| 164 |
+
</p>
|
| 165 |
+
|
| 166 |
+
Segmentation of the two overlapping boxes is correct and the OCR reads the Arabic brand name correctly (كولوكالم, Colocalm — mebeverine), but the LLM maps it to **Lokelma**, a different medicine. Box #1 gets the right active ingredient without a name.
|
| 167 |
+
|
| 168 |
## Limitations
|
| 169 |
|
| 170 |
+
- Single class (`medicine_box`); not a general-purpose detector. Any rectangular, label-like region (text panels, product packaging, glasses) can trigger a detection — see the failure cases above. Raise the score threshold (default 0.5) to trade recall for precision.
|
| 171 |
+
- Small dataset (540 training images), mostly French/Arabic packages photographed on plain backgrounds — expect weaker results on cluttered scenes, blisters, bottles or other scripts.
|
| 172 |
+
- Overlapping boxes may produce duplicate or partial detections.
|
| 173 |
+
- The identification stage depends on OCR quality and on an external LLM + web search; it can return a wrong medicine with high confidence. Treat it as a hint, not a diagnosis.
|
| 174 |
+
- `Backbone_RES` downloads ImageNet ResNet-50 weights when training from scratch (not needed for inference with `pretrained_backbone=False`).
|
| 175 |
|
| 176 |
## Citation
|
| 177 |
|
|
|
|
|
|
|
| 178 |
```
|
| 179 |
@misc{mediseg2026,
|
| 180 |
title = {MediSeG: Mask R-CNN for Medicine Box Instance Segmentation},
|
| 181 |
year = {2026},
|
| 182 |
+
url = {https://huggingface.co/ApyHTML19/MediSeg-Mask-RCNN}
|
| 183 |
}
|
| 184 |
```
|