dsimaging-admin 0.2.0__tar.gz → 0.3.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.
- dsimaging_admin-0.3.0/IMPROVEMENTS.md +244 -0
- dsimaging_admin-0.3.0/PKG-INFO +164 -0
- dsimaging_admin-0.3.0/README.md +139 -0
- {dsimaging_admin-0.2.0 → dsimaging_admin-0.3.0}/pyproject.toml +2 -1
- {dsimaging_admin-0.2.0 → dsimaging_admin-0.3.0}/src/dsimaging_admin/__init__.py +1 -1
- dsimaging_admin-0.3.0/src/dsimaging_admin/cli.py +985 -0
- dsimaging_admin-0.3.0/src/dsimaging_admin/controller.py +74 -0
- {dsimaging_admin-0.2.0 → dsimaging_admin-0.3.0}/src/dsimaging_admin/manifest.py +60 -0
- dsimaging_admin-0.3.0/src/dsimaging_admin/s3.py +177 -0
- dsimaging_admin-0.3.0/src/dsimaging_admin/store.py +304 -0
- dsimaging_admin-0.3.0/src/dsimaging_admin/verify.py +220 -0
- dsimaging_admin-0.3.0/tests/test_admin_features.py +204 -0
- dsimaging_admin-0.2.0/PKG-INFO +0 -139
- dsimaging_admin-0.2.0/README.md +0 -115
- dsimaging_admin-0.2.0/src/dsimaging_admin/cli.py +0 -498
- dsimaging_admin-0.2.0/src/dsimaging_admin/s3.py +0 -64
- {dsimaging_admin-0.2.0 → dsimaging_admin-0.3.0}/.gitignore +0 -0
- {dsimaging_admin-0.2.0 → dsimaging_admin-0.3.0}/LICENSE +0 -0
- {dsimaging_admin-0.2.0 → dsimaging_admin-0.3.0}/src/dsimaging_admin/hashing.py +0 -0
- {dsimaging_admin-0.2.0 → dsimaging_admin-0.3.0}/tests/test_manifest.py +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# IMPROVEMENTS
|
|
2
|
+
|
|
3
|
+
Candidate improvements to make `dsimaging-admin` a complete administration tool
|
|
4
|
+
for `dsimaging-store`, beyond the happy-path coverage shipped in **v0.2.0**.
|
|
5
|
+
|
|
6
|
+
This is a roadmap, not a backlog. Items are prioritised by how much real
|
|
7
|
+
operator pain they remove on the current `dsImaging` / `dsHPC` deployment.
|
|
8
|
+
|
|
9
|
+
## Current coverage (v0.2.0)
|
|
10
|
+
|
|
11
|
+
| Command | What it does |
|
|
12
|
+
|-----------|------------------------------------------------------------------------|
|
|
13
|
+
| `init` | Write `~/.dsimaging.yaml`. |
|
|
14
|
+
| `publish` | Local dir -> S3 (upload + indexes + manifest, optional Opal register). |
|
|
15
|
+
| `rescan` | Recompute indexes / manifest from current S3 contents. |
|
|
16
|
+
| `list` | Enumerate datasets; `published` if `manifest.yaml` exists. |
|
|
17
|
+
| `doctor` | Check S3 connectivity, bucket existence, versioning, dataset count. |
|
|
18
|
+
|
|
19
|
+
Two structural observations that motivate most of what follows:
|
|
20
|
+
|
|
21
|
+
1. The CLI does **not** talk to the `dsimaging-store` controller
|
|
22
|
+
(`:8080/health`, `:8080/datasets`). All status comes from S3 directly.
|
|
23
|
+
2. `cli.py::_write_dataset_artifacts` and
|
|
24
|
+
`dsimaging-store/controller/controller.py::write_dataset_artifacts`
|
|
25
|
+
re-implement the same scan / hash / manifest pipeline in parallel. They
|
|
26
|
+
already differ subtly (extra-metadata handling). This is a drift hazard.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Priority 1 — missing core admin verbs
|
|
31
|
+
|
|
32
|
+
### 1.1 `delete <dataset-id>`
|
|
33
|
+
|
|
34
|
+
Hard gap. Today the only way to drop a broken or obsolete dataset is
|
|
35
|
+
`mc rm --recursive`.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
dsimaging-admin delete <dataset-id> [--yes]
|
|
39
|
+
[--purge-versions]
|
|
40
|
+
[--opal-url ... --opal-project ... --opal-resource ...]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Refuse without `--yes`.
|
|
44
|
+
- By default, delete current objects only; prior versions stay (the bucket has
|
|
45
|
+
versioning on). `--purge-versions` issues `delete_object_versions` in pages
|
|
46
|
+
of 1000.
|
|
47
|
+
- If `--opal-*` is provided, also remove the Opal resource.
|
|
48
|
+
|
|
49
|
+
Effort: **low**. Pure `list_objects_v2` + `delete_objects` paginator work.
|
|
50
|
+
|
|
51
|
+
### 1.2 `verify <dataset-id>`
|
|
52
|
+
|
|
53
|
+
Validate that the dataset on S3 still matches its
|
|
54
|
+
`content_hash_index.parquet`. The hash index is the whole reason content
|
|
55
|
+
hashes are computed; there is no command to check it.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
dsimaging-admin verify <dataset-id> [--sample-fraction 1.0]
|
|
59
|
+
[--output text|json]
|
|
60
|
+
[--quick]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- Reads `indexes/content_hash_index.parquet` (and the masks index if present).
|
|
64
|
+
- Re-streams every object, recomputes sha256, reports
|
|
65
|
+
`missing` / `extra` / `mismatch` per sample.
|
|
66
|
+
- `--quick` compares S3 ETag against the recorded etag first; only falls back
|
|
67
|
+
to full hashing on mismatch. Caveat: ETag == sha256 only for single-part
|
|
68
|
+
uploads, so `--quick` is an heuristic, not a proof.
|
|
69
|
+
- Non-zero exit on drift; suitable for CI.
|
|
70
|
+
|
|
71
|
+
Effort: **medium**. DICOM series must hash with the same composition rule as
|
|
72
|
+
`controller.scan_s3_images` (sorted per-file sha256 fed back into a parent
|
|
73
|
+
sha256) — another reason to share code (see P4).
|
|
74
|
+
|
|
75
|
+
### 1.3 `opal {list,register,unregister}` standalone
|
|
76
|
+
|
|
77
|
+
Today Opal registration is a side-effect of `publish`. Adding a fourth Opal
|
|
78
|
+
site, or replaying registration after a wipe, forces either a full re-upload
|
|
79
|
+
or hand-crafted Opal REST calls.
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
dsimaging-admin opal list --opal-url ... [--project IMAGING]
|
|
83
|
+
dsimaging-admin opal register --dataset-id <id> --opal-url ... [--name ... --replace]
|
|
84
|
+
dsimaging-admin opal unregister --opal-url ... --project IMAGING --resource <name>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`register` rebuilds the `imaging+dataset://` URL from the dataset's
|
|
88
|
+
manifest on S3, so it does not need access to the original local source.
|
|
89
|
+
|
|
90
|
+
Implementation: extract `_register_opal_resource` from `cli.py` into an
|
|
91
|
+
`opal.py` module and reuse from the new sub-commands and from `publish`.
|
|
92
|
+
|
|
93
|
+
Effort: **low**.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Priority 2 — observability & drift detection
|
|
98
|
+
|
|
99
|
+
### 2.1 `doctor` extended for the full module
|
|
100
|
+
|
|
101
|
+
The current `doctor` is bucket-scoped. Extend it to validate the deployment:
|
|
102
|
+
|
|
103
|
+
- Ping controller `http://<host>:8080/health`, surface `dirty_datasets` and
|
|
104
|
+
`last_errors`.
|
|
105
|
+
- Confirm the MinIO webhook is configured for `datasets/` (compare against
|
|
106
|
+
what the controller advertises).
|
|
107
|
+
- Compare CLI-side `list` against controller `/datasets` and flag any
|
|
108
|
+
divergence.
|
|
109
|
+
- Validate the init container ran (presence of `datasets/.keep`,
|
|
110
|
+
versioning enabled).
|
|
111
|
+
|
|
112
|
+
New flags:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
--controller-url http://127.0.0.1:8080 (default; from ~/.dsimaging.yaml)
|
|
116
|
+
--skip-controller (for remote / firewalled stores)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2.2 `list` enriched with controller state
|
|
120
|
+
|
|
121
|
+
When `--controller-url` is reachable, merge `dirty` and `last_error` into the
|
|
122
|
+
table. Same network call as the extended `doctor`; gracefully degrade to
|
|
123
|
+
S3-only when the controller is unreachable.
|
|
124
|
+
|
|
125
|
+
### 2.3 `status <dataset-id>`
|
|
126
|
+
|
|
127
|
+
Drill-down for a single dataset:
|
|
128
|
+
|
|
129
|
+
- Object counts under `source/images/` and `source/masks/`.
|
|
130
|
+
- Manifest `schema_version` + last-modified.
|
|
131
|
+
- Row count of `content_hash_index.parquet` vs object count (cheap drift hint
|
|
132
|
+
short of a full `verify`).
|
|
133
|
+
- Controller `last_reconcile` / `last_error` if reachable.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Priority 3 — robustness of `publish`
|
|
138
|
+
|
|
139
|
+
### 3.1 Atomic publish via staging prefix
|
|
140
|
+
|
|
141
|
+
Today, a mid-upload failure leaves a partial set of objects under
|
|
142
|
+
`datasets/<id>/source/`. The controller fires on each PUT and writes a
|
|
143
|
+
partial manifest. Replace with a two-phase flow:
|
|
144
|
+
|
|
145
|
+
1. Upload to `datasets/<id>/.staging-<uuid>/source/...`.
|
|
146
|
+
2. On full success, server-side `CopyObject` into the canonical prefix and
|
|
147
|
+
delete the staging copy.
|
|
148
|
+
3. Only then do the webhook events on the final prefix trigger the
|
|
149
|
+
controller; the controller never sees an in-progress dataset.
|
|
150
|
+
|
|
151
|
+
Trade-off: intra-bucket copy adds IO but no egress. Acceptable for the
|
|
152
|
+
target scale.
|
|
153
|
+
|
|
154
|
+
### 3.2 Idempotent upload (skip-if-hash-matches)
|
|
155
|
+
|
|
156
|
+
For larger public datasets (LUNG1, other TCIA cohorts), re-running `publish`
|
|
157
|
+
should skip objects whose local sha256 already appears in
|
|
158
|
+
`content_hash_index.parquet`. Add `--no-skip` to opt out.
|
|
159
|
+
|
|
160
|
+
### 3.3 `--dry-run`
|
|
161
|
+
|
|
162
|
+
Scan + report what would be uploaded, no S3 writes. Useful for sanity-checking
|
|
163
|
+
metadata CSV joins (`--metadata`) before paying the upload cost.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Priority 4 — internal refactor (drift killer)
|
|
168
|
+
|
|
169
|
+
`cli.py::_write_dataset_artifacts` and
|
|
170
|
+
`dsimaging-store/controller/controller.py::write_dataset_artifacts` are
|
|
171
|
+
siblings that must stay in sync. They already diverge:
|
|
172
|
+
|
|
173
|
+
- The controller has no concept of `extra_metadata` provided from outside;
|
|
174
|
+
it only preserves whatever was already in `metadata/samples.parquet`.
|
|
175
|
+
- Mask handling code paths are similar but not identical.
|
|
176
|
+
|
|
177
|
+
Two viable options:
|
|
178
|
+
|
|
179
|
+
**Option A — shared core library (recommended).**
|
|
180
|
+
Extract `manifest`, `hashing`, `schema`, `s3_layout` into a small package
|
|
181
|
+
(`dsimaging-core` or similar) installed in both the CLI distribution and the
|
|
182
|
+
controller container. CLI keeps `cli` + `opal`; controller keeps the HTTP
|
|
183
|
+
layer + reconcile loop.
|
|
184
|
+
|
|
185
|
+
**Option B — controller as sole writer.**
|
|
186
|
+
CLI never writes indexes/manifests directly; after upload it issues
|
|
187
|
+
`POST /reconcile/<dataset-id>` and waits. Single source of truth, but the
|
|
188
|
+
CLI now hard-requires the controller stack — that breaks the use-case of
|
|
189
|
+
publishing into a vanilla S3 bucket without the `dsimaging-store` services
|
|
190
|
+
deployed.
|
|
191
|
+
|
|
192
|
+
Recommendation: **A**. Keep `publish` self-sufficient.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Priority 5 — nice-to-haves
|
|
197
|
+
|
|
198
|
+
- `download <dataset-id> <dest>` — pull a dataset locally for inspection
|
|
199
|
+
/ debugging.
|
|
200
|
+
- `copy <src-id> <dst-id>` — server-side intra-bucket copy for renames or
|
|
201
|
+
forks.
|
|
202
|
+
- Multi-profile config: `~/.dsimaging.yaml` with `profiles.<name>:` and a
|
|
203
|
+
`--profile <name>` flag (today only `default` is honoured).
|
|
204
|
+
- `--output json` on `list`, `verify`, `status`, `doctor` for scripting.
|
|
205
|
+
- Structured logging (`--log-format json`).
|
|
206
|
+
- Pre-publish DICOM sanity checks (consistent SeriesInstanceUID per folder,
|
|
207
|
+
monotonic InstanceNumber, single Modality value).
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Non-goals (explicit)
|
|
212
|
+
|
|
213
|
+
- Web UI. The MinIO console plus the CLI is enough for operator needs.
|
|
214
|
+
- Authentication / RBAC inside the CLI. Inherited from S3 and Opal endpoints.
|
|
215
|
+
- Image-level operations (DICOM de-identification, resampling, format
|
|
216
|
+
conversion). That is `dsImaging` / `dsRadiomics` territory, not admin.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Open questions
|
|
221
|
+
|
|
222
|
+
- Should `delete` cascade to `dsHPC` artifacts that reference the dataset?
|
|
223
|
+
No back-reference index exists today; probably out of scope for the admin
|
|
224
|
+
CLI.
|
|
225
|
+
- Should `verify` write a tombstone (`indexes/last_verify.json`) on detected
|
|
226
|
+
drift so the controller can refuse to republish stale manifests?
|
|
227
|
+
- Minimal `release.yml` for PyPI Trusted Publishing from
|
|
228
|
+
`isglobal-brge/dsimaging-admin`? Tracked separately; only blocks future
|
|
229
|
+
cuts.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Sequencing suggestion
|
|
234
|
+
|
|
235
|
+
**One full day of work**:
|
|
236
|
+
|
|
237
|
+
1. Extract shared core (**P4 Option A**). Pays for everything below.
|
|
238
|
+
2. `delete` + `opal *` standalone (**P1.1** + **P1.3**). Both small, both
|
|
239
|
+
unblock real operations.
|
|
240
|
+
3. `verify` (**P1.2**). Turns the hash index from a hopeful artifact into a
|
|
241
|
+
real safety net.
|
|
242
|
+
|
|
243
|
+
**An afternoon**: skip P4, do P1.1 + P1.3 only. Defer the shared-core
|
|
244
|
+
refactor until the next time the manifest schema changes.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dsimaging-admin
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Admin CLI for managing medical imaging datasets in S3/MinIO for DataSHIELD
|
|
5
|
+
Project-URL: Homepage, https://github.com/isglobal-brge/dsimaging-admin
|
|
6
|
+
Project-URL: Repository, https://github.com/isglobal-brge/dsimaging-admin
|
|
7
|
+
Author-email: David Sarrat Gonzalez <david.sarrat@isglobal.org>, Juan R Gonzalez <juanr.gonzalez@isglobal.org>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: datashield,dicom,medical-imaging,minio,radiomics,s3
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Healthcare Industry
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Requires-Dist: boto3>=1.28.0
|
|
19
|
+
Requires-Dist: click>=8.0.0
|
|
20
|
+
Requires-Dist: pyarrow>=14.0.0
|
|
21
|
+
Requires-Dist: pydicom>=3.0.0
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Requires-Dist: requests>=2.31.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# dsimaging-admin
|
|
27
|
+
|
|
28
|
+
Admin CLI for creating and operating `dsimaging-store` deployments and the
|
|
29
|
+
medical imaging datasets stored in them.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install dsimaging-admin
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Create a store
|
|
38
|
+
|
|
39
|
+
`dsimaging-admin store init` writes a Docker Compose project for MinIO plus the
|
|
40
|
+
`dsimaging-store` controller.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
dsimaging-admin store init ./study-store \
|
|
44
|
+
--controller-image davidsarratgonzalez/dsimaging-store:latest
|
|
45
|
+
dsimaging-admin store up ./study-store
|
|
46
|
+
dsimaging-admin store doctor ./study-store
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For local controller development, build from a checked-out `dsimaging-store`
|
|
50
|
+
repo instead of using an image:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
dsimaging-admin store init ./study-store \
|
|
54
|
+
--store-source /path/to/dsimaging-store
|
|
55
|
+
dsimaging-admin store up ./study-store
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Use the generated connection details as a reusable CLI profile:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
dsimaging-admin init \
|
|
62
|
+
--endpoint http://127.0.0.1:9000 \
|
|
63
|
+
--controller-url http://127.0.0.1:8080 \
|
|
64
|
+
--bucket imaging-data
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Dataset operations
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Publish with staging, publish lock, skip-if-hash-matches and DICOM checks.
|
|
71
|
+
dsimaging-admin publish \
|
|
72
|
+
--dataset-id study_ct_v1 \
|
|
73
|
+
--source /data/study_ct \
|
|
74
|
+
--metadata /data/study_ct/clinical.csv \
|
|
75
|
+
--modality ct
|
|
76
|
+
|
|
77
|
+
# Inspect and verify.
|
|
78
|
+
dsimaging-admin list
|
|
79
|
+
dsimaging-admin status study_ct_v1
|
|
80
|
+
dsimaging-admin verify study_ct_v1
|
|
81
|
+
dsimaging-admin doctor
|
|
82
|
+
|
|
83
|
+
# Rebuild artifacts from S3, copy, download or delete.
|
|
84
|
+
dsimaging-admin rescan study_ct_v1
|
|
85
|
+
dsimaging-admin copy study_ct_v1 study_ct_v2 --yes
|
|
86
|
+
dsimaging-admin download study_ct_v1 ./debug/study_ct_v1
|
|
87
|
+
dsimaging-admin delete study_ct_v1 --yes --purge-versions
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
All reporting commands that are useful for automation support JSON output:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
dsimaging-admin list --output json
|
|
94
|
+
dsimaging-admin status study_ct_v1 --output json
|
|
95
|
+
dsimaging-admin verify study_ct_v1 --output json
|
|
96
|
+
dsimaging-admin doctor --output json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## What `publish` does
|
|
100
|
+
|
|
101
|
+
1. Scans your local image directory and optional masks under `source/masks/`,
|
|
102
|
+
`masks/`, `source/labels/`, or `labels/`.
|
|
103
|
+
2. Runs basic DICOM sanity checks for series UID, modality and instance order.
|
|
104
|
+
3. Computes SHA-256 content hashes.
|
|
105
|
+
4. Skips uploads that already match the current dataset hash indexes.
|
|
106
|
+
5. Uploads through `datasets/<id>/.staging-*` and a `.publish-lock`, then copies
|
|
107
|
+
into `datasets/<id>/source/...`.
|
|
108
|
+
6. Generates and uploads:
|
|
109
|
+
- `manifest.yaml`
|
|
110
|
+
- `indexes/content_hash_index.parquet`
|
|
111
|
+
- `indexes/masks_content_hash_index.parquet` when masks exist
|
|
112
|
+
- `metadata/sample_manifests.parquet`
|
|
113
|
+
- `metadata/samples.parquet`
|
|
114
|
+
|
|
115
|
+
Use `--dry-run` to scan and show the upload plan without S3 writes, `--no-skip`
|
|
116
|
+
to force uploads, or `--no-atomic` to disable staging.
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
`~/.dsimaging.yaml` supports multiple profiles:
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
default_profile: default
|
|
124
|
+
profiles:
|
|
125
|
+
default:
|
|
126
|
+
endpoint: http://127.0.0.1:9000
|
|
127
|
+
controller_url: http://127.0.0.1:8080
|
|
128
|
+
bucket: imaging-data
|
|
129
|
+
access_key: minioadmin
|
|
130
|
+
secret_key: minioadmin123
|
|
131
|
+
region: ""
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Environment variables override profile values:
|
|
135
|
+
|
|
136
|
+
| Variable | Default | Description |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `DSIMAGING_PROFILE` | `default` | Config profile |
|
|
139
|
+
| `DSIMAGING_ENDPOINT` | `http://127.0.0.1:9000` | S3/MinIO endpoint |
|
|
140
|
+
| `DSIMAGING_CONTROLLER_URL` | (empty) | dsimaging-store controller URL |
|
|
141
|
+
| `DSIMAGING_ACCESS_KEY` | `minioadmin` | S3 access key |
|
|
142
|
+
| `DSIMAGING_SECRET_KEY` | `minioadmin123` | S3 secret key |
|
|
143
|
+
| `DSIMAGING_BUCKET` | `imaging-data` | Bucket name |
|
|
144
|
+
| `DSIMAGING_REGION` | (empty) | S3 region |
|
|
145
|
+
|
|
146
|
+
## Dataset layout in S3
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
s3://<bucket>/datasets/<dataset_id>/
|
|
150
|
+
manifest.yaml
|
|
151
|
+
metadata/
|
|
152
|
+
samples.parquet
|
|
153
|
+
sample_manifests.parquet
|
|
154
|
+
indexes/
|
|
155
|
+
content_hash_index.parquet
|
|
156
|
+
masks_content_hash_index.parquet
|
|
157
|
+
source/
|
|
158
|
+
images/
|
|
159
|
+
masks/
|
|
160
|
+
derived/
|
|
161
|
+
qc/
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Store creation and dataset management only target dsimaging-store deployments.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# dsimaging-admin
|
|
2
|
+
|
|
3
|
+
Admin CLI for creating and operating `dsimaging-store` deployments and the
|
|
4
|
+
medical imaging datasets stored in them.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install dsimaging-admin
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Create a store
|
|
13
|
+
|
|
14
|
+
`dsimaging-admin store init` writes a Docker Compose project for MinIO plus the
|
|
15
|
+
`dsimaging-store` controller.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
dsimaging-admin store init ./study-store \
|
|
19
|
+
--controller-image davidsarratgonzalez/dsimaging-store:latest
|
|
20
|
+
dsimaging-admin store up ./study-store
|
|
21
|
+
dsimaging-admin store doctor ./study-store
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For local controller development, build from a checked-out `dsimaging-store`
|
|
25
|
+
repo instead of using an image:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
dsimaging-admin store init ./study-store \
|
|
29
|
+
--store-source /path/to/dsimaging-store
|
|
30
|
+
dsimaging-admin store up ./study-store
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use the generated connection details as a reusable CLI profile:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
dsimaging-admin init \
|
|
37
|
+
--endpoint http://127.0.0.1:9000 \
|
|
38
|
+
--controller-url http://127.0.0.1:8080 \
|
|
39
|
+
--bucket imaging-data
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Dataset operations
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Publish with staging, publish lock, skip-if-hash-matches and DICOM checks.
|
|
46
|
+
dsimaging-admin publish \
|
|
47
|
+
--dataset-id study_ct_v1 \
|
|
48
|
+
--source /data/study_ct \
|
|
49
|
+
--metadata /data/study_ct/clinical.csv \
|
|
50
|
+
--modality ct
|
|
51
|
+
|
|
52
|
+
# Inspect and verify.
|
|
53
|
+
dsimaging-admin list
|
|
54
|
+
dsimaging-admin status study_ct_v1
|
|
55
|
+
dsimaging-admin verify study_ct_v1
|
|
56
|
+
dsimaging-admin doctor
|
|
57
|
+
|
|
58
|
+
# Rebuild artifacts from S3, copy, download or delete.
|
|
59
|
+
dsimaging-admin rescan study_ct_v1
|
|
60
|
+
dsimaging-admin copy study_ct_v1 study_ct_v2 --yes
|
|
61
|
+
dsimaging-admin download study_ct_v1 ./debug/study_ct_v1
|
|
62
|
+
dsimaging-admin delete study_ct_v1 --yes --purge-versions
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
All reporting commands that are useful for automation support JSON output:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
dsimaging-admin list --output json
|
|
69
|
+
dsimaging-admin status study_ct_v1 --output json
|
|
70
|
+
dsimaging-admin verify study_ct_v1 --output json
|
|
71
|
+
dsimaging-admin doctor --output json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## What `publish` does
|
|
75
|
+
|
|
76
|
+
1. Scans your local image directory and optional masks under `source/masks/`,
|
|
77
|
+
`masks/`, `source/labels/`, or `labels/`.
|
|
78
|
+
2. Runs basic DICOM sanity checks for series UID, modality and instance order.
|
|
79
|
+
3. Computes SHA-256 content hashes.
|
|
80
|
+
4. Skips uploads that already match the current dataset hash indexes.
|
|
81
|
+
5. Uploads through `datasets/<id>/.staging-*` and a `.publish-lock`, then copies
|
|
82
|
+
into `datasets/<id>/source/...`.
|
|
83
|
+
6. Generates and uploads:
|
|
84
|
+
- `manifest.yaml`
|
|
85
|
+
- `indexes/content_hash_index.parquet`
|
|
86
|
+
- `indexes/masks_content_hash_index.parquet` when masks exist
|
|
87
|
+
- `metadata/sample_manifests.parquet`
|
|
88
|
+
- `metadata/samples.parquet`
|
|
89
|
+
|
|
90
|
+
Use `--dry-run` to scan and show the upload plan without S3 writes, `--no-skip`
|
|
91
|
+
to force uploads, or `--no-atomic` to disable staging.
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
`~/.dsimaging.yaml` supports multiple profiles:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
default_profile: default
|
|
99
|
+
profiles:
|
|
100
|
+
default:
|
|
101
|
+
endpoint: http://127.0.0.1:9000
|
|
102
|
+
controller_url: http://127.0.0.1:8080
|
|
103
|
+
bucket: imaging-data
|
|
104
|
+
access_key: minioadmin
|
|
105
|
+
secret_key: minioadmin123
|
|
106
|
+
region: ""
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Environment variables override profile values:
|
|
110
|
+
|
|
111
|
+
| Variable | Default | Description |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `DSIMAGING_PROFILE` | `default` | Config profile |
|
|
114
|
+
| `DSIMAGING_ENDPOINT` | `http://127.0.0.1:9000` | S3/MinIO endpoint |
|
|
115
|
+
| `DSIMAGING_CONTROLLER_URL` | (empty) | dsimaging-store controller URL |
|
|
116
|
+
| `DSIMAGING_ACCESS_KEY` | `minioadmin` | S3 access key |
|
|
117
|
+
| `DSIMAGING_SECRET_KEY` | `minioadmin123` | S3 secret key |
|
|
118
|
+
| `DSIMAGING_BUCKET` | `imaging-data` | Bucket name |
|
|
119
|
+
| `DSIMAGING_REGION` | (empty) | S3 region |
|
|
120
|
+
|
|
121
|
+
## Dataset layout in S3
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
s3://<bucket>/datasets/<dataset_id>/
|
|
125
|
+
manifest.yaml
|
|
126
|
+
metadata/
|
|
127
|
+
samples.parquet
|
|
128
|
+
sample_manifests.parquet
|
|
129
|
+
indexes/
|
|
130
|
+
content_hash_index.parquet
|
|
131
|
+
masks_content_hash_index.parquet
|
|
132
|
+
source/
|
|
133
|
+
images/
|
|
134
|
+
masks/
|
|
135
|
+
derived/
|
|
136
|
+
qc/
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Store creation and dataset management only target dsimaging-store deployments.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dsimaging-admin"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Admin CLI for managing medical imaging datasets in S3/MinIO for DataSHIELD"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -28,6 +28,7 @@ dependencies = [
|
|
|
28
28
|
"pyyaml>=6.0",
|
|
29
29
|
"click>=8.0.0",
|
|
30
30
|
"requests>=2.31.0",
|
|
31
|
+
"pydicom>=3.0.0",
|
|
31
32
|
]
|
|
32
33
|
|
|
33
34
|
[project.scripts]
|