Skip to content

feat: download dicomweb data to local file system #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4cb37e6
feat: Add DICOMweb download
wayfarer3130 Jul 2, 2024
33792b9
Add lint settings
wayfarer3130 Jul 5, 2024
ccbba42
fix: Add logging
wayfarer3130 Mar 3, 2025
74dccb8
Merge remote-tracking branch 'origin/main' into feat/download-dicomweb
wayfarer3130 Mar 20, 2025
d363abc
WIP - Extended download dicomweb command
TFRadicalImaging Mar 27, 2025
7323ff6
feat: implement modular DICOMweb downloader CLI with pluggable archit…
TFRadicalImaging Apr 7, 2025
8f04f8f
Update the interfaces for downloading
wayfarer3130 Apr 9, 2025
6781501
fix: Run dicomwebjs command and store as iterative store data
wayfarer3130 Apr 10, 2025
7c0602a
Partial refactoring to work as tree structured data
wayfarer3130 Apr 22, 2025
31dec6b
fix: Add generic child type
wayfarer3130 Apr 22, 2025
d500c69
fix: Store metadata as downloaded.
wayfarer3130 Apr 22, 2025
e362f39
fix: Update instance store to have frame and bulkdata store
wayfarer3130 Apr 28, 2025
c918238
Add bulkdata and instances writing
wayfarer3130 Apr 29, 2025
7a79830
fix: Adding start of DICOMweb json retrieve
wayfarer3130 Apr 29, 2025
243a06b
Use jsdom in favor of the XML http request
wayfarer3130 Apr 29, 2025
d04b467
fix: Download studies from local SDW - mostly working
wayfarer3130 Jun 4, 2025
40e4ae4
Fix issue with series query results
wayfarer3130 Jun 4, 2025
b90d791
fix: Most of study download now working
wayfarer3130 Jun 13, 2025
e09e398
Working download from both dicomweb and local static dicomweb to bulk…
wayfarer3130 Jun 16, 2025
667a71a
fix: Bulkdata download
wayfarer3130 Jun 16, 2025
73d140a
Update to released dicomweb-client library
wayfarer3130 Jun 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
studies/

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand Down
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
To install, clone the directory and run:

```
yarn install
yarn link:exec
bun install
bun run build
bun run link:exec
```

# dcmjs commands
Expand All @@ -29,13 +30,25 @@ dicomwebjs dump https://d33do7qe4w26qo.cloudfront.net/dicomweb/studies/1.3.6.1.4
dicomwebjs dump testdata/studies/1.2.276.1.74.1.2.132733202464108492637644434464108492/series/2.16.840.1.113883.3.8467.132733202477512857637644434477512857/metadata.gz
```

## File Locations
Files dicomweb can be paths to JSON files. However, tree structure data must follow the Static DICOMweb format, specifically starting at `studies/` relative to the base directory, and containing some/all of the ones below.
## download `url`

dicomwebjs can be used to download a directory of file locations. By default it will fetch everything at and below the specified URL, plus referenced bulkdata. Bulkdata will be
placed in the `studies/<studyUID>/bulkdata` directory, with metadata references to the bulkdata using the `../../bulkdata/<path>` relative URI locations.

### Example Commands

```
dicomwebjs download https://d33do7qe4w26qo.cloudfront.net/dicomweb/studies/1.3.6.1.4.1.14519.5.2.1.4792.2001.105216574054253895819671475627 -d ~/dicomweb
```

### File Locations

Files dicomweb can be paths to JSON files. However, tree structure data must follow the Static DICOMweb format, specifically starting at `studies/` relative to the base directory, and containing some/all of the ones below.
Note that un-compressed files are acceptable as well, but will not be found on a search.

* `studies/index.json.gz` - an index in DICOMweb QIDO response format for the studies
* `studies/<studyUID>/index.json.gz` - the index entry of this study
* `studies/<studyUID>/series/index.json.gz` - the series QIDO response
* `studies/<studyUID>/series/<seriesUID>/metadata.gz` - the metadata WADO response
* `studies/<studyUID>/bulkdata/...` - bulkdata files
* `studies/<studyUID>/series/<seriesUID>/instances/<instanceUID>/frame/<frameNumber>` - compressed frame data
- `studies/index.json.gz` - an index in DICOMweb QIDO response format for the studies
- `studies/<studyUID>/index.json.gz` - the index entry of this study
- `studies/<studyUID>/series/index.json.gz` - the series QIDO response
- `studies/<studyUID>/series/<seriesUID>/metadata.gz` - the metadata WADO response
- `studies/<studyUID>/bulkdata/...` - bulkdata files
- `studies/<studyUID>/series/<seriesUID>/instances/<instanceUID>/frame/<frameNumber>` - compressed frame data
52 changes: 52 additions & 0 deletions bin/cliDownload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { DicomAccess } from "@dcmjs/commands";

const action = async (url, options) => {
try {
const destination = await DicomAccess.createInstance(options.directory, {
...options,
scheme: "file",
});

// Create access instance (currently supports only DICOMweb)
console.debug(
"🔌 Creating DICOM access instance...",
url,
"to",
options.directory
);
const access = await DicomAccess.createInstance(url, options);

const { StudyInstanceUID: studyUID } = options;
if (!studyUID) {
console.warn("Please provide a studyUID");
return -2;
}
const srcStudy = await access.queryStudy(studyUID);

await destination.store(srcStudy, options);

console.log(
`🎉 Download complete. Study saved to: ${options.directory}/studies/${studyUID}`
);
} catch (err) {
console.warn("❌ An error occurred during download:", err);
process.exit(1);
}

process.exit(0);
};

// CLI registration
export default async function cliDownload(program) {
program
.command("download")
.description("Download a full DICOM study from any supported source")
.argument("<url>", "DICOMweb URL or local path (e.g. ./study, scp://...)")
.option(
"-S, --StudyInstanceUID <StudyInstanceUID>",
"StudyInstanceUID to download"
)
.option("-d, --directory <targetDir>", "Download to local directory", ".")
.option("--debug", "Enable debug logging")
.action(action);
}
43 changes: 27 additions & 16 deletions bin/dicomwebjs.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
#!/usr/bin/env node
import { Command } from 'commander';
import { dicomweb, instanceDicom, dumpDicom } from '../src/index.js';
#!/usr/bin/env bun
import { Command } from "commander";
import { dicomweb, instanceDicom, dumpDicom } from "../src/index.js";
import cliDownload from "./cliDownload.js";

const program = new Command();

program.option(
"-s, --study <studyInstanceUID>",
"Download a specific study instance UID"
);

program
.name('dicomwebjs')
.description('dicomwebjs based tools for manipulation of DICOMweb')
.version('0.0.1')
.option('--seriesUID <seriesUID>', 'For a specific seriesUID');
.name("dicomwebjs")
.description("dicomwebjs based tools for manipulation of DICOMweb")
.version("0.0.1")
.option("--seriesUID <seriesUID>", "For a specific seriesUID");

program.command('dump')
.description('Dump a dicomweb file')
.argument('<dicomwebUrl>', 'dicomweb URL or file location')
program
.command("dump")
.description("Dump a dicomweb file")
.argument("<dicomwebUrl>", "dicomweb URL or file location")
.option("--debug", "Set debug level logging")
.action(async (fileName, options) => {
const qido = await dicomweb.readDicomWeb(fileName, options);
for (const dict of qido) {
dumpDicom({ dict });
}
});

program.command('instance')
.description('Write the instance data')
.argument('<part10>', 'part 10 file')
.option('-p, --pretty', 'Pretty print')
program
.command("instance")
.description("Write the instance data")
.argument("<part10>", "part 10 file")
.option("-p, --pretty", "Pretty print")
.option("--debug", "Set debug level logging")
.action(async (fileName, options) => {
const qido = await dicomweb.readDicomWeb(fileName, options);
for (const dict of qido) {
instanceDicom({ dict }, options);
}
})
});

cliDownload(program);

program.parse();
program.parse();
Loading