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

Pixel encapsulation #312

Merged
merged 29 commits into from Jun 25, 2023
Merged

Pixel encapsulation #312

merged 29 commits into from Jun 25, 2023

Conversation

dougyau
Copy link
Contributor

@dougyau dougyau commented Nov 25, 2022

Add the function to encapsulate a vector of frames. The function will return a PixelSequence which can be used with DataElement::new() to add the tag to the dataset.

let mut frames: Vec<Vec<u8>> = ...

let encapsulated_pixels = dicom::pixeldata::encaspulation::encapsulate(&mut frames, 1)?;
dcm.put(DataElement::new(dicom::dictionary_std::tags::PIXEL_DATA, dicom::core::VR::OB, encapsulated_pixels);

@Enet4 Enet4 added the A-lib Area: library label Nov 25, 2022
@Enet4 Enet4 self-requested a review November 25, 2022 10:26
…se the minimum posible size. Consume the input vector intead of using a reference.
…l on how to add the frames, this gives options on how much data is stored at the time
@dougyau
Copy link
Contributor Author

dougyau commented Nov 27, 2022

I have done a refactor, to allow different kind of scenarios.

In the first scenario, you could use the simplest call. The bad part of this is that at some point, the input frames and the output frames are allocated.

let frames: Vec<Vec<u8>> = ...

let encapsulated_pixels = dicom::pixeldata::encapsulation::encapsulate(&mut frames, 1);
dcm.put(DataElement::new(dicom::dictionary_std::tags::PIXEL_DATA, dicom::core::VR::OB, encapsulated_pixels);

Now if memory is an issue (mostly when dealing with multiframe images) the frames can be processed individually.

let frames: Vec<Vec<u8>> = ...
let encapsulated_pixels = frames.
    .into_iter()
    .map(|frame| ...do some encoding)
    .for_each(|encoded_frame| encapsulated_pixels.add_frame(encoded_frame));

dcm.put(DataElement::new(dicom::dictionary_std::tags::PIXEL_DATA, dicom::core::VR::OB, encapsulated_pixels);

And the last way is useful when using rayon

let frames: Vec<Vec<u8>> = ...
let fragments = frames.
    .into_par_iter()
    .map(|frame| ...do some encoding)
    .flat_map(|encoded_frame| dicom::pixeldata::encapsulation::fragment_frame(encoded_frame, 1))
    .collect::<Vec<Vec<u8>>>;
let encapsulated_pixels = dicom::pixeldata::encapsulation::EncapsulatedPixels::from(fragments);

dcm.put(DataElement::new(dicom::dictionary_std::tags::PIXEL_DATA, dicom::core::VR::OB, encapsulated_pixels);

Copy link
Owner

@Enet4 Enet4 left a comment

Choose a reason for hiding this comment

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

Thank you for continuing this contribution. I like the idea of introducing a more substantial API! But I also feel that there is a lot to improve and fix before it is ready. Please attend to the comments inline.

pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
@Enet4 Enet4 added enhancement C-pixeldata Crate: dicom-pixeldata labels Dec 10, 2022
@dougyau
Copy link
Contributor Author

dougyau commented Jan 17, 2023

@Enet4 I have improved the code based on the suggestions. Please let me know if you see more things that can be improved.

@Enet4 Enet4 self-requested a review January 17, 2023 08:41
Copy link
Owner

@Enet4 Enet4 left a comment

Choose a reason for hiding this comment

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

Hey @dougyau! I have been doing some changes to most of the crates in the DICOM-rs project. In particular, I felt that we would be better served with the pixel data sequence type being declared in the core crate itself, which may subvert some of the decisions made here.
Nevertheless, it is still important and useful to have some mechanisms to turn frames into fragments and vice versa (which we do not have yet), so I do not want to put this pull request to waste! :) Please see the inline comment and let me know if you have any other plans here.

/// dcm.put(DataElement::new(PIXEL_DATA, OB, encapsulated_pixels));
/// ```
#[derive(Debug, Default)]
pub struct EncapsulatedPixels {
Copy link
Owner

Choose a reason for hiding this comment

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

The upstream version (master branch) of dicom_core now features a pixel data fragment sequence type dicom_core::value::PixelFragmentSequence, so we want to build up our frames-to-fragments abstraction from this. EncapsulatedPixels would be removed and PixelFragmentSequence would be given an impl From<FrameFragments>.

If there are any good methods that would be important to have in PixelFragmentSequence, it should be possible to accommodate them there as well.

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 think this is a great idea as the API would be simpler.

Currently this code should be compatible with the changes made on upstream. But I do like your proposal.

@dougyau dougyau marked this pull request as draft June 20, 2023 18:19
@dougyau dougyau marked this pull request as ready for review June 20, 2023 18:56
@dougyau
Copy link
Contributor Author

dougyau commented Jun 23, 2023

@Enet4 I have updated the tests and the doc blocks on the PR.

use dicom_core::value::Value;

/// Encapsulate the pixel data of the frames. If fragment_size > 0 it will use 1 fragment per frame.
/// This parameter is ignored for multi frame data, as 1 fragment per frame is required.
Copy link
Contributor

@ingwinlu ingwinlu Jun 24, 2023

Choose a reason for hiding this comment

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

isn't that depending on the transfer syntax used?
https://dicom.nema.org/dicom/2013/output/chtml/part05/sect_A.4.html
Whether more than one fragment per frame is permitted or not is defined per Transfer Syntax.

Copy link
Owner

@Enet4 Enet4 Jun 24, 2023

Choose a reason for hiding this comment

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

Thank you for having a look @ingwinlu. The limitations of this abstraction are known, as it aims to facilitate the constructions in the following scenarios (which I believe are the most common ones):

  • Each frame is given a single fragment, as in RLE Lossless and all JPEG-based transfer syntaxes;
  • A simple image frame is split into multiple fragments.

With that said, there is probably a better way to describe this function in a way which does not confuse API consumers, maybe by having a signature encapsulate_single_frame(frame: Vec<u8>, max_fragment_size: u32) -> Value instead. Unless it happens that the current function is convenient for both situations, because otherwise it may become a source of confusion that fragment_size is ignored when more than one frame is passed.

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 think it is better to have 1 function for this, otherwise when you intend to encapsulate you need to verify if it is single or multi frame, then call the corresponding function.

Copy link
Owner

@Enet4 Enet4 left a comment

Choose a reason for hiding this comment

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

I made a few more suggestions. Primarily, I think it makes sense to split encapsulate for the two key scenarios (unless there is a reason I haven't grasped yet). encapsulate_single_frame would receive a single frame and a maximum fragment size, whereas the other encapsulate_frames would give each frame its own fragment and provide a basic offset table accordingly.

Comment on lines 4 to 5
/// Represents the fragments of a single frame. [PixelFragmentSequence] can be generated from a list
/// of [Fragments]. In case of multi frame a list of frames composed by 1 fragment is expected.
Copy link
Owner

Choose a reason for hiding this comment

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

I usually prefer adding backticks when referring to other types.

The first paragraph is dedicated as a the type's short description.

Suggested change
/// Represents the fragments of a single frame. [PixelFragmentSequence] can be generated from a list
/// of [Fragments]. In case of multi frame a list of frames composed by 1 fragment is expected.
/// Represents the fragments of a single frame.
///
/// A [`PixelFragmentSequence`] can be generated from a list of [`Fragments`].
/// In case of multi-frame, a list of frames composed by 1 fragment is expected.

use dicom_core::value::Value;

/// Encapsulate the pixel data of the frames. If fragment_size > 0 it will use 1 fragment per frame.
/// This parameter is ignored for multi frame data, as 1 fragment per frame is required.
Copy link
Owner

@Enet4 Enet4 Jun 24, 2023

Choose a reason for hiding this comment

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

Thank you for having a look @ingwinlu. The limitations of this abstraction are known, as it aims to facilitate the constructions in the following scenarios (which I believe are the most common ones):

  • Each frame is given a single fragment, as in RLE Lossless and all JPEG-based transfer syntaxes;
  • A simple image frame is split into multiple fragments.

With that said, there is probably a better way to describe this function in a way which does not confuse API consumers, maybe by having a signature encapsulate_single_frame(frame: Vec<u8>, max_fragment_size: u32) -> Value instead. Unless it happens that the current function is convenient for both situations, because otherwise it may become a source of confusion that fragment_size is ignored when more than one frame is passed.

core/src/value/fragments.rs Outdated Show resolved Hide resolved
core/src/value/fragments.rs Show resolved Hide resolved
@dougyau
Copy link
Contributor Author

dougyau commented Jun 24, 2023

The changes have been implemented.

pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
pixeldata/src/encapsulation.rs Outdated Show resolved Hide resolved
Copy link
Owner

@Enet4 Enet4 left a comment

Choose a reason for hiding this comment

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

OK, we are in condition to bring this upstream. Thank you @dougyau for your commitment.

@Enet4 Enet4 merged commit d4ccc60 into Enet4:master Jun 25, 2023
4 checks passed
@dougyau dougyau deleted the pixel-encapsulation branch July 23, 2023 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lib Area: library C-pixeldata Crate: dicom-pixeldata enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants