Metadata-Version: 2.5
Name: cardamage
Version: 0.1.3
Summary: Phát hiện tổn thất vật lý trên ô tô bằng Mask R-CNN R-101-DC5 (PyTorch thuần, không cần Detectron2)
Author: Naiscorp
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: car-damage,computer-vision,instance-segmentation,insurance,mask-rcnn
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: huggingface-hub>=0.24
Requires-Dist: numpy>=1.22
Requires-Dist: pillow>=9.0
Requires-Dist: safetensors>=0.4
Requires-Dist: torch>=2.0
Requires-Dist: torchvision>=0.15
Provides-Extra: demo
Requires-Dist: gradio>=4.0; extra == 'demo'
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Provides-Extra: onnx
Requires-Dist: onnxruntime>=1.16; extra == 'onnx'
Provides-Extra: serve
Requires-Dist: fastapi>=0.110; extra == 'serve'
Requires-Dist: python-multipart>=0.0.9; extra == 'serve'
Requires-Dist: uvicorn[standard]>=0.27; extra == 'serve'
Description-Content-Type: text/markdown

# Car Damage Mask R-CNN (R-101-DC5)

Instance segmentation of **7 types of physical damage on cars**, for insurance, rental and
resale inspection workflows. Given a photo, the model outputs a pixel mask, a bounding box,
a damage class and a confidence score for every damaged region it finds.

## Installation

```bash
pip install cardamage
```

## Quick start

```python
import torch
from PIL import Image
from cardamage import AutoModel

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = AutoModel.from_pretrained("Naiscorp/car-damage-maskrcnn-r101-dc5").to(device).eval()

overlay = model.inference(Image.open("car.jpg"))   # PIL.Image with masks drawn
overlay.save("damage.png")
```

Structured output instead of a picture:

```python
r = model.predict(Image.open("car.jpg"))
r["boxes"]      # (N, 4) float32, xyxy in ORIGINAL image coordinates
r["scores"]     # (N,)   float32
r["classes"]    # (N,)   int64, 0..6
r["labels"]     # list[str], Vietnamese
r["labels_en"]  # list[str], English
r["masks"]      # (N, H, W) bool, pasted back to the original resolution
```

## Labels

| id | Tiếng Việt | English |
|---:|---|---|
| 0 | Móp lõm | Dent |
| 1 | Trầy sơn | Paint scratch |
| 2 | Rách | Tear |
| 3 | Mất bộ phận | Missing part |
| 4 | Thủng | Puncture |
| 5 | Bể đèn | Broken lamp |
| 6 | Vỡ kính | Broken glass |

## Architecture

| | |
|---|---|
| Meta architecture | Mask R-CNN (`GeneralizedRCNN`) |
| Backbone | ResNet-101, **Dilated-C5** — `res5` dilation 2, single feature map, stride 16, 2048 ch |
| Normalisation | FrozenBatchNorm (all stages) |
| Region proposals | `StandardRPNHead`, 15 anchors/location (sizes 32–512, ratios 0.5/1/2) |
| ROI heads | `StandardROIHeads`, ROIAlignV2 7×7 → 2 × FC-1024 |
| Mask head | ROIAlignV2 14×14 → 4 × conv-256 → deconv → 1×1 conv, 28×28 output |
| Parameters | 190,900,534 (191,111,222 tensor entries including FrozenBN buffers) |
| Backbone init | ImageNet-pretrained `MSRA/R-101`; stem and `res2` frozen during training |

### Training schedule

| | |
|---|---|
| Batch size | 16 |
| Scheduled iterations | 270,000 |
| **Iterations in this checkpoint** | **59,999 — about 22% of the schedule** |
| LR schedule | `WarmupMultiStepLR`: 1,000-iter linear warmup from 2e-5 to `BASE_LR` 0.02, then ×0.1 at 210,000 and 250,000 |

> This is an **intermediate checkpoint**, not the end of the planned schedule. It is the one
> that has been running in production, which is why it is the one released.

Inference defaults (all overridable, all recorded in `config.json`):

| | |
|---|---|
| Input format | BGR, shortest edge 800 px, longest edge capped at 1333 px |
| Pixel mean / std | `[103.53, 116.28, 123.675]` / `[1.0, 1.0, 1.0]` |
| Score threshold | 0.7 |
| NMS threshold | 0.5 |
| Max detections | 100 per image |
| Mask binarisation | 0.5 |


## Training data

Internal dataset, collected in **Vietnam**, annotated with VGG Image
Annotator and converted to COCO instance-segmentation format. **The dataset itself is not
published.** The statistics below describe the annotated split the model was trained and
evaluated on.

| | Train | Test |
|---|---:|---:|
| Images | 2,085 | 417 |
| Annotated instances | 4,715 | 996 |

Per-class instance counts:

| Class | Train | Test |
|---|---:|---:|
| Trầy sơn / Paint scratch | 1,461 | 323 |
| Móp lõm / Dent | 1,087 | 235 |
| Vỡ kính / Broken glass | 692 | 127 |
| Rách / Tear | 679 | 140 |
| Mất bộ phận / Missing part | 472 | 90 |
| Bể đèn / Broken lamp | 185 | 42 |
| Thủng / Puncture | 139 | 39 |

![](https://huggingface.co/Naiscorp/car-damage-maskrcnn-r101-dc5/resolve/main/dataset_stats/01_instances_per_class.png)

<details>
<summary>More distribution charts</summary>

![](https://huggingface.co/Naiscorp/car-damage-maskrcnn-r101-dc5/resolve/main/dataset_stats/02_image_resolution.png)
![](https://huggingface.co/Naiscorp/car-damage-maskrcnn-r101-dc5/resolve/main/dataset_stats/03_damages_per_image.png)
![](https://huggingface.co/Naiscorp/car-damage-maskrcnn-r101-dc5/resolve/main/dataset_stats/04_damage_area.png)
![](https://huggingface.co/Naiscorp/car-damage-maskrcnn-r101-dc5/resolve/main/dataset_stats/05_centroid_distribution.png)

</details>

## Benchmarks

### Accuracy

Measured on the 417-image test split described above, taken from the internal project
report *Car Damage Analysis* (HCMC, June 2025):

| Model | AP50 ↑ | AP50-95 ↑ | AR ↑ |
|---|---:|---:|---:|
| **`mask_rcnn_R_101_DC5_3x` — this model** | **19.64** | **11.09** | **18.8** |
| `mask_rcnn_R_101_FPN_3x` — a later retrain, not released here | 21.97 | 11.92 | 18.7 |


### Latency

Measured directly on this checkpoint, 1280×720 input (resized to 1333×750), full pipeline
including preprocessing and mask pasting:

| Device | Median | Peak VRAM |
|---|---:|---:|
| NVIDIA RTX 4090 (fp32) | **81 ms/image** | 1.17 GB |

## Customisation

```python
model.score_thresh = 0.5          # default 0.7
model.inference(img, language="en", alpha=0.6, draw_boxes=False)
```

`predict()` returns raw arrays instead of a picture:

```python
r = model.predict(Image.open("car.jpg"))
r["boxes"]      # (N, 4) float32, xyxy in ORIGINAL image coordinates
r["scores"]     # (N,)   float32
r["classes"]    # (N,)   int64, 0..6
r["labels"]     # list[str], Vietnamese
r["labels_en"]  # list[str], English
r["masks"]      # (N, H, W) bool, pasted back to the original resolution
```

## Running with ONNX (no PyTorch)

```bash
pip install onnxruntime pillow numpy huggingface_hub
python onnx_run.py car.jpg result.png
```

`onnx_run.py` lives in the model repo and does preprocessing, box decoding and mask pasting in
plain NumPy.

## Parity with the original Detectron2

`model.py` is a re-implementation, so its output is checked against the Detectron2 build that
produced the weights, over 14 test images, box by box and mask pixel by mask pixel:

| Environment | Result |
|---|---|
| torch 2.5.1 + CUDA (the environment the reference was generated in) | **14/14 images bit-identical**, masks match pixel for pixel |
| torch 2.13 + CUDA | Same detection count; boxes within 2.1e-1 px, scores within 2.0e-3 |
| torch 2.5.1 + CPU | Same detection count; boxes within 3.0e+0 px, scores within 7.6e-4 |

Bit-exactness is tied to that exact torch version **and** device. Change either and results
stay numerically equivalent but stop being bit-identical — and when two detections have nearly
equal scores, the noise can **swap their order**. Do not rely on element order; filter on
`scores` and `classes`.

## License and attribution

**Weights** — `model.safetensors`, `CarDamage_R101_DC5.pth`, `CarDamage_R101_DC5.onnx`,
`config.json` and the images under `demo_pictures/`: © 2026 Naiscorp. Provided as-is, with no
warranty of any kind. The published metrics were measured on an internal Vietnamese test split
and are not a performance guarantee on your data; this is also an intermediate checkpoint
(59,999 of 270,000 scheduled iterations). Evaluate on your own data before relying on it, and
do not use it to decide insurance claims without a human adjuster reviewing the result.

**Code** — `model.py`, `inference.py`, `onnx_run.py`, `handler.py` and the
[`cardamage`](https://pypi.org/project/cardamage/) package: Apache-2.0.

`model.py` re-implements the inference algorithms of
[Detectron2](https://github.com/facebookresearch/detectron2) (Copyright 2019-present,
Facebook, Inc. — licensed under the Apache License, Version 2.0), keeping the original module
and parameter names so that checkpoints trained with Detectron2 load without any key
remapping. The re-implemented components are `FrozenBatchNorm2d`, `Conv2d`, `BottleneckBlock`,
`BasicStem`, `ResNetDC5`, `DefaultAnchorGenerator`, `Box2BoxTransform`, `StandardRPNHead`,
`find_top_rpn_proposals`, `StandardROIHeads`, `FastRCNNConvFCHead`, `FastRCNNOutputLayers`,
`MaskRCNNConvUpsampleHead`, `mask_rcnn_inference` and `paste_masks_in_image`. The backbone was
initialised from the ImageNet-pretrained MSRA R-101 weights distributed by Detectron2.
