Skip to content

Implement DICOMDIR#669

Draft
Cryt1c wants to merge 3 commits into
Enet4:masterfrom
Cryt1c:dicomdir
Draft

Implement DICOMDIR#669
Cryt1c wants to merge 3 commits into
Enet4:masterfrom
Cryt1c:dicomdir

Conversation

@Cryt1c

@Cryt1c Cryt1c commented Jul 29, 2025

Copy link
Copy Markdown
Contributor

#184

Done:

  1. Integrate test data from pydicom
  2. Load DICOMDIR file in a test
  3. Get all REFERENCE_FILE_IDs from the file

@Cryt1c

Cryt1c commented Aug 2, 2025

Copy link
Copy Markdown
Contributor Author

@Enet4 I am looking into how to extract the DICOM hierarchy from the DICOMDIR file.
The information is stored using these offsets in the DirectoryRecordSequence items:

  • _FIRST_OFFSET = "OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity"
  • _NEXT_OFFSET = "OffsetOfTheNextDirectoryRecord"
  • _LOWER_OFFSET = "OffsetOfReferencedLowerLevelDirectoryEntity"
  • _LAST_OFFSET = "OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity"

Is there already an API to read information by offsets in dicom-rs? If not can you point me to some part in the code where the offsets are read currently?

@Enet4

Enet4 commented Aug 6, 2025

Copy link
Copy Markdown
Owner

@Enet4 I am looking into how to extract the DICOM hierarchy from the DICOMDIR file. The information is stored using these offsets in the DirectoryRecordSequence items:

  • _FIRST_OFFSET = "OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity"
  • _NEXT_OFFSET = "OffsetOfTheNextDirectoryRecord"
  • _LOWER_OFFSET = "OffsetOfReferencedLowerLevelDirectoryEntity"
  • _LAST_OFFSET = "OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity"

Is there already an API to read information by offsets in dicom-rs? If not can you point me to some part in the code where the offsets are read currently?

The mid-level data set reader API in dicom_parser has a way to retrieve its current byte position in the source data, but you are right that the current DICOM object implementation ends up mostly ignoring them. In this case we would need to record them somewhere as it goes. How we do this is a matter of knowing the trade-offs. Thinking out loud:

  • Option a) Extending InMemElement may seem trivial, but it would have the immediate cost of all elements requiring 4 more bytes even when they aren't needed. There are ways to make this effect only apply to specific DICOM object implementations, since they aren't hardcoded to a specific data element type, but working with different impls is still a bit tricky (needs at least Revise DicomObject abstraction #524) and might make dicom_object more complex.
  • Option b) There's also the possibility of saving a separate offset dictionary, which comes with the upside of making this extra memory cost per element optional at run time, and it's also not hard to implement. The main disadvantage is poorer memory usage and cache locality for those who need to read these offsets.
  • Option c) An even less invasive approach would be only collecting these offsets in the DICOM collector API, meaning that they were only retained temporarily and not saved in the DICOM object implementation. The DicomDir would then use the collector API and consult whichever offsets needed before dropping the collector.

Looking forward to your thoughts on these approaches, though I'm feeling inclined to option c) right now.

@Enet4 Enet4 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the initiative! Though this is surely a work in progress, I took the liberty of leaving some notes and suggestions inline.

Comment thread object/src/dicomdir.rs
pub fn get_referenced_file_ids(&self) -> Vec<String> {
let directory_record_sequence = self.file
.element(tags::DIRECTORY_RECORD_SEQUENCE)
.expect("could not get DIRECTORY_RECORD_SEQUENCE")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this is just temporary code, but all of these expects should become recoverable errors before merging. It is fine to create a new error type specifically for DicomDir methods.

An alternative approach to this is pre-validate and transform all data as it is read from the file, so that these methods become pure getters.

Comment thread object/src/dicomdir.rs
})
}

pub fn get_referenced_file_ids(&self) -> Vec<String> {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we can make do without the get_ prefix (at least per my interpretation of C-GETTER, as this is something like a fallible getter).

Suggested change
pub fn get_referenced_file_ids(&self) -> Vec<String> {
pub fn referenced_file_ids(&self) -> Vec<String> {

Comment thread object/src/dicomdir.rs
fn test_dicom_dir() {
let current_dir = env::current_dir().unwrap();
println!("Current directory: {}", current_dir.display());
let path = "src/dicomdirtests/DICOMDIR";

@Enet4 Enet4 Aug 6, 2025

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a better strategy for holding test assets. If they sit alongside src, it'll be harder to exclude them from packages.

Even better would be introducing this data set to dicom-test-files so that they stay in a separate repository and are only fetched when needed.

Comment thread object/src/dicomdir.rs
@@ -0,0 +1,69 @@
use std::path::Path;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a public module, some crate-level documentation would be nice to have. An example: https://docs.rs/dicom-object/latest/dicom_object/collector/index.html

Comment thread object/src/dicomdir.rs
pub type Result<T, E = ReadError> = std::result::Result<T, E>;

impl DicomDir {
pub fn new<P>(path: P) -> Result<DicomDir>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, either open_file or open_dicomdir would be suitable function names for this one.

@jmlaka

jmlaka commented Aug 11, 2025

Copy link
Copy Markdown
Contributor

Hello there,
I've been working on DICOMDIR read and write / update for quite a while.
If you need a working Rust code just to READ the DICOMDIR, I can post it.

Editing / creating at this time is not bug free, which is strongly coupled with #645 .

Unless this is fixed, the creation of a DICOMDIR will suffer from length calculation / recalculation bugs, leading to incorrect offset values.

@Cryt1c

Cryt1c commented Aug 12, 2025

Copy link
Copy Markdown
Contributor Author

@jmlaka interesting! Thanks for the info.
It would be great if you could provide your code to READ DICOMDIR.

@jmlaka

jmlaka commented Oct 30, 2025

Copy link
Copy Markdown
Contributor

@jmlaka interesting! Thanks for the info. It would be great if you could provide your code to READ DICOMDIR.

@Cryt1c sorry for the late reply as I haven't been very active lately.

Here I made this public repo for you:
https://github.com/jmlaka/dicomdir-work

It is updated to compile against the newest dicom 0.9.0

It was originally meant to support read, write, update ...

The main logic was copied from PyDicom code.

The binary lets you test the capabilities. It has a lot of debugging output.

Included /test_files come from the PyDicom project itself

Read capability works flawlessly and can be integrated even into the Dicom library (after getting rid of everything else)

creating new dicomdirs from a directory of dicom instances works (I Think)

Now so sure about updating the datasets.

As you can see, I you want to do anything more beyond simple parsing of a DICOMDIR file, you have to
implement a custom Tree, that can be further manipulated.

If you extract the read part of the code (just parsing the file paths) it is pretty lightweight stuff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants