Skip to content
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

First crack at adding dataset read adapter in DS read for deflate #438

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

naterichman
Copy link
Contributor

No description provided.

)?;
Ok(FileDicomObject { meta, obj })
if let Codec::Dataset(Some(adapter)) = ts.codec() {
let adapter = adapter.adapt_reader(Box::new(file));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest, not sure where you had in mind to adapt the reader.

Copy link
Owner

Choose a reason for hiding this comment

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

Seems like a better place than most at the moment. 😅 Maybe there will be a better place after some of the efforts for lazy DICOM data loading are in place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I pushed another commit where I got this working at least with one test. After playing around with this more, I don't think this is the right place for it to go, I think it needs to go into DataSetReader and DataSetWriter constructor new_with_ts_cs_options since there are some code paths for reading a dataset that would not go through this change.

I tried doing that a bit, but I started running into issues with type mismatches:

    pub fn new_with_ts_cs_options(
        source: R,
        ts: &TransferSyntax,
        cs: SpecificCharacterSet,
        options: DataSetReaderOptions,
    ) -> Result<Self>
    where
        R: Read,
    {
        if let Codec::Dataset(Some(adapter)) = ts.codec() {
            let adapter = adapter.adapt_reader(Box::new(source));
            let parser = DynStatefulDecoder::new_with(adapter, ts, cs, 0).context(CreateDecoderSnafu)?;

            is_stateful_decode(&parser);

            Ok(DataSetReader {
                parser,
                options,
                seq_delimiters: Vec::new(),
                delimiter_check_pending: false,
                offset_table_next: false,
                in_sequence: false,
                hard_break: false,
                last_header: None,
                peek: None,
            })
        } else {
            let parser = DynStatefulDecoder::new_with(adapter, ts, cs, 0).context(CreateDecoderSnafu)?;

            is_stateful_decode(&parser);

            Ok(DataSetReader {
                parser,
                options,
                seq_delimiters: Vec::new(),
                delimiter_check_pending: false,
                offset_table_next: false,
                in_sequence: false,
                hard_break: false,
                last_header: None,
                peek: None,
            })
        }
    }

But I keep getting variants of this and I have not found a good way around it...

error[E0308]: mismatched types
   --> parser/src/dataset/read.rs:214:17
    |
175 | impl<R> DataSetReader<DynStatefulDecoder<R>> {
    |      - this type parameter
...
214 |                 parser,
    |                 ^^^^^^ expected type parameter `R`, found `Box<dyn std::io::Read>`
    |
    = note: expected struct `StatefulDecoder<Box<dyn DecodeFrom<R>>, R>`
               found struct `StatefulDecoder<Box<dyn DecodeFrom<Box<dyn std::io::Read>>>, Box<dyn std::io::Read>>`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I think I understand the issue, the type parameter R is changing, so I'm thinking one of these structs needs to be dynamic instead of static, i.e.

struct DataSetReader {
    parser: Box<dyn Read>,
    ...

If we go with this method.

I'll let you decide on how deep you want to have the adapter applied, seems to me it can either be

  • In InMemDicomObject and FileDicomObject in the object module, which would need to be in multiple methods, i.e. write_to_file, write_all, write_dataset, open_file, read_dataset and the other variants
  • In the DatasetReader and DatasetWriter in a few variants
  • In the StatefulEncoder and StatefulDecoder

To me it seems the first would require the most code change, but its all straightforward (mostly copy and pasted), while the second two would require a bit of refactor on those structs, but wouldn't duplicate as much code.

Let me know if I'm interpreting this wrong, and if you have any opinions. Happy to go forward with whatever you feel is best!

Copy link
Owner

@Enet4 Enet4 Nov 21, 2023

Choose a reason for hiding this comment

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

Thank you for describing the situation @naterichman. That is pretty much the kind of rabbit hole I might have found last time I tried to incorporate support for deflated explicit VR little endian.

The components in the dicom-parser crate were designed with the one-way handshake pattern (and occasionally three-way handshake pattern) to allow some forms of dynamic dispatching while preventing most underlying calls from having an unwanted indirection. The current implementation relies on reading and writing to be buffered and fast, as it can end up making many calls per element. If parser in DataSetReader becomes a Box<dyn Read>, we are likely to lose performance.

In the long term, I understand now that low-level primitives expecting a fast impl Read or impl Write has its issues, and might not be the best way to go towards a flexible implementation with better performance. In particular, separating the concerns of fetching/producing the byte stream from DICOM data decoding and encoding would help the constructs in dicom-encoding and dicom-parser by making them less dependent on so many type parameters.

For the time being however, support for deflate may have to be written in the upper layers, where we can more easily create a separate path for arbitrary data set encoders and decoders.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Enet4 thanks for the link to the handshake pattern that was a cool read. I understand the issue. In the meantime I will go forward with adding the support for deflate to the other places in the upper layers. Probably won't be for a week or so due to american thanksgiving! Thanks again for the info and feedback!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I've added the adapters to read and write, but I had to add the 'static lifetime annotation to satisfy the Box::from() when wrapping the source/destination in the adapter. From my basic test writing a deflated TS file which doesn't currently compile, it seems like this would be a breaking change, which I'm guessing you don't want.

Any guidance on how to approach this to relax that lifetime annotation would be appreciated

@Enet4 Enet4 added A-lib Area: library C-object Crate: dicom-object C-pixeldata Crate: dicom-pixeldata C-transfer-syntax Crate: dicom-transfer-syntax-registry labels Mar 22, 2024
* Note, not all paths lead to writing, for example using
  `InMemDicomObject.read_dataset_with_dict` would bypass deflate.
* No support for writing right now
@Enet4 Enet4 added the breaking change Hint that this may require a major version bump on release label Apr 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lib Area: library breaking change Hint that this may require a major version bump on release C-object Crate: dicom-object C-pixeldata Crate: dicom-pixeldata C-transfer-syntax Crate: dicom-transfer-syntax-registry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants