Skip to content

Conversation

weanti
Copy link

@weanti weanti commented Jun 26, 2025

Fix constructing frame offsets for encapsulated pixel data.
Fix reading frames from encapsulated pixel data.

Remove restrictions for bits stored value, because it is not valid in all cases e.g. CT.

Antal Ispanovity added 3 commits June 26, 2025 15:03
… specific e.g. in case of CT it can be 12,13,14,15,16
…ally not read, undefined length was assumed

use more intuitive offset calculation: previous offset + tag and length size + current length
@jcupitt
Copy link
Collaborator

jcupitt commented Jun 30, 2025

Hi @weanti,

This looks great! Thank you for doing this work.

Could you explain what kinds of DICOM you are working with? Do you have some sample DICOMs I could use for testing?

You probably saw the other PR (#102): we should probably merge that first, since it covers some of the same ground.

@weanti
Copy link
Author

weanti commented Jun 30, 2025

Hi @weanti,

This looks great! Thank you for doing this work.

Could you explain what kinds of DICOM you are working with? Do you have some sample DICOMs I could use for testing?

You probably saw the other PR (#102): we should probably merge that first, since it covers some of the same ground.

Hi,

thank you for your response.
In the meantime I did further testing and I found problems. Memory overwrites and offset handling problems.
Sure, I try to attach or send some test files. Btw I used JPEG2000 encoded DICOM images.
First I need to polish this PR.

@jcupitt
Copy link
Collaborator

jcupitt commented Jun 30, 2025

OK, let's tag this as a draft for now.

@jcupitt jcupitt marked this pull request as draft June 30, 2025 15:01
fix indexing problem when creating offsets table
fix frame size calculation: bits allocated was not considered
@weanti
Copy link
Author

weanti commented Jul 1, 2025

I think I solved the problems.
I have attached a bunch of DICOM files. See the readme.txt for important properties.
UPDATE: uoloading files doesn't seem to work. Here is a link for a shared zip file: https://drive.google.com/file/d/1UPgq89YLx94XyiuGOupR2t0LtgQ4VnkS/view?usp=sharing

@weanti weanti marked this pull request as ready for review July 3, 2025 04:18
@weanti
Copy link
Author

weanti commented Aug 27, 2025

@jcupitt What's your opinion about this PR? The other PR (the one before this) is updated. If that is merged, then I'll rebase and update my PR.

@jcupitt
Copy link
Collaborator

jcupitt commented Aug 27, 2025

Hi @weanti, sorry, I was on holiday and then got distracted by other projects.

I'll look this over again now.

@jcupitt
Copy link
Collaborator

jcupitt commented Aug 27, 2025

... I saw one final tiny issue in #102, when that's resolved I'll look at this more closely.

@weanti
Copy link
Author

weanti commented Aug 27, 2025

... I saw one final tiny issue in #102, when that's resolved I'll look at this more closely.

Thank you for the feedback.

@weanti
Copy link
Author

weanti commented Sep 30, 2025

Resolved conflicts.

@jcupitt
Copy link
Collaborator

jcupitt commented Sep 30, 2025

I'll read this tomorrow. Thanks for the update!

@jcupitt
Copy link
Collaborator

jcupitt commented Oct 1, 2025

I downloaded the zip file, these are useful samples!

What's the licence? Could we add them to the test suite?

@weanti
Copy link
Author

weanti commented Oct 1, 2025

I downloaded the zip file, these are useful samples!

What's the licence? Could we add them to the test suite?
I downloaded the compsamples_j2k archive (a set of jpeg 2000 compressed images) from ftp://medical.nema.org/MEDICAL/Dicom/DataSets/WG04
This includes the SC1_J2KR and VL1_J2KR images.
Another set of images was dwnloaded from https://www.dcmtk.org/download/images/
nema97cd.zip.
This contains im309 in gems/dlx folder.
I think these are public domain.
I try to find the source of the segmentation_j2k.

src/dicom-file.c Outdated
&length);
uint32_t length = 0;
char* frame_data = NULL;
if ( dcm_is_encapsulated_transfer_syntax(filehandle->desc.transfer_syntax_uid) )
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's a getter for this, I would write:

    const char *syntax = dcm_filehandle_get_transfer_syntax_uid(filehandle);
    uint32_t length = 0;
    char* frame_data = NULL;
    if (dcm_is_encapsulated_transfer_syntax(syntax)) {

.big_endian = is_big_endian(),
};

const uint8_t bytes_per_pixel = desc->bits_allocated/8;
Copy link
Collaborator

Choose a reason for hiding this comment

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

bits_allocated is defined as a uint16, so I think uint8 could (potentially!) be too small here. Let's make this uint16 as well.

libdicom style is for spaces around operators, so:

const uint16_t bytes_per_pixel = desc->bits_allocated / 8;

while( position < frame_end_offset && tag != TAG_SQ_DELIM )
{
read_uint32(&state, &fragment_length, &position);
dcm_read(&state, fragment, fragment_length, &position );
Copy link
Collaborator

Choose a reason for hiding this comment

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

You need dcm_require() here, you must loop on dcm_read() if you need a certain number of bytes.

I would also check the return status of require in case of IO errors.

Copy link
Collaborator

Choose a reason for hiding this comment

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

So this second loop could perhaps be (untested!):

    char *fragment = value;
    while (position < frame_end_offset) {
        uint32_t tag;
        read_tag(&state, &tag, &position);
        if (tag == TAG_SQ_DELIM) {
            break;
        }

        uint32_t fragment_length;
        read_uint32(&state, &fragment_length, &position);
        if (!dcm_require(&state, fragment, fragment_length, &position)) {
            free(value);
            return NULL;
        }
        fragment += fragment_length;
    }

}
uint32_t fragment_length = 0;
// first determine the total length of bytes to be read
while( position < frame_end_offset && tag != TAG_SQ_DELIM )
Copy link
Collaborator

Choose a reason for hiding this comment

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

Two tag reads and checking the first tag at the head of the loop is a little ugly. How about (untested!):

    int64_t position = 0;

    // first determine the total length of bytes to be read
    *length = 0;
    while (position < frame_end_offset) {
        uint32_t tag;
        if (!read_tag(&state, &tag, &position)) {
            return NULL;
        }
        if (tag == TAG_SQ_DELIM) {
            break;
        }
        if (tag != TAG_ITEM) {
            dcm_error_set(error, DCM_ERROR_CODE_PARSE,
                          "reading frame item failed",
                          "no item tag found for frame item");
            return NULL;
        }

        uint32_t fragment_length;
        if (!read_uint32(&state, &fragment_length, &position)) {
            return NULL;
        }
        *length += fragment_length;
        dcm_seekcur(&state, fragment_length, &position);
    }

Now you only have one tag read and the tag check always tests the tag read just before.

You can reorder the loop below in the same way.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks. I managed to simplify the other loop.

}
if ( fragment_idx < num_frames )
{
offsets[fragment_idx] = offsets[fragment_idx-1] + 8/*tag and length field size*/ + length;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure I understand. I think you can have many fragments in each frame, so don't you need two loops here?

for (i = 0; i < num_frames; i++) {
    record position as start of frame i 
    while (frame has fragments)
        skip fragment
}

Copy link
Author

Choose a reason for hiding this comment

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

This part is a kind of an assumption. It may be according to the standard, but the standard is not an easy reading.
This part covers the case where there is no Basic Offset Table (so the frame offsets are absent). In this case we need to find the frame offsets ourselves.
Here is the assumption: if there is no BOT then I assume that each frame consists of only 1 fragment.
Why do I assume this: If each frame could have arbitrary number of fragments then it would be impossible to find out which fagment belongs to which frame. This means that each frame shall have the same amount of fragments.
We could use a different assumption here: number of fragments = C * number of frames, where C >= 1 and C is an integer. This would complicate things a bit. I think encapsulation is used mainly for compressed Transfer Syntaxes and there is no guarantee that each frame can be compressed in a way that they span across exactly C fragment.
Feel free to ask or propose a different approach to assign fragments to frames.

@jcupitt
Copy link
Collaborator

jcupitt commented Oct 1, 2025

Great! Still to do:

  • add some tests
  • add a line to the changelog and credit yourself
  • reformat for libdicom style
  • some minor restructuring, as noted

@fedorov
Copy link
Member

fedorov commented Oct 2, 2025

What's the licence? Could we add them to the test suite?

I downloaded the compsamples_j2k archive (a set of jpeg 2000 compressed images) from ftp://medical.nema.org/MEDICAL/Dicom/DataSets/WG04
This includes the SC1_J2KR and VL1_J2KR images.
Another set of images was dwnloaded from https://www.dcmtk.org/download/images/
nema97cd.zip.
This contains im309 in gems/dlx folder.
I think these are public domain.
I try to find the source of the segmentation_j2k.

@dclunie can you confirm what is the license for the images available from the NEMA FTP server?

@michaelonken @jriesmeier how about the images shared in https://www.dcmtk.org/download/images/ ?

@dclunie
Copy link

dclunie commented Oct 2, 2025

There is no specified license for the CAR97, NEMA97, or WG04 images - we had intended all of these to be publicly usable without restriction (we gave away the CDs at meetings, and shared the images online by anonymous ftp), but never defined a license.

@michaelonken
Copy link

michaelonken commented Oct 3, 2025

David said it all ☝️

[Edit: The folder ddsm/ has its own README. The collection of images stems from the University of South Florida and has originally been provided in non-DICOM format. We converted it (I don't remember the exact context) and offered to host these images on the OFFIS servers (which they were fine with). I found a copy of the now-offline original website in the Internet archive. The images have been part of a research grant; maybe you find more information if you dig through the site.]

@weanti
Copy link
Author

weanti commented Oct 6, 2025

Great! Still to do:

  • add some tests
  • add a line to the changelog and credit yourself
  • reformat for libdicom style
  • some minor restructuring, as noted

I'm working on tests. Will take some time due to limited availability.

@weanti
Copy link
Author

weanti commented Oct 9, 2025

Added some tests. These are rather functional test, because the implementation that handles encapsulated pixel data is not on the public API.
The test data is generated and may not be DICOM conformant e.g. not really loadable by real applications. The tests focus on the "happy path". Shall I add some tests for the error cases as well?

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.

5 participants