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

Update to 1.0 #56

Merged
merged 94 commits into from
Sep 25, 2023
Merged

Update to 1.0 #56

merged 94 commits into from
Sep 25, 2023

Conversation

jcupitt
Copy link
Collaborator

@jcupitt jcupitt commented Jun 25, 2023

This PR updates libdicom to 1.0.

The headline changes are:

New parser

The DICOM file parser has been split off to dicom-parse.c. It's a callback-based parser and knows about the internal structure of PixelData. This generic parser is used by dicom-file.c to load parts of DICOM files into memory, to scan files for features, and to print files to stdout.

This parse API is only internal, for now at least.

Revised Filehandle API

The various filehandle API calls can now be called any number of times, and in any order. They are all optional, so it's now possible to simply open a file and immediately call dcm_filehandle_read_frame().

dcm_filehandle_read_file_meta() and dcm_filehandle_read_metadata() have been renamed as dcm_filehandle_get_file_meta() and dcm_filehandle_get_metadata(). These new functions return a const pointer to libdicom's internal copy of the parsed metadata, and should NOT be destroyed. Use clone to make a copy if you need the result to live longer.

The metadata returned by dcm_filehandle_get_metadata() is only the metadata which can be read quickly and without using much memory. To read all metadata (so including, for example, the sometimes extremely large PerFrameFunctionalGroupSequence) there's a new API call dcm_filehandle_read_metadata(). This function takes a set of stop tags. If necessary, it can be called many times to read all the file metadata.

There's a new API call dcm_filehandle_read_frame_position() which will read a frame at a certain (column, row) position. It automatically takes account of any ordering in PerFrameFunctionalGroupSequence, if present.

DcmBOT is no longer exposed ion the API, since this is now all handled automatically.

Automatic handling of byte ordering and implicit versus explicit encoding has been improved.

Revised data model

dcm_sequence_foreach() and dcm_dataset_foreach() have a client pointer, allow early termination, and track sequence index.

A new function, dcm_element_set_value(), can set the value of an element from a generic byte buffer.

A new function, dcm_element_value_to_string(), makes a formatted character string representing the value of an element. It is handy for displaying values to users in an understandable way.

Some more of dicom-dict.c is in the public API, notably DcmVRClass and associated functions. This was needed by openslide.

Revised logging

There's now dcm_log_set_level() to set the log level (rather than a global variable).

If the environment variable DCM_DEBUG is set, logging defaults to DCM_LOG_DEBUG in dcm_init(). For example:

$ DCM_DEBUG=1 dcm-getframe -o x.jpg 1.3.6.1.4.1.36533.341664110819124279227187194203724415118298.dcm 1
INFO     [Sun Jun 25 15:07:56 2023] - Read filehandle '1.3.6.1.4.1.36533.341664110819124279227187194203724415118298.dcm'
INFO     [Sun Jun 25 15:07:56 2023] - Read frame 1
DEBUG    [Sun Jun 25 15:07:56 2023] - Read frame number #1.
DEBUG    [Sun Jun 25 15:07:56 2023] - Create Data Set.
DEBUG    [Sun Jun 25 15:07:56 2023] - Read Data Element body '00020001'
DEBUG    [Sun Jun 25 15:07:56 2023] - Read Data Element body '00020002'
...

New dcm-dump

There's a new function, dcm_filehandle_print(), which prints all metadata in a file, including pixeldata. This is the function used by dcm-dump.

The output looks like eg.:

$ dcm-dump 1.3.6.1.4.1.36533.341664110819124279227187194203724415118298.dcm
===File Meta Information===
(0002,0002) FileMetaInformationVersion | OB | 2 | <binary value of 2 bytes>
(0002,0002) MediaStorageSOPClassUID | UI | 30 | 1.2.840.10008.5.1.4.1.1.77.1.6
(0002,0002) MediaStorageSOPInstanceUID | UI | 60 | 1.3.6.1.4.1.36533.341664110819124279227187194203724415118298
(0002,0002) TransferSyntaxUID | UI | 22 | 1.2.840.10008.1.2.4.50
(0002,0002) ImplementationClassUID | UI | 10 | 2.16.840.1
(0002,0002) ImplementationVersionName | SH | 12 | GT450_1_0_1
(0002,0002) SourceApplicationEntityTitle | AE | 16 | Leica ScnUtility
===Dataset===
(0008,0008) ImageType | CS | 30 | [ORIGINAL, PRIMARY, OVERVIEW, NONE]
(0008,0008) SOPClassUID | UI | 30 | 1.2.840.10008.5.1.4.1.1.77.1.6
(0008,0008) SOPInstanceUID | UI | 60 | 1.3.6.1.4.1.36533.341664110819124279227187194203724415118298
(0008,0008) StudyDate | DA | 0 | 
(0008,0008) ContentDate | DA | 8 | 20210720
(0008,0008) AcquisitionDateTime | DT | 20 | 20210720114626+0100
(0008,0008) StudyTime | TM | 0 | 
...
(2200,2200) LabelText | UT | 0 | 
(2200,2200) BarcodeValue | LT | 16 | LH20-59902_1_2_1
(5200,5200) SharedFunctionalGroupsSequence [
  ---Item #1---
  (0028,0028) PixelMeasuresSequence [
    ---Item #1---
    (0018,0018) SliceThickness | DS | 2 | 0
    (0028,0028) PixelSpacing | DS | 34 | [0.03916449099779, 0.03916449099779]
  ]
  (0048,0048) OpticalPathIdentificationSequence [
    ---Item #1---
    (0048,0048) OpticalPathIdentifier | SH | 2 | 0
  ]
]
(5200,5200) PerFrameFunctionalGroupsSequence [
  ---Item #1---
  (0020,0020) FrameContentSequence [
    ---Item #1---
    (0018,0018) FrameAcquisitionDateTime | DT | 20 | 20210720114626+0100
    (0018,0018) FrameReferenceDateTime | DT | 20 | 20210720114626+0100
    (0018,0018) FrameAcquisitionDuration | FD | 8 | 0.0242
    (0020,0020) DimensionIndexValues | UL | 8 | [1, 1]
  ]
  (0048,0048) PlanePositionSlideSequence [
    ---Item #1---
    (0040,0040) XOffsetInSlideCoordinateSystem | DS | 14 | 23.69451713562
    (0040,0040) YOffsetInSlideCoordinateSystem | DS | 16 | 58.511749267578
    (0040,0040) ZOffsetInSlideCoordinateSystem | DS | 2 | 0
    (0048,0048) ColumnPositionInTotalImagePixelMatrix | SL | 4 | 1
    (0048,0048) RowPositionInTotalImagePixelMatrix | SL | 4 | 1
  ]
]
(7fe0,7fe0) PixelData | OB | 4294967295 [
  frame 0 | 0 | 
  frame 1 | 178960 | ff d8 ff ee 00 0e 41 64 6f 62 65 00 64 00 00 00 00 ...
]

You can see it's displaying the first few bytes of the frame in hex. It knows about OW and OF and OD as well, and encapsulated and native pixeldata.

It has the very nice feature of printing as it parses, so it can display DICOM files of any size very quickly and only using a little memory.

New argument parser

The two tools now use something like getopt to parse command-line arguments and switches, making them more consistent and easier to use.

Some DICOMS (eg. Leica) don't have this element.
test suite still crashes though, needs some cleaning up
dcm-dump and getframe both work now
now uses almost the same callbacks as the main parser
with a special parse thing that just records the index
Many DICOMs seem to have overlong DT fields, so make this check a
warning rather than a fatal error.

See openslide/openslide#439 (comment)
and move implicit into the parser state
- no more BOT object, it's built into filehandle
- a new API call dcm_filehandle_read_pixeldata() gets ready for frame
  fetch
- dcm_filehandle_read_frame() no longer needs metadata or bot
So we don't load all of localizer images unnecessarily.
jcupitt and others added 15 commits June 26, 2023 22:43
We were just printing length.
We were using char*
ooops, two silly errors
Meson >= 0.60 lets users filter the installed files with
`meson install --tags`.  Tell Meson that the tools are "executables
bundled with a library meant to be used by end users".  Older Meson
versions complain about the install_tags argument but don't fail:

    WARNING: Passed invalid keyword argument "install_tag"
meson: apply `bin` tag to tools
doc/source/tools.rst Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
Comment on lines 22 to 23
:c:func:`dcm_filehandle_get_metadata()`. This function will stop read on tags
which are likely to take a long time to process.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does is stop on any tags other than Pixel Data tags? These tags were given very high numbers so that they end up being located at the end of the data set.

If it's only Pixel Data tags, then I suggest stating that to not confuse the reader and make them wonder about the implementation details and behavior.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it'll stop on any of these tags:

const DcmDataSet *dcm_filehandle_get_metadata(DcmError **error,
                                              DcmFilehandle *filehandle)
{
    // we stop on any of the tags that start a huge group that
    // would take a long time to parse
    static uint32_t stop_tags[] = {
        TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE,
        TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE,
        TAG_PIXEL_DATA,
        TAG_FLOAT_PIXEL_DATA,
        TAG_DOUBLE_PIXEL_DATA,
        0,
    };

I wanted to avoid listing the tags explicitly so people didn't depend on this behaviour and we could change it in the future without breaking compatibility.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE is unfortunately very bulky, but strictly shouldn't be considered a bulkdata element (the sequence is large but the individual nested elements are small).

I suggest loading this metadata element, even if it may take a bit to parse.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It can take many, many seconds to parse, unfortunately.

I tried a test with a 1.9gb Leica DICOM I have:

john@banana ~/pics/openslide/dicom/272320e7-5abb-ccac-8f8b-9c2008f53845_114626 $ time openslide-show-properties 1.3.6.1.4.1.36533.1214640142408762111756320324051133413.dcm > log

real	0m0.083s
user	0m0.012s
sys	0m0.071s

So about 0.1s to assemble the files into a pyramid and get the metadata. If I disable the skip-per-frame-functional-group-sequence feature I see:

john@banana ~/pics/openslide/dicom/272320e7-5abb-ccac-8f8b-9c2008f53845_114626 $ time openslide-show-properties 1.3.6.1.4.1.36533.1214640142408762111756320324051133413.dcm > log
real	0m5.412s
user	0m5.423s
sys	0m0.493s

Over 5s, so around 50x slower. I think a 5s pause on file open would make interactive viewing of DICOM files frustrating, and there's no benefit, since the information is not useful.

Comment on lines +25 to +26
You can read all metadata and control read stop using a sequence of calls to
:c:func:`dcm_filehandle_read_metadata()`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

It appears the implementation has changed. Metadata in DICOM is defined as elements with a value below a given size threshold.

A way to parametrize the behavior could be to pass an size_threshold argument (which by default could be a relatively large number of bytes). This would allow the caller to tune the behavior to their needs, rather than having to call the function several times.

In general, there are only a few elements that tend to have large elements (Pixel Data, ICC Profile, etc.).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's a speed thing -- we need to stop parsing as soon as we see a tag that might potentially be dodgy, otherwise it's too late and we're committed to reading something horrible like TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I have little experience with TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE, but guess that it's similarly bad as TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE.

While these sequence elements are annoying and take time to parse, their content is considered part of the metadata and are generally needed to understand the pixel data element. Therefore, I suggest reading them along with the rest of the metadata.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tried enabling read of TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE and TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE.

john@banana ~/pics/openslide/dicom/272320e7-5abb-ccac-8f8b-9c2008f53845_114626 $ time openslide-show-properties 1.3.6.1.4.1.36533.1214640142408762111756320324051133413.dcm > log

real	0m8.913s
user	0m8.151s
sys	0m0.748s

I think 9s is too slow, and for openslide at least, the information is not useful.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You can use /usr/bin/time -f %M:%e ... to get peak ram use for a command, which can be very useful. With git master libdicom I see:

john@banana ~/pics/openslide/dicom/272320e7-5abb-ccac-8f8b-9c2008f53845_114626 $ /usr/bin/time -f %M:%e openslide-show-properties 1.3.6.1.4.1.36533.1214640142408762111756320324051133413.dcm > log
115148:0.06

So 115mb of memory and 60ms. If I enable the parsing of these two elements I see:

john@banana ~/pics/openslide/dicom/272320e7-5abb-ccac-8f8b-9c2008f53845_114626 $ /usr/bin/time -f %M:%e openslide-show-properties 1.3.6.1.4.1.36533.1214640142408762111756320324051133413.dcm > log
995708:8.91

Almost 1gb of memory use, and 9s. That's more than 100x slower, almost 10x more memory use, and no benefit for openslide.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For the dcm-dump tool, we could introduce a --stop-tag option that enables users to limit reading of metadata.

I agree, that would be a nice addition (dcmdump has something similar).

Copy link
Collaborator Author

@jcupitt jcupitt Aug 1, 2023

Choose a reason for hiding this comment

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

Perhaps renaming dcm_filehandle_get_metadata() as dcm_filehandle_get_metadata_subset() would be clearer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've done the rename.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I really think these kind of performance optimizations belong into OpenSlide.

Providing the caller a (poorly defined) subset of the metadata is in my opinion against our core principle of providing a library that is free of surprises. The library includes functions that allow an application to load metadata in a more sophisticated, performance-optimized manner if needed. We can give examples in the documentation on how this can be achieved.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

dcm_filehandle_get_metadata_subset() is an optional call which can help performance, so to my mind it seems OK.

The main metadata API call (dcm_filehandle_read_metadata()) is very surprise-free, don't you think? One call, reads in all the metadata.

doc/source/usage.rst Outdated Show resolved Hide resolved
* the DcmVRTag enum. As the DICOM standard evolves, numbering must be
* maintained for ABI compatibility.
*/
typedef enum _DcmVR {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I just wanted to look at the online documentation at https://libdicom.readthedocs.io to see whether types and functions with names starting with an underscore get included in the API documentation.

Unfortunately, I realized that the API documentation is empty: https://libdicom.readthedocs.io/en/latest/api.html

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There's a formatted copy of the docs here:

http://www.rollthepotato.net/~john/libdicom/

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for the link!

It looks like these private type definitions get included into the API documentation: http://www.rollthepotato.net/~john/libdicom/api.html#c._DcmVR

It would be nice if we could configure Sphinx or Hawkmoth to ignore declarations with a leading underscore. My understanding is that they are considered private and shouldn't be relied up by users.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Regarding the empty API doc page, is there an issue with the API doc compilation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've not updated that copy of the formatted docs for a little while, I'll update it now. You can generate the docs on your machine too, of course.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OK, updated now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've updated the formatted docs again for these renames.

include/dicom/dicom.h Outdated Show resolved Hide resolved
include/dicom/dicom.h Show resolved Hide resolved
Comment on lines +1730 to +1731
* Scans the PixelData sequence and loads the PerFrameFunctionalGroupSequence,
* if present.
Copy link
Collaborator

Choose a reason for hiding this comment

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

The Per-Frame Functional Group Sequence is not part of the Pixel Data element.

Unless the function reads the entire pixel data, I suggest renaming it. Maybe dcm_filehandle_seek_pixeldata?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It scans pixeldata and finds all the offsets, and also loads in per frame functional group sequence, if present.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The name dcm_filehandle_read_pixeldata implies that the function reads the entire pixel data elements and loads the data into memory.

It scans pixeldata and finds all the offsets

I suggest naming the function dcm_filehandle_find_frame_offsets, dcm_filehandle_build_frame_offset_table, or something along those lines.

also loads in per frame functional group sequence, if present

I understand the motivation to group reading of the per-frame functional groups items with reading the frame offset items. However, conceptually they are two different things since the latter is part of the pixel data element and the former are metadata elements. Therefore, I would keep them separate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In my mind it was something like "get ready to read pixeldata", so it fetches all the parts that will be required for that.

Maybe dcm_filehandle_prepare_read_frame()?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've renamed this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What bothers me about this function is that its behavior is opaque and that it introduces state.

Previously, the caller needed to get the (complete) metadata and the frame offset table prior to reading a frame and that information needed to get passed to the read_frame function. Now, a number of functions need to be called in the right order and it's not clear why this is the case and what's happening under the hood.

Thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You can call the functions in any order, and they are all optional. Although there is internal state (necessary for performance), the user doesn't need to know about it.

dcm_filehandle_prepare_read_frame() is only split out as a separate function for programs which want to control the delay you get with the first read_frame.

Comment on lines +1784 to +1787
DcmFrame *dcm_filehandle_read_frame_position(DcmError **error,
DcmFilehandle *filehandle,
uint32_t column,
uint32_t row);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This assumes that the DICOM object represents a tiled image.

I suggest renaming to dcm_filehandle_read_frame_at_tile_position to make that more clear. I also suggest implementing a check to confirm that this assumption is met.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It'll work for non-tiled images too -- just set col, row to (0, 0).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm...I find this behavior surprising, because non-tiled images don't really have any columns or rows. I would find it cleaner to implement a dcm_filehandle_is_tiled_image function to check whether the DICOM object is a tiled image (we may also want dcm_filehandle_is_image, which can be called internally) and fails if it isn't.

See corresponding Python implementation: https://github.com/ImagingDataCommons/dicomslide/blob/dc63981c5ac5a3a49e74e9ed373d06d5ebd3d46e/src/dicomslide/utils.py#L34

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I imagined a one-frame image (perhaps the label, for example) as a 1x1 array, so dcm_filehandle_read_frame_position() can make sense, I think. It makes openslide a bit simpler:

https://github.com/openslide/openslide/blob/b605126b8994b2748749e020d8126d1114667ce7/src/openslide-vendor-dicom.c#L578-L583

You can see the get-associated-image-pixel-data function can just call off into the main fetch-a-tile, it doesn't need a special read-untiled function, and it doesn't need an is-tiled tester.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should design the library not just with OpenSlide in mind. In my opinion, the library should be useful beyond whole slide imaging. Indexing image frames at a (column, row) position only makes sense for tiled images. If the image is not tiled, there is per definition no tile to be found at (0, 0) position.

OpenSlide will need to check for the image type and handle "associated images" differently.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Openslide was just an example use case -- I think all programs would benefit from a single code path for both tiled and untiled images.

It checks that (column, row) is (0, 0) for untiled images, so I think it's safe and reasonable. Users can always call read_frame(0) if they wish.

Copy link

Choose a reason for hiding this comment

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

I agree John, let's close this discussion so that we can release this, unless anyone strenuously objects.

jcupitt and others added 5 commits July 28, 2023 10:18
Co-authored-by: Markus D. Herrmann <[email protected]>
rename dcm_filehandle_read_pixeldata() as dcm_filehandle_prepare_read_frame()

rename dcm_filehandle_get_metadata() as dcm_filehandle_get_metadata_subset()

revise docs and tests
We were treating AT as a string rather than a fixed length numeric
array. Improve display with dcm-dump as well.
@jcupitt
Copy link
Collaborator Author

jcupitt commented Sep 23, 2023

Looks like the windows runner is failing for some config reason. Maybe %PATH% is messed up? Hopefully they'll fix it soon.

WARNING: Found pkg-config 'C:\\Strawberry\\perl\\bin\\pkg-config.BAT' but it failed when run

@fedorov
Copy link
Member

fedorov commented Sep 23, 2023

There is an error about missing Ninja (https://github.com/ImagingDataCommons/libdicom/actions/runs/6283904801/job/17064777492?pr=56#step:6:210) - maybe try adding its setup as part of the workflow? https://github.com/marketplace/actions/install-ninja-build-tool

image

@bgilbert
Copy link
Collaborator

Looks like Ninja was recently removed from the Windows images: actions/runner-images#8348

this was installed transitively before
@jcupitt jcupitt merged commit 5659c44 into ImagingDataCommons:main Sep 25, 2023
4 checks passed
@jcupitt
Copy link
Collaborator Author

jcupitt commented Sep 25, 2023

Woo! I'll kick the tyres a bit, then tag it as 1.0-rc1 for downstream integration and further tests.

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.

Use of size_t/ssize_t in dicom.h
5 participants