bruise-data 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bruise_data-1.2.0/PKG-INFO +607 -0
- bruise_data-1.2.0/README.md +588 -0
- bruise_data-1.2.0/bruise_data/__init__.py +16 -0
- bruise_data-1.2.0/bruise_data/__main__.py +92 -0
- bruise_data-1.2.0/bruise_data/checks.py +94 -0
- bruise_data-1.2.0/bruise_data/core.py +422 -0
- bruise_data-1.2.0/bruise_data/transforms.py +107 -0
- bruise_data-1.2.0/bruise_data.egg-info/PKG-INFO +607 -0
- bruise_data-1.2.0/bruise_data.egg-info/SOURCES.txt +14 -0
- bruise_data-1.2.0/bruise_data.egg-info/dependency_links.txt +1 -0
- bruise_data-1.2.0/bruise_data.egg-info/entry_points.txt +2 -0
- bruise_data-1.2.0/bruise_data.egg-info/requires.txt +7 -0
- bruise_data-1.2.0/bruise_data.egg-info/top_level.txt +1 -0
- bruise_data-1.2.0/pyproject.toml +48 -0
- bruise_data-1.2.0/setup.cfg +4 -0
- bruise_data-1.2.0/setup.py +6 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bruise-data
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: PyTorch loaders for the BRUISE_DATA collection (NIJ, Fenwick, ALS, mimics).
|
|
5
|
+
Keywords: pytorch,dataset,dataloader,bruise,segmentation,medical-imaging
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: torch>=1.12
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: pandas>=1.3
|
|
16
|
+
Requires-Dist: Pillow>=9
|
|
17
|
+
Provides-Extra: augment
|
|
18
|
+
Requires-Dist: albumentations; extra == "augment"
|
|
19
|
+
|
|
20
|
+
# BRUISE_DATA
|
|
21
|
+
|
|
22
|
+
All bruise images (NIJ and Fenwick) and bruise look-alike images (mimics) in one consistent layout, plus a PyTorch loader that gives you `train`, `val` and `test` DataLoaders for any mix of them in one line. New datasets plug in by adding a folder with a `manifest.csv` ([section 11.1](#111-adding-a-new-dataset-plug-and-play)):
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from bruise_data import get_dataloaders
|
|
26
|
+
train_dl, val_dl, test_dl = get_dataloaders(["nij", "fenwick", "mimics"])
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Dataset | What it is | Images (train / val / test) | Masks | Skin tone |
|
|
30
|
+
|---|---|---|---|---|
|
|
31
|
+
| `nij` | NIJ white-light bruise photos, Labelbox full-consensus subset | 664 / 167 / 185 (subjects: 92 / 23 / 28) | yes (Paul; majority vote on test) | L\*a\*b\* + ITA per visit |
|
|
32
|
+
| `nij_als` | NIJ ALS photos (415–535 nm), same subjects and folds as `nij` ([3.5](#35-als-nij_als-fenwick_als)) | ≈ 6,868 train+val / 1,459 test (July 2026 export; built on the server) | yes (Paul; majority vote on test) | as `nij` |
|
|
33
|
+
| `fenwick` | Fenwick white-light bruise photos | 1,536 / 385 / 127 (subjects: 85 / 20 / 15) | yes (mzehra2; majority vote on test) | L\*a\*b\* + ITA per lesion |
|
|
34
|
+
| `fenwick_als` | Fenwick ALS photos (410/455 nm), same subjects and folds as `fenwick` | counted when downloaded | yes (mzehra2; majority vote on test) | as `fenwick` |
|
|
35
|
+
| `mimics` | Open-source images of conditions that look like bruises, in 9 taxonomy classes | 4,360 / 543 / 524 | no bruise (empty mask) | Fitzpatrick / Monk where the source gives it |
|
|
36
|
+
| `extra_bruise` | Web bruise photos (no masks, classification only) | 68 / 12 / 8 | none | none |
|
|
37
|
+
|
|
38
|
+
Hard negatives (healthy skin, generic rashes, moles, chronic ulcers) are left out on purpose.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Contents
|
|
43
|
+
|
|
44
|
+
1. [Quick start](#1-quick-start)
|
|
45
|
+
2. [Folder layout](#2-folder-layout)
|
|
46
|
+
3. [The datasets](#3-the-datasets)
|
|
47
|
+
4. [Naming rules](#4-naming-rules)
|
|
48
|
+
5. [manifest.csv columns](#5-manifestcsv-columns)
|
|
49
|
+
6. [Using the dataloader](#6-using-the-dataloader)
|
|
50
|
+
7. [Recipes](#7-recipes)
|
|
51
|
+
8. [Skin tone (L\*a\*b\*)](#8-skin-tone-lab)
|
|
52
|
+
9. [Command-line tools](#9-command-line-tools)
|
|
53
|
+
10. [Running on the GPU server](#10-running-on-the-gpu-server)
|
|
54
|
+
11. [Rebuilding or adding data](#11-rebuilding-or-adding-data) (incl. [11.1 adding a new dataset](#111-adding-a-new-dataset-plug-and-play), [11.2 ALS data](#112-adding-the-als-data-nij_als-fenwick_als))
|
|
55
|
+
12. [Licences and data rules](#12-licences-and-data-rules)
|
|
56
|
+
13. [Troubleshooting](#13-troubleshooting)
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 1. Quick start
|
|
61
|
+
|
|
62
|
+
**Requirements:** Python 3.9+, `torch`, `numpy`, `pandas`, `Pillow`. Nothing else; `albumentations` is optional.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cd BRUISE_DATA
|
|
66
|
+
pip install -r requirements.txt # skip if torch/pandas/pillow are already installed
|
|
67
|
+
|
|
68
|
+
python -m bruise_data check # 1. confirm files are there and splits don't leak
|
|
69
|
+
python -m bruise_data summary # 2. see how many images each split has
|
|
70
|
+
python examples/quick_start.py # 3. load one batch and print what is inside
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
In your own script:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import sys; sys.path.insert(0, "/path/to/BRUISE_DATA") # not needed if you run from inside BRUISE_DATA
|
|
77
|
+
from bruise_data import get_dataloaders
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__": # required on Windows when num_workers > 0
|
|
80
|
+
train_dl, val_dl, test_dl = get_dataloaders(["nij", "fenwick", "mimics"], image_size=512, batch_size=8)
|
|
81
|
+
for batch in train_dl:
|
|
82
|
+
images = batch["image"] # float [8, 3, 512, 512] (ImageNet-normalised)
|
|
83
|
+
masks = batch["mask"] # float [8, 1, 512, 512] 1 = bruise; all zeros for mimics
|
|
84
|
+
labels = batch["label"] # int [8] 0 = bruise, 1.. = mimic classes
|
|
85
|
+
break
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 2. Folder layout
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
BRUISE_DATA/
|
|
94
|
+
├── README.md this file
|
|
95
|
+
├── requirements.txt
|
|
96
|
+
├── bruise_data/ the Python loader package (import bruise_data)
|
|
97
|
+
├── examples/ quick_start.py, train_segmentation.py
|
|
98
|
+
├── tools/build_bruise_data.py rebuilds every folder below from the original sources
|
|
99
|
+
├── tools/labelbox_als/ Labelbox ALS downloader for Hopper (notebook, Slurm job, README)
|
|
100
|
+
│
|
|
101
|
+
├── nij/
|
|
102
|
+
│ ├── train/ images/ masks/ primary annotator (paul)
|
|
103
|
+
│ ├── val/ images/ masks/ primary annotator, fold 0 of the subject 5-fold split
|
|
104
|
+
│ ├── test/ images/ masks/ majority vote of paul + gbarimah + erik
|
|
105
|
+
│ │ masks_by_annotator/{paul,gbarimah,erik}/
|
|
106
|
+
│ ├── alt_annotators/{gbarimah,erik}/{images,masks}/ other annotators' train pools
|
|
107
|
+
│ ├── metadata/skin_tone_lab.csv L*a*b* per subject / visit / arm
|
|
108
|
+
│ ├── metadata/skin_tone_lab_subject_median.csv
|
|
109
|
+
│ ├── metadata/original/ the untouched source CSVs
|
|
110
|
+
│ ├── reports/build_logs/ how the dataset was built (consensus filter, leakage check)
|
|
111
|
+
│ └── manifest.csv one row per image (and per annotator)
|
|
112
|
+
│
|
|
113
|
+
├── fenwick/ same layout; primary = mzehra2, alt = hliu36, nmousta5
|
|
114
|
+
│
|
|
115
|
+
├── nij_als/, fenwick_als/ same layout (train/ val/ test/ + masks_by_annotator/), no alt_annotators;
|
|
116
|
+
│ server only (≈ 55 GB), see section 11.2
|
|
117
|
+
│
|
|
118
|
+
├── mimics/
|
|
119
|
+
│ ├── <class>/<source_dataset>/<image> e.g. purpura/scin/..., burn/sunburn_roboflow/...
|
|
120
|
+
│ ├── manifest.csv
|
|
121
|
+
│ ├── mimic_class_map.csv every original diagnosis -> taxonomy class
|
|
122
|
+
│ └── README.md per-class counts and sources
|
|
123
|
+
│
|
|
124
|
+
└── extra_bruise/
|
|
125
|
+
├── <source_dataset>/<image>
|
|
126
|
+
└── manifest.csv
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Every image and mask is a hard link to the original file, so this tree takes almost no extra disk space on the laptop. The zip archives hold real copies.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 3. The datasets
|
|
134
|
+
|
|
135
|
+
### 3.1 NIJ (`nij/`)
|
|
136
|
+
|
|
137
|
+
- **What it is:** white-light (`_N`) photos of experimentally induced bruises on the upper and lower arm, from GMU and partner sites. Only images where all three Labelbox raters said "bruise visible" (full consensus) are included.
|
|
138
|
+
- **Annotators:**
|
|
139
|
+
- `paul` is the primary annotator; his masks are used for `train`/`val`.
|
|
140
|
+
- `gbarimah` (862 images) and `erik` (763 images) have their own train pools under `alt_annotators/`.
|
|
141
|
+
- **Test set:** 185 images from 28 subjects. Nobody's train pool contains these subjects.
|
|
142
|
+
- Masks: `test/masks/` holds the majority vote (a pixel is bruise if at least 2 of the 3 annotators marked it).
|
|
143
|
+
- `test/masks_by_annotator/` holds each annotator's own mask.
|
|
144
|
+
- **Validation:** fold 0 of a subject-level 5-fold split, so a subject is never in both train and val. Any other fold can be used instead (`val_fold=`).
|
|
145
|
+
- **Image names:** `AB012_V03_UA_N` means subject AB012, visit 3, upper arm (`UA`; `LA` = lower arm), white light.
|
|
146
|
+
|
|
147
|
+
### 3.2 Fenwick (`fenwick/`)
|
|
148
|
+
|
|
149
|
+
- **What it is:** white-light photos of bruises from the Fenwick study.
|
|
150
|
+
- **Annotators:** the top-3 labelers from the labeler-selection study.
|
|
151
|
+
- `mzehra2` is the primary annotator.
|
|
152
|
+
- `hliu36` (1,124 images) and `nmousta5` (507 images) are under `alt_annotators/`.
|
|
153
|
+
- **Test set:** 127 images from 15 subjects.
|
|
154
|
+
- Masks: majority vote of the three labelers in `test/masks/`, individual masks in `test/masks_by_annotator/`.
|
|
155
|
+
- **Validation:** fold 0 of the subject 5-fold split, same as NIJ.
|
|
156
|
+
- **Image names:** `123_F_2-4_WL_Bruise_...` means participant 123, lesion F (example IDs).
|
|
157
|
+
|
|
158
|
+
### 3.3 Mimics (`mimics/`)
|
|
159
|
+
|
|
160
|
+
Classes follow the bruise taxonomy (`BRUISE_LOOKALIKE/TAXONOMY/Bruise Taxonomy.pdf`): its non-bruise leaf classes that can be mistaken for a bruise. Each class folder has one sub-folder per source dataset.
|
|
161
|
+
|
|
162
|
+
| Class | Taxonomy branch | Contains | Total images |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `purpura` | Ecchymosis → Purpura / large ecchymosis | purpura, vasculitis, pigmented purpuric eruption, hemosiderin staining, erythema elevatum diutinum | 771 |
|
|
165
|
+
| `petechiae` | Ecchymosis → Petechiae (<3 mm) | petechiae | 10 (merged into `purpura` by default) |
|
|
166
|
+
| `hemangioma` | Vascular mimic → no blanching | hemangioma, port wine stain, pyogenic granuloma, Kaposi sarcoma, lymphangioma | 1,071 |
|
|
167
|
+
| `erythema` | Vascular mimic → blanches | cellulitis, erythema multiforme / nodosum / migrans, annular erythemas, urticaria, phytophotodermatitis | 1,197 |
|
|
168
|
+
| `pigmentary_lesion` | Pigmentary lesion (melanin, endogenous / exogenous) | melasma, hyperpigmentation, PIH, acanthosis nigricans, arsenical pigmentation, fixed drug eruption, dark circles | 1,521 |
|
|
169
|
+
| `burn` | Wound → Burn | thermal burns, sunburn, erythema ab igne | 589 |
|
|
170
|
+
| `abrasion` | Wound → Abrasion | scrapes, grazes | 138 |
|
|
171
|
+
| `laceration` | Wound → Laceration | blunt-force tears | 61 |
|
|
172
|
+
| `incised_wound` | Wound → Incised wound | cuts, stab wounds | 69 |
|
|
173
|
+
|
|
174
|
+
- **Splits:** train/val/test are stored in `manifest.csv`, not in folders, so the folders stay `class/source`. The split is about 80/10/10. Images from the same patient (SCIN case, PAD-UFES patient) and near-duplicate photos always share a split.
|
|
175
|
+
- **Filters applied:** short side ≥ 200 px, no cut-outs on white backgrounds, one image per near-duplicate group.
|
|
176
|
+
- **Exact mapping:** `mimic_class_map.csv` lists every original diagnosis name and its class. `mimics/README.md` gives counts per class, source and split.
|
|
177
|
+
- **Not filled yet:** `ecchymosis` (non-traumatic) and `surface_mark` (ink, paint, tattoo) have no open source yet, so they have no folder.
|
|
178
|
+
- **Mixed source to watch:** in `pigmentary_lesion`, 740 of 1,521 images are arsenicosis photos (`skin_diseases_normal_arsenic`), often palms with keratoses. Use `exclude_mimic_sources=["skin_diseases_normal_arsenic"]` if you want a cleaner class.
|
|
179
|
+
|
|
180
|
+
### 3.4 Extra bruise (`extra_bruise/`)
|
|
181
|
+
|
|
182
|
+
88 unique web photos of bruises from the Hugging Face "Clinical Wound Dataset" (`SaniaSalah/wound`) and the Kaggle wound dataset (`yasinpratomo`), with duplicates removed. They have no masks, so the loader only accepts them with `task="classification"`.
|
|
183
|
+
|
|
184
|
+
### 3.5 ALS (`nij_als/`, `fenwick_als/`)
|
|
185
|
+
|
|
186
|
+
- **What it is:** the same studies photographed under an alternate light source (narrow-band light + barrier filter), downloaded from Labelbox with `tools/labelbox_als/` (section 11.2).
|
|
187
|
+
- NIJ: wavelengths 415, 450, 475, 495, 515, 535 nm. The filter letter (`Y`/`O`/`R`) is in `light_filter`. UV photos are left out.
|
|
188
|
+
- Fenwick: `ALS_410_455`.
|
|
189
|
+
- **Selection:** the same rule as white light.
|
|
190
|
+
- NIJ: Paul's mask, and every Labelbox rater said "bruise visible".
|
|
191
|
+
- Fenwick: mzehra2 drew a bruise mask.
|
|
192
|
+
- Only the primary annotator is in train/val (no `alt_annotators/`).
|
|
193
|
+
- **Same subjects, same folds:** every subject keeps its white-light fold, and the white-light test subjects are the only ALS test subjects. So `get_dataloaders(["nij", "nij_als"])` is leak-free, and `check` verifies it across the pair. Subjects that only have ALS photos are added to the lightest folds.
|
|
194
|
+
- **Test set:** majority vote of the three annotators (paul / gbarimah / erik, or mzehra2 / hliu36 / nmousta5), as for white light. Individual masks are in `test/masks_by_annotator/`.
|
|
195
|
+
- **Image names:** `GM059_V11_LA_415_Y` = subject GM059, visit 11, lower arm, 415 nm, yellow filter. `182_K_2-6_ALS_410_455_Bruise_...` = Fenwick participant 182, lesion K.
|
|
196
|
+
- **Where:** built on the GPU server only. The NIJ part is about 55 GB, too large for the laptop or a zip.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 4. Naming rules
|
|
201
|
+
|
|
202
|
+
These are the same in every folder:
|
|
203
|
+
|
|
204
|
+
- **Splits** are always `train`, `val`, `test`. No "fixed", "holdout" or "minus".
|
|
205
|
+
- **Images** are in `images/`, **masks** in `masks/`. A mask has the image's stem plus `.png`. Mask pixels are 0 = background, 255 = bruise; the loader turns them into 0/1.
|
|
206
|
+
- **Folder and file names** are lower-case `snake_case`.
|
|
207
|
+
- **Dataset names** are the folder names: `nij`, `fenwick`, `mimics`, `extra_bruise`, plus any folder you add with a `manifest.csv`.
|
|
208
|
+
- **`image_id`** is `<dataset>__<original stem>`, unique across everything.
|
|
209
|
+
- **Skin tone** always lives in `metadata/skin_tone_lab.csv`.
|
|
210
|
+
- **Manifests:** each dataset has exactly one `manifest.csv`, and all manifests share the same columns (next section).
|
|
211
|
+
- **Paths** inside manifests are relative to `BRUISE_DATA/`, so the folder can be moved anywhere.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 5. `manifest.csv` columns
|
|
216
|
+
|
|
217
|
+
| Column | Meaning |
|
|
218
|
+
|---|---|
|
|
219
|
+
| `image_id` | Unique ID: `<dataset>__<stem>` |
|
|
220
|
+
| `dataset` | `nij`, `fenwick`, `mimics`, `extra_bruise` |
|
|
221
|
+
| `split` | `train` / `val` / `test` (for NIJ/Fenwick, `val` = fold 0; the loader can re-cut with `val_fold`) |
|
|
222
|
+
| `fold` | Subject-level fold 0–4 for NIJ/Fenwick train/val rows; `-1` otherwise |
|
|
223
|
+
| `class_name` | `bruise` or a mimic class |
|
|
224
|
+
| `is_bruise` | True for NIJ, Fenwick, extra_bruise |
|
|
225
|
+
| `source_dataset` | Where the image came from (`nij`, `fenwick`, `scin`, `dermnet`, ...) |
|
|
226
|
+
| `source_class` | Original label from that source (e.g. "Pigmented purpuric eruption") |
|
|
227
|
+
| `annotator` | Whose mask `mask_path` points to: `paul`, `gbarimah`, `erik`, `mzehra2`, `hliu36`, `nmousta5`, `majority` (test only), `none` (mimics) |
|
|
228
|
+
| `is_primary_annotator` | True for the default rows (primary annotator for train/val, majority vote for test) |
|
|
229
|
+
| `image_path`, `mask_path` | Relative to `BRUISE_DATA/`. `mask_path` is empty for mimics and extra_bruise |
|
|
230
|
+
| `subject_id` | Participant / patient / case ID (for leak-free splits) |
|
|
231
|
+
| `lesion_id` | Fenwick lesion letter |
|
|
232
|
+
| `visit` | NIJ visit number |
|
|
233
|
+
| `near_dup_group` | Mimics: images whose perceptual hash matches |
|
|
234
|
+
| `width`, `height` | Original image size in pixels |
|
|
235
|
+
| `mask_positive_pixels` | Bruise pixels in the mask (5 NIJ train images have empty masks) |
|
|
236
|
+
| `skin_L`, `skin_a`, `skin_b`, `skin_ita` | Colorimeter CIE L\*a\*b\* of normal skin next to the bruise, and ITA° |
|
|
237
|
+
| `lesion_L`, `lesion_a`, `lesion_b` | Colorimeter L\*a\*b\* on the bruise itself |
|
|
238
|
+
| `ita_category` | Very Light / Light / Intermediate / Tan / Brown / Dark |
|
|
239
|
+
| `fitzpatrick`, `monk_tone` | Mimics, where the source provides it |
|
|
240
|
+
| `body_site` | NIJ `upper_arm` / `lower_arm`; mimics where known |
|
|
241
|
+
| `modality`, `lighting` | Photo type; `lighting` is `white_light`, or `als` for `nij_als` / `fenwick_als` |
|
|
242
|
+
| `licence`, `commercial_use` | See [section 12](#12-licences-and-data-rules) |
|
|
243
|
+
| `original_file` | File name or path in the original source |
|
|
244
|
+
| `wavelength`, `light_filter` | ALS only: NIJ `415`…`535` + filter `Y`/`O`/`R`; Fenwick `ALS_410_455`. Empty for white light |
|
|
245
|
+
|
|
246
|
+
NIJ and Fenwick manifests have one row per image **per annotator**, so an image's masks from different annotators sit side by side. The loader always picks one annotator per dataset, so you never get the same image twice.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 6. Using the dataloader
|
|
251
|
+
|
|
252
|
+
### 6.1 `get_dataloaders(...)` → `(train_dl, val_dl, test_dl)`
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
get_dataloaders(
|
|
256
|
+
datasets=("nij", "fenwick"), # any dataset folder: "nij", "fenwick", "mimics", "extra_bruise", ... (list_datasets())
|
|
257
|
+
task="segmentation", # or "classification"
|
|
258
|
+
# --- which mimics -------------------------------------------------------------
|
|
259
|
+
mimic_classes="all", # or a list, e.g. ["purpura", "hemangioma"]
|
|
260
|
+
mimic_sources=None, # keep only these sources, e.g. ["scin", "fitzpatrick17k"]
|
|
261
|
+
exclude_mimic_sources=None, # drop these sources, e.g. ["dermnet"]
|
|
262
|
+
merge=None, # class renames; default {"petechiae": "purpura"}; {} = no merging
|
|
263
|
+
min_class_images=50, # with mimic_classes="all", skip classes smaller than this
|
|
264
|
+
binary=False, # True: labels are 0 = non_bruise (all mimic classes in one), 1 = bruise
|
|
265
|
+
commercial_only=False, # True = only images whose licence allows commercial use
|
|
266
|
+
# --- which masks and folds -----------------------------------------------------
|
|
267
|
+
annotator="primary", # train/val masks: "primary", an annotator name, or a dict
|
|
268
|
+
test_annotator="majority", # test masks: "majority", "primary", a name, or a dict
|
|
269
|
+
val_fold=0, # which subject fold (0-4) is val for NIJ/Fenwick
|
|
270
|
+
# --- images ------------------------------------------------------------------
|
|
271
|
+
image_size=512, # output is image_size x image_size
|
|
272
|
+
resize="pad", # "pad" keeps aspect ratio (black borders), "stretch" squashes
|
|
273
|
+
augment=True, # light train augmentation (crop, flip, ±15° rotation, brightness/contrast/saturation)
|
|
274
|
+
train_transform=None, # your own transform (e.g. albumentations) replaces the built-in one
|
|
275
|
+
eval_transform=None,
|
|
276
|
+
# --- loader ------------------------------------------------------------------
|
|
277
|
+
batch_size=8, eval_batch_size=None, num_workers=4,
|
|
278
|
+
balance="dataset", # None, "dataset", "class" or "source" (see below)
|
|
279
|
+
seed=42,
|
|
280
|
+
root=None, # BRUISE_DATA folder; default = the folder holding bruise_data/
|
|
281
|
+
verbose=True, # prints a count table
|
|
282
|
+
)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**What the loader does, step by step:**
|
|
286
|
+
|
|
287
|
+
1. Reads `manifest.csv` of each dataset you asked for.
|
|
288
|
+
2. Filters rows. Mimics are filtered by class, source and licence. NIJ/Fenwick rows are reduced to one annotator (`annotator` for train/val, `test_annotator` for test).
|
|
289
|
+
3. Assigns splits. For NIJ/Fenwick, `fold == val_fold` becomes `val`, the other folds `train`, and the test set never changes. Mimic splits come straight from the manifest.
|
|
290
|
+
4. Gives every class an integer label: `0 = bruise`, then the mimic classes in alphabetical order. The list is in `train_dl.dataset.class_names`. With `binary=True` there are only two, and bruise is the positive class: `0 = non_bruise`, `1 = bruise`.
|
|
291
|
+
5. For each image:
|
|
292
|
+
1. Loads it. Large JPEGs are decoded at reduced resolution, which is much faster for NIJ's 24 MP photos.
|
|
293
|
+
2. Loads the mask, or uses an all-zero mask for mimics.
|
|
294
|
+
3. Applies the transform and returns a dict.
|
|
295
|
+
6. Draws training batches with `WeightedRandomSampler` when `balance` is set:
|
|
296
|
+
- `"dataset"`: NIJ, Fenwick and mimics are drawn equally often. This stops 5,000 mimics from drowning 664 NIJ images.
|
|
297
|
+
- `"class"`: every class (bruise and each mimic class) is drawn equally often.
|
|
298
|
+
- `"source"`: every source dataset is drawn equally often.
|
|
299
|
+
- `None`: plain shuffling.
|
|
300
|
+
Val and test are never re-weighted or shuffled.
|
|
301
|
+
|
|
302
|
+
### 6.2 What one batch contains
|
|
303
|
+
|
|
304
|
+
| Key | Type / shape | Notes |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| `image` | float `[B, 3, S, S]` | ImageNet-normalised; `bruise_data.denormalize()` undoes it for plotting |
|
|
307
|
+
| `mask` | float `[B, 1, S, S]` | 1 = bruise. Segmentation only. All zeros for mimics |
|
|
308
|
+
| `label` | int `[B]` | index into `class_names` |
|
|
309
|
+
| `class_name`, `dataset`, `source_dataset`, `subject_id`, `image_id` | list of str | for analysis / per-group metrics |
|
|
310
|
+
| `is_bruise` | bool `[B]` | |
|
|
311
|
+
| `skin_L`, `skin_a`, `skin_b`, `skin_ita`, `lesion_L`, `lesion_a`, `lesion_b` | float `[B]` | NaN when unknown (mimics) |
|
|
312
|
+
| `fitzpatrick`, `monk_tone` | float `[B]` | NaN when unknown |
|
|
313
|
+
|
|
314
|
+
### 6.3 Other functions
|
|
315
|
+
|
|
316
|
+
| Function | Use |
|
|
317
|
+
|---|---|
|
|
318
|
+
| `get_test_loaders(datasets, ...)` | `{"nij": dl, "fenwick": dl, "mimics": dl}`, one test loader per dataset, to report each one separately |
|
|
319
|
+
| `get_datasets(datasets, ...)` | Returns `(train_ds, val_ds, test_ds)` PyTorch Datasets, if you want to build DataLoaders yourself |
|
|
320
|
+
| `load_index(datasets, ...)` | The filtered table as a pandas DataFrame: no images loaded, good for counting or custom splits |
|
|
321
|
+
| `summary(df)` | Count table per dataset / class / split |
|
|
322
|
+
| `check_splits()` | Runs all leakage and file checks; returns a list of problems |
|
|
323
|
+
| `BruiseTransform(image_size, train=...)` | The built-in transform, if you want to tweak it (`vflip=0.5`, `rotate=30`, `jitter=0`, `normalize=False`, ...) |
|
|
324
|
+
| `read_manifest("nij")` | Raw manifest as a DataFrame (missing optional columns filled with defaults) |
|
|
325
|
+
| `list_datasets()` | Every dataset found: each folder of `BRUISE_DATA/` that has a `manifest.csv` |
|
|
326
|
+
| `dataset_info("nij")` | What a manifest holds: annotators, primary, masks, majority-vote test, folds |
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 7. Recipes
|
|
331
|
+
|
|
332
|
+
**Bruise segmentation on NIJ only, primary annotator (the setup of earlier experiments):**
|
|
333
|
+
```python
|
|
334
|
+
train_dl, val_dl, test_dl = get_dataloaders(["nij"], balance=None)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**NIJ + Fenwick + all mimics as negatives (segmentation):**
|
|
338
|
+
```python
|
|
339
|
+
train_dl, val_dl, test_dl = get_dataloaders(["nij", "fenwick", "mimics"], balance="dataset")
|
|
340
|
+
```
|
|
341
|
+
Mimic images come with empty masks, so the model learns not to fire on purpura, burns, erythema and the rest.
|
|
342
|
+
|
|
343
|
+
**Only the strongest mimics, without DermNet:**
|
|
344
|
+
```python
|
|
345
|
+
get_dataloaders(["fenwick", "mimics"], mimic_classes=["purpura", "hemangioma", "pigmentary_lesion"],
|
|
346
|
+
exclude_mimic_sources=["dermnet"])
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Classification (bruise vs. each mimic class), including the extra web bruises:**
|
|
350
|
+
```python
|
|
351
|
+
train_dl, val_dl, test_dl = get_dataloaders(["nij", "fenwick", "mimics", "extra_bruise"],
|
|
352
|
+
task="classification", image_size=224, balance="class")
|
|
353
|
+
print(train_dl.dataset.class_names)
|
|
354
|
+
# ['bruise', 'abrasion', 'burn', 'erythema', 'hemangioma', 'incised_wound', 'laceration', 'pigmentary_lesion', 'purpura']
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Keep petechiae as its own class:**
|
|
358
|
+
```python
|
|
359
|
+
get_dataloaders(["mimics"], task="classification", merge={}, min_class_images=0)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Train on a different annotator; test against one annotator instead of the majority:**
|
|
363
|
+
```python
|
|
364
|
+
get_dataloaders(["nij"], annotator="gbarimah", test_annotator="paul")
|
|
365
|
+
get_dataloaders(["nij", "fenwick"], annotator={"nij": "erik", "fenwick": "hliu36"})
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**5-fold cross-validation:**
|
|
369
|
+
```python
|
|
370
|
+
for k in range(5):
|
|
371
|
+
train_dl, val_dl, test_dl = get_dataloaders(["nij", "fenwick"], val_fold=k, verbose=False)
|
|
372
|
+
...
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Your own albumentations pipeline** (the built-in transform is replaced; add Normalize + ToTensorV2 yourself):
|
|
376
|
+
```python
|
|
377
|
+
import albumentations as A
|
|
378
|
+
from albumentations.pytorch import ToTensorV2
|
|
379
|
+
train_tf = A.Compose([A.LongestMaxSize(640), A.PadIfNeeded(640, 640), A.RandomCrop(512, 512),
|
|
380
|
+
A.HorizontalFlip(), A.Normalize(), ToTensorV2()])
|
|
381
|
+
eval_tf = A.Compose([A.LongestMaxSize(512), A.PadIfNeeded(512, 512), A.Normalize(), ToTensorV2()])
|
|
382
|
+
get_dataloaders(["nij", "mimics"], train_transform=train_tf, eval_transform=eval_tf)
|
|
383
|
+
```
|
|
384
|
+
With a custom transform, images are decoded at full resolution, so your transform should do the resizing.
|
|
385
|
+
|
|
386
|
+
**Per-dataset test scores:**
|
|
387
|
+
```python
|
|
388
|
+
for name, dl in get_test_loaders(["nij", "fenwick", "mimics"], image_size=512).items():
|
|
389
|
+
print(name, evaluate(model, dl)) # mimics: count false-positive pixels / images
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Results by skin tone:**
|
|
393
|
+
```python
|
|
394
|
+
for batch in test_dl:
|
|
395
|
+
pred = model(batch["image"])
|
|
396
|
+
# batch["skin_ita"] / batch["skin_L"] / batch["subject_id"] let you group the metric
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**ALS (server only, section 3.5):**
|
|
400
|
+
```python
|
|
401
|
+
get_dataloaders(["nij", "nij_als", "fenwick", "fenwick_als"]) # white light + ALS, leak-free (shared folds)
|
|
402
|
+
get_dataloaders(["nij_als", "fenwick_als"]) # ALS only
|
|
403
|
+
tests = get_test_loaders(["nij", "nij_als"]) # report white light and ALS separately
|
|
404
|
+
|
|
405
|
+
# one wavelength only (e.g. 415 nm): filter the index, then build the Datasets yourself
|
|
406
|
+
from bruise_data import load_index, BruiseDataset, BruiseTransform
|
|
407
|
+
df = load_index(["nij_als"])
|
|
408
|
+
df = df[df.wavelength.astype(str) == "415"]
|
|
409
|
+
parts = {s: df[df.split == s].copy() for s in ("train", "val", "test")}
|
|
410
|
+
for part in parts.values():
|
|
411
|
+
part.attrs = dict(df.attrs) # keeps class names and root
|
|
412
|
+
train_ds = BruiseDataset(parts["train"], transform=BruiseTransform(512, train=True), fast_decode=1024)
|
|
413
|
+
val_ds = BruiseDataset(parts["val"], transform=BruiseTransform(512, train=False), fast_decode=1024)
|
|
414
|
+
test_ds = BruiseDataset(parts["test"], transform=BruiseTransform(512, train=False), fast_decode=1024)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Only the table (no images):**
|
|
418
|
+
```python
|
|
419
|
+
from bruise_data import load_index
|
|
420
|
+
df = load_index(["nij", "fenwick", "mimics"])
|
|
421
|
+
df.groupby(["split", "ita_category"]).size()
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
A full, runnable training loop is in `examples/train_segmentation.py`.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 8. Skin tone (L\*a\*b\*)
|
|
429
|
+
|
|
430
|
+
Both bruise datasets carry colorimeter readings measured during the study (CIE L\*a\*b\*). ITA° = arctan((L\* − 50) / b\*) × 180/π, and `ita_category` uses the standard bands: >55 Very Light, 41–55 Light, 28–41 Intermediate, 10–28 Tan, −30–10 Brown, < −30 Dark.
|
|
431
|
+
|
|
432
|
+
| | NIJ | Fenwick |
|
|
433
|
+
|---|---|---|
|
|
434
|
+
| File | `nij/metadata/skin_tone_lab.csv` | `fenwick/metadata/skin_tone_lab.csv` |
|
|
435
|
+
| Key | `subject_id, visit, arm` (UA/LA) | `subject_id, lesion_id` |
|
|
436
|
+
| Skin (`skin_*`) | "around" reading next to the bruise | `cie_skin_l/a/b` |
|
|
437
|
+
| Lesion (`lesion_*`) | "bruise" reading on the bruise | `cie_lesion_l/a/b` |
|
|
438
|
+
| Source | `final_upperarm_dataset.csv`, `final_lowerarm_dataset.csv` | REDCap export `IAL_Fenwick_10_12_2026.csv` |
|
|
439
|
+
| Fallback | subject median ("around", same arm) when the visit has no reading | none needed |
|
|
440
|
+
| Coverage | 99.7% of images (skin), 97.9% (lesion) | 100% |
|
|
441
|
+
|
|
442
|
+
The untouched source files are in each dataset's `metadata/original/`. Skin-tone values are already joined into every manifest row and returned in every batch.
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## 9. Command-line tools
|
|
447
|
+
|
|
448
|
+
Run from inside `BRUISE_DATA/`:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
python -m bruise_data datasets # every dataset found, its annotators / masks / folds
|
|
452
|
+
python -m bruise_data check # every dataset: files exist + no subject/near-duplicate leakage
|
|
453
|
+
python -m bruise_data check --deep 50 # also open 50 images per dataset, compare mask size
|
|
454
|
+
python -m bruise_data summary --datasets nij fenwick mimics
|
|
455
|
+
python -m bruise_data summary --datasets nij fenwick mimics extra_bruise --task classification
|
|
456
|
+
python -m bruise_data classes # mimic counts per class x source x split
|
|
457
|
+
python -m bruise_data preview --datasets nij mimics --out preview.jpg # 16 training images, bruise mask in red
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## 10. Running on the GPU server
|
|
463
|
+
|
|
464
|
+
1. Copy the data: unzip all four archives into the **same** folder, e.g. `/home/data/bruise/BRUISE_DATA/`. Every path in the manifests is relative, so nothing has to be edited.
|
|
465
|
+
| Archive | Contains | Size |
|
|
466
|
+
|---|---|---|
|
|
467
|
+
| `bruise_data_code.zip` | `README.md`, `requirements.txt`, `bruise_data/`, `examples/`, `tools/` | < 1 MB |
|
|
468
|
+
| `nij.zip` | `nij/` | 6.6 GB (4 GB of it is `alt_annotators/`) |
|
|
469
|
+
| `fenwick.zip` | `fenwick/` | 0.6 GB |
|
|
470
|
+
| `mimics.zip` | `mimics/` and `extra_bruise/` | 2.6 GB |
|
|
471
|
+
```bash
|
|
472
|
+
# on the laptop (Git Bash): send the zips (~10.4 GB)
|
|
473
|
+
scp C:/BRUISE_ORGANIZED/KIYARASH_DATASET/BRUISE_DATA_zips/*.zip tbommawa@mlidl:/home/data/bruise/
|
|
474
|
+
|
|
475
|
+
# on the server: unzip (needs ~11 GB more free space), make it readable for everyone, remove the zips
|
|
476
|
+
df -h /home/data
|
|
477
|
+
mkdir -p /home/data/bruise/BRUISE_DATA && cd /home/data/bruise/BRUISE_DATA
|
|
478
|
+
for z in bruise_data_code nij fenwick mimics; do unzip -q /home/data/bruise/$z.zip; done
|
|
479
|
+
chmod -R a+rX /home/data/bruise/BRUISE_DATA
|
|
480
|
+
rm /home/data/bruise/bruise_data_code.zip /home/data/bruise/nij.zip /home/data/bruise/fenwick.zip /home/data/bruise/mimics.zip
|
|
481
|
+
```
|
|
482
|
+
2. Activate your environment and check:
|
|
483
|
+
```bash
|
|
484
|
+
conda activate /home/tbommawa/conda_envs/bruise # or any env with torch, pandas, pillow
|
|
485
|
+
cd /home/data/bruise/BRUISE_DATA
|
|
486
|
+
python -m bruise_data check
|
|
487
|
+
python -m bruise_data summary --datasets nij fenwick mimics
|
|
488
|
+
python examples/quick_start.py
|
|
489
|
+
```
|
|
490
|
+
3. Use it from a training script anywhere:
|
|
491
|
+
```bash
|
|
492
|
+
export BRUISE_DATA_ROOT=/home/data/bruise/BRUISE_DATA
|
|
493
|
+
export PYTHONPATH=$BRUISE_DATA_ROOT:$PYTHONPATH
|
|
494
|
+
python my_train.py
|
|
495
|
+
```
|
|
496
|
+
or in Python: `get_dataloaders(..., root="/home/data/bruise/BRUISE_DATA")`.
|
|
497
|
+
4. Example training run:
|
|
498
|
+
```bash
|
|
499
|
+
python examples/train_segmentation.py --datasets nij fenwick mimics --epochs 2 --image-size 512 --batch-size 8
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Use `num_workers=8` or more on the server. Decoding the large NIJ JPEGs is the slowest step.
|
|
503
|
+
|
|
504
|
+
The ALS datasets (`nij_als`, `fenwick_als`) are not in these zips. They are built directly on the server: see [11.2](#112-adding-the-als-data-nij_als-fenwick_als).
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 11. Rebuilding or adding data
|
|
509
|
+
|
|
510
|
+
`tools/build_bruise_data.py` rebuilds every folder and manifest from the original sources in `C:/BRUISE_ORGANIZED/KIYARASH_DATASET`. It is safe to re-run: existing links are kept and manifests are rewritten.
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
python tools/build_bruise_data.py # everything
|
|
514
|
+
python tools/build_bruise_data.py --only mimics # one part
|
|
515
|
+
python -m bruise_data check # always re-check afterwards
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
- **Change a mimic class mapping:** edit `CLASS_MAP` at the top of the builder, re-run `--only mimics`.
|
|
519
|
+
- **Add Roboflow "Basic Wound Classify"** (needs a Roboflow API key): export it in *folder* format into `BRUISE_LOOKALIKE/downloads/basic_wound_classify/` (`<split>/<class>/*.jpg`), then re-run `--only mimics`. Abrasion, burn and laceration go to mimics, bruise to `extra_bruise`, and duplicates of existing images are removed automatically.
|
|
520
|
+
- **Splits are fixed.** NIJ/Fenwick folds come from the original subject split. Mimic splits come from the look-alike package. New web images get a split from a hash of their near-duplicate group, so re-running never moves an image to another split.
|
|
521
|
+
|
|
522
|
+
### 11.1 Adding a new dataset (plug-and-play)
|
|
523
|
+
|
|
524
|
+
The loader does not hard-code any dataset. **Every sub-folder of `BRUISE_DATA/` that holds a `manifest.csv` is a dataset**, and the loader treats it according to what its manifest contains. To add one:
|
|
525
|
+
|
|
526
|
+
1. Put the files in `BRUISE_DATA/<name>/`. Any layout works; `train|val|test/images|masks/` matches the other datasets.
|
|
527
|
+
2. Write `BRUISE_DATA/<name>/manifest.csv`.
|
|
528
|
+
3. Run `python -m bruise_data datasets` (it should appear in the list) and `python -m bruise_data check` (everything should PASS).
|
|
529
|
+
4. Use it: `get_dataloaders(["nij", "fenwick", "<name>"])`. No code changes are needed, including in the SegFormer script (`--datasets nij fenwick <name>`).
|
|
530
|
+
|
|
531
|
+
**Required columns** (only these five): `image_id`, `split` (`train`/`val`/`test`), `class_name`, `is_bruise`, `image_path` (relative to `BRUISE_DATA/`). Every other column in [section 5](#5-manifestcsv-columns) is optional; missing ones get defaults:
|
|
532
|
+
|
|
533
|
+
| Column | Default |
|
|
534
|
+
|---|---|
|
|
535
|
+
| `mask_path` | `""` (no mask) |
|
|
536
|
+
| `annotator` | `none` |
|
|
537
|
+
| `is_primary_annotator` | `True` |
|
|
538
|
+
| `fold` | `-1` |
|
|
539
|
+
| `subject_id` | `""` |
|
|
540
|
+
| `source_dataset` | the folder name |
|
|
541
|
+
| skin-tone columns | NaN |
|
|
542
|
+
|
|
543
|
+
**How the manifest decides the behaviour** (`dataset_info("<name>")` shows the result):
|
|
544
|
+
|
|
545
|
+
| The manifest has… | It is treated like | What the loader does |
|
|
546
|
+
|---|---|---|
|
|
547
|
+
| bruise rows with real annotator names (one flagged `is_primary_annotator=True`) | `nij`, `fenwick` | Trains on the chosen annotator's masks (`annotator=`, default the primary). If train/val rows have `fold` 0–4, val is re-cut with `val_fold`; otherwise the manifest's split is kept. Test uses rows with `annotator == "majority"` if present, otherwise the primary (with a warning). |
|
|
548
|
+
| bruise rows, no annotators | `extra_bruise` | Uses the rows as listed. Segmentation needs a `mask_path`, otherwise it is classification only. |
|
|
549
|
+
| no bruise rows (`is_bruise` all False) | `mimics` | Each image is a negative with an empty mask. `mimic_classes`, `mimic_sources`, `exclude_mimic_sources`, `merge` and `min_class_images` apply to all negative datasets together. |
|
|
550
|
+
|
|
551
|
+
**Rules that keep it leak-free:**
|
|
552
|
+
- For annotated bruise datasets, give every row a `subject_id`, and keep each subject in one fold and out of test. `check` verifies this.
|
|
553
|
+
- For negatives, set `near_dup_group` (and `subject_id` if known) so that duplicates stay in one split.
|
|
554
|
+
- Use `class_name="bruise"` for bruise images, and the taxonomy names for mimics so that classes merge across sources.
|
|
555
|
+
|
|
556
|
+
Minimal example: a new negative set with only the required columns.
|
|
557
|
+
```python
|
|
558
|
+
import pandas as pd
|
|
559
|
+
rows = [dict(image_id="newneg__001", split="train", class_name="purpura", is_bruise=False,
|
|
560
|
+
image_path="newneg/purpura/001.jpg")] # ... one dict per image
|
|
561
|
+
pd.DataFrame(rows).to_csv("BRUISE_DATA/newneg/manifest.csv", index=False)
|
|
562
|
+
```
|
|
563
|
+
An annotated bruise set adds `mask_path`, `annotator`, `is_primary_annotator`, `fold` and `subject_id`. It has one row per image per annotator, plus `annotator="majority"` rows on test if you have a consensus mask. For a dataset that is rebuilt from raw sources, add a `build_<name>()` function to `tools/build_bruise_data.py` (copy `build_fenwick`), so the manifest can be regenerated.
|
|
564
|
+
|
|
565
|
+
### 11.2 Adding the ALS data (`nij_als`, `fenwick_als`)
|
|
566
|
+
|
|
567
|
+
The ALS photos are downloaded from Labelbox on the Hopper cluster and built into BRUISE_DATA on mlidl. They are never on the laptop, which has too little space. Full steps with every command are in `tools/labelbox_als/README.md`. In short:
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
# Hopper (/scratch/tbommawa/als_download, after the one-time setup in that README)
|
|
571
|
+
sbatch --export=ALL,STUDY=nij run_als_download.slurm
|
|
572
|
+
sbatch --export=ALL,STUDY=fenwick run_als_download.slurm
|
|
573
|
+
# mlidl: pull the finished download, then build and check
|
|
574
|
+
mkdir -p /home/data/bruise/als_download && cd /home/data/bruise/als_download
|
|
575
|
+
ssh tbommawa@hopper.orc.gmu.edu 'cd /scratch/tbommawa/als_download && tar cf - labelbox_*_als/train_*_als_minus_test_subjects labelbox_*_als/fixed_consensus_test_als labelbox_*_als/parsed_tables' | tar xf -
|
|
576
|
+
cd /home/data/bruise/BRUISE_DATA
|
|
577
|
+
python tools/build_bruise_data.py --only nij_als fenwick_als --als-src /home/data/bruise/als_download
|
|
578
|
+
python -m bruise_data check
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
The builder needs `nij/` and `fenwick/` in the same BRUISE_DATA: it reads the subject folds, test subjects and skin tone from them. It does not rebuild them.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## 12. Licences and data rules
|
|
586
|
+
|
|
587
|
+
- **NIJ and Fenwick** are private study data. Never upload them to GitHub, a public bucket or a model hub.
|
|
588
|
+
- **Mimics** come from open datasets with different licences (`licence` and `commercial_use` columns). Non-commercial: DermNet, Fitzpatrick17k, SCIN, arsenic set. Commercial use allowed: Skin Burn (CC0), the Roboflow sets and MEMI-DS (CC BY 4.0), Clinical Wound (Apache-2.0). The Kaggle wound set (`wound_yasinpratomo`) has no stated licence. Use `commercial_only=True` to restrict.
|
|
589
|
+
- **Citations:** cite the source datasets in papers. Their URLs are in `BRUISE_LOOKALIKE/bruise_lookalike_negatives/sources.csv`.
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## 13. Troubleshooting
|
|
594
|
+
|
|
595
|
+
| Problem | Fix |
|
|
596
|
+
|---|---|
|
|
597
|
+
| `FileNotFoundError: .../manifest.csv` | `root` is wrong. Pass `root="/path/to/BRUISE_DATA"` or set `BRUISE_DATA_ROOT` |
|
|
598
|
+
| `ModuleNotFoundError: bruise_data` | Run from inside `BRUISE_DATA/` or add it to `PYTHONPATH` / `sys.path` |
|
|
599
|
+
| Windows: `RuntimeError ... freeze_support` or the script restarts | Put your code under `if __name__ == "__main__":`, or use `num_workers=0` |
|
|
600
|
+
| Training is slow | Raise `num_workers`; keep the built-in transform (it decodes big JPEGs at reduced scale); use a smaller `image_size` |
|
|
601
|
+
| `'extra_bruise' has no masks` | Use `task="classification"`, or drop `extra_bruise` (same for any bruise dataset without `mask_path`) |
|
|
602
|
+
| `unknown dataset(s)` | The name must be a folder of `BRUISE_DATA/` with a `manifest.csv`; `python -m bruise_data datasets` lists them |
|
|
603
|
+
| `missing required column(s)` | A new manifest needs `image_id, split, class_name, is_bruise, image_path` |
|
|
604
|
+
| `annotators [...] but none has is_primary_annotator=True` | Flag exactly one annotator's train/val rows as primary |
|
|
605
|
+
| Warning "mimic classes with < 50 images left out" | Expected for small classes. Name them in `mimic_classes`, or set `min_class_images=0` |
|
|
606
|
+
| `unknown mimic class` | Run `python -m bruise_data classes` to see valid names |
|
|
607
|
+
| A batch has NaN `skin_L` | Normal for mimics and the 0.3% of NIJ images without a colorimeter reading |
|