diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..866dff8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{cpp,c,h}] +indent_style = space +indent_size = 4 + +[*.py] +indent_style = space +indent_size = 4 diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 3ebbf55..f4e36c3 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -1,14 +1,8 @@ ---- name: Codespell -on: - push: - branches: [main] - pull_request: - branches: [main] +on: [push, pull_request] -permissions: - contents: read +permissions: {} jobs: codespell: diff --git a/.github/workflows/run_unit_tests.yml b/.github/workflows/run_unit_tests.yml index dcbc1df..5495746 100644 --- a/.github/workflows/run_unit_tests.yml +++ b/.github/workflows/run_unit_tests.yml @@ -46,7 +46,7 @@ jobs: - name: Install build and test dependencies (Windows) if: startsWith(matrix.os, 'windows') - run: pip install meson + run: pip install meson ninja - name: Build and install library shell: bash diff --git a/README.md b/README.md index ea61e0e..bdb227c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Build Status](https://github.com/ImagingDataCommons/libdicom/actions/workflows/run_unit_tests.yml/badge.svg)](https://github.com/ImagingDataCommons/libdicom/actions) +[![Build +Status](https://github.com/ImagingDataCommons/libdicom/actions/workflows/run_unit_tests.yml/badge.svg)](https://github.com/ImagingDataCommons/libdicom/actions) # libdicom @@ -8,19 +9,43 @@ C library and executable tools for reading DICOM files. ### Building from source -```none +```shell meson setup --buildtype release builddir meson compile -C builddir meson install -C builddir ``` -See [the installation documentation](https://libdicom.readthedocs.io/en/latest/installation.html) for build dependencies and installation options. +See [the installation +documentation](https://libdicom.readthedocs.io/en/latest/installation.html) +for build dependencies and installation options. ### Printing the metadata of a DICOM file -```none +```shell dcm-dump data/test_files/sm_image.dcm ``` +### Fetching a frame from a file + +```shell +dcm-getframe -o tile.raw data/test_files/sm_image.dcm 12 +``` + +### From Python + +There's a sample Python binding here: + +https://github.com/ImagingDataCommons/pylibdicom + ## Documentation -User and developer guides as well as API documentation can be found at [libdicom.readthedocs.io](https://libdicom.readthedocs.io/en/latest/). +User and developer guides as well as API documentation can be found at +[libdicom.readthedocs.io](https://libdicom.readthedocs.io/en/latest/). + +# Thanks + +Development of this library was supported by [NCI Imaging Data +Commons](https://imaging.datacommons.cancer.gov/), and has been funded in +whole or in part with Federal funds from the National Cancer Institute, +National Institutes of Health, under Task Order No. HHSN26110071 under +Contract No. HHSN261201500003l. + diff --git a/TODO b/TODO deleted file mode 100644 index 6b798c8..0000000 --- a/TODO +++ /dev/null @@ -1,65 +0,0 @@ -# TODO - -- need to be able to set a stop-at function on filehandles - - -# bot support - -- need more tests and test images - -- extended offset table support seems to be broken? see FIXME comment - - probably will never be used - -- a fast scanner to find the BOT table - - -# data types - -- bot frame offsets should be int64_t to match dcm_seekset(), - pixel_data_offset, etc. - -- should create_element() take a size_t for length? - -- remove dcm_dataset_copy_tags() etc should not use uint32_t? what about - dcm_dataset_count()? - - get_num_frames() uses strtol(), so it's a long - -- does utarray_len() really return uint32? - - no, it's unsigned int - - -# asserts - -- should the foreach funcs take two params (one for error)? are they used? - - nope, unused within libdicom - -- add eg. - - #define DCM_RETURN_VALUE_IF_FAIL(ERROR, CONDITION, RETURN_VALUE) - if (!(CONDITION)) { - dcm_error_set(error, invalid, - "Pointer is null", "%s:%s (%d)" - __FILE__, __FUNCTION__, __LINE__); - return RETURN_VALUE - } - - and use instead of assert in most places - - hmm a sure way to memleaks because of the way that pointer ownership is - handled, fix that first - - -# Tips - -- debug failing test with eg. - - CK_FORK=no CK_RUN_CASE=frame gdb ./check_dicom - -- leak check - - CK_FORK=no valgrind --leak-check=full ./check_dicom - diff --git a/doc/source/api.rst b/doc/source/api.rst index 5db9bce..0d9a8eb 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -1,4 +1,4 @@ API documentation ----------------- -.. c:autodoc:: dicom.h +.. c:autodoc:: dicom/dicom.h diff --git a/doc/source/conf.py b/doc/source/conf.py index f1745b4..50fcdc2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -14,6 +14,7 @@ import os import sys from hawkmoth.util import readthedocs +from hawkmoth.util import compiler from clang.cindex import Config as clang_config # -- Project information ----------------------------------------------------- @@ -22,7 +23,6 @@ copyright = '2021, Markus D. Herrmann' author = 'Markus D. Herrmann' - # -- General configuration --------------------------------------------------- primary_domain = 'c' @@ -57,8 +57,11 @@ # -- Hawkmoth extension ------------------------------------------------------ -cautodoc_root = os.path.abspath('../../include') +hawkmoth_root = os.path.abspath('../../include') readthedocs.clang_setup() +hawkmoth_clang = compiler.get_include_args() +# we need build to get version.h +hawkmoth_clang.append(f"-I{os.path.abspath('../../build')}") if sys.platform == 'darwin': lib_search_dirs = [ '/usr/lib', diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index 649a31c..9e3f709 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -4,7 +4,9 @@ Contributing Coding style ++++++++++++ -Source code should be written following the `K&R (Kernighan & Ritchie) style `_ with a few modifications. +Source code should be written following the `K&R (Kernighan & Ritchie) +style `_ with +a few modifications. * Line length: max 80 characters @@ -13,68 +15,107 @@ Source code should be written following the `K&R (Kernighan & Ritchie) style `_ format using `field lists `_ ``:param:`` and ``:return:`` to document function parameters and return values, respectively, and ``:c:member:``, ``:c:func:``, ``:c:macro:``, ``:c:struct:``, ``:c:union:``, ``:c:enum:``, ``:c:enumerator:``, ``:c:type:``, ``:c:expr:``, ``:c:var:`` from the `Sphinx C domain `_ directive to `cross-reference other C language constructs `_ or to insert a C expression as inline code. + - Documentation of functions and other symbols: balanced, + multi-line ``/** ... */`` comments in `reStructuredText + `_ format using `field lists + `_ + ``:param:`` and ``:return:`` to document function parameters and return + values, respectively, and ``:c:member:``, ``:c:func:``, ``:c:macro:``, + ``:c:struct:``, ``:c:union:``, ``:c:enum:``, ``:c:enumerator:``, + ``:c:type:``, ``:c:expr:``, ``:c:var:`` from the `Sphinx C domain + `_ + directive to `cross-reference other C language constructs + `_ + or to insert a C expression as inline code. - Inline comments in function body: single-line ``//`` C++ style comments * Naming conventions: - - Data structures (``struct`` or ``enum``) and types are named using upper camel case (e.g., ``DcmDataSet``), while functions are named using all lower case with underscores (e.g., ``dcm_dataset_create()``). + - Data structures (``struct`` or ``enum``) and types are named using + upper camel case (e.g., ``DcmDataSet``), while functions are named using + all lower case with underscores (e.g., ``dcm_dataset_create()``). - - Names of ``external`` functions, data structures, and types that are declared in the ``dicom.h`` header file are prefixed with ``dcm_`` or ``Dcm``. Names of ``static`` functions, types, or data structures declared in ``*.c`` files are never prefixed. + - Names of ``external`` functions, data structures, and types that are + declared in the ``dicom.h`` header file are prefixed with ``dcm_`` or + ``Dcm``. Names of ``static`` functions, types, or data structures declared + in ``*.c`` files are never prefixed. Interface +++++++++ -The library exposes an "object-oriented" application programming interface (API), which provides data structures and functions to store, access, and manipulate the data. +The library exposes an "object-oriented" application programming interface +(API), which provides data structures and functions to store, access, +and manipulate the data. To facilitate portability, the ``dicom.h`` header file is restricted to -* C99 version of the standard (C89 + Boolean type from ``stdbool.h`` + fixed-width integer types from ``stdint.h``/``inttypes.h``) +* C99 version of the standard (C89 + Boolean type from ``stdbool.h`` + + fixed-width integer types from ``stdint.h``/``inttypes.h``) + * Opaque data types -* Clear, exact-width integer types (``int16_t``, ``int32_t``, ``int64_t``, ``uint16_t``, ``uint32_t``, and ``uint64_t``) + +* Clear, exact-width integer types (``int16_t``, ``int32_t``, ``int64_t``, + ``uint16_t``, ``uint32_t``, and ``uint64_t``) + * Minimal use of enums Implementation ++++++++++++++ -The ``dicom-data.c`` (Part 5), ``dicom-dict.c`` (Part 6), and ``dicom-file.c`` (Part 10) are implemented based on the C11 version of the standard. +The ``dicom-data.c`` (Part 5), ``dicom-dict.c`` (Part 6), and ``dicom-file.c`` + and ``dicom-parse.c`` (Part 10) are implemented based on the C11 version + of the standard. -The Data Set and Sequence data structures are implemented using the battletested `uthash `_ headers. +The Data Set and Sequence data structures are implemented using the +battletested `uthash `_ headers. Documentation +++++++++++++ -Documentation is written in `reStructuredText `_ format and HTML documents are autogenerated using `Sphinx `_. -API documentation is automatically extracted from the comments in the source code in the ``dicom.h`` header file via the `Hawkmoth Sphinx C Autodoc `_ extension, which relies on `Clang `_ to parse C code. +Documentation is written in `reStructuredText +`_ format and HTML documents +are autogenerated using `Sphinx `_. +API documentation is automatically extracted from the comments in the source +code in the ``dicom.h`` header file via the `Hawkmoth Sphinx C Autodoc +`_ extension, which +relies on `Clang `_ +to parse C code. -Documentation files are located under the ``doc/source`` directory of the repository. -To build the documentation, install ``libclang`` development headers and the Python ``venv`` module, then build with ``meson``: +Documentation files are located under the ``doc/source`` directory of the +repository. To build the documentation, install ``libclang`` development +headers and the Python ``venv`` module, then build with ``meson``: meson compile -C builddir html -The generated documentation files will then be located under the ``builddir/html`` directory. -The ``builddir/html/index.html`` HTML document can be rendered in the web browser. +The generated documentation files will then be located under the +``builddir/html`` directory. The ``builddir/html/index.html`` HTML document +can be rendered in the web browser. Testing +++++++ -Unit test cases are defined and run using `check `_. +Unit test cases are defined and run using `check +`_. -Test files are located under ``/tests`` and can be built and run using ``meson``:: +Test files are located under ``/tests`` and can be built and run using +``meson``:: meson test -C builddir @@ -87,8 +128,8 @@ For example:: valgrind --leak-check=full dcm-dump data/test_files/sm_image.dcm - -Unit testing and dynamic analysis can also be performed using the provided `Dockerfile` (located in the root of the repository):: +Unit testing and dynamic analysis can also be performed using the provided +`Dockerfile` (located in the root of the repository):: docker build -t dcm-testing . docker run dcm-testing diff --git a/doc/source/installation.rst b/doc/source/installation.rst index 43f40f8..38ee59e 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -4,7 +4,8 @@ Installation Building ++++++++ -The library and executables can be built using `Meson `_: +The library and executables can be built using `Meson +`_: .. code:: bash @@ -45,15 +46,18 @@ Build for development and debugging: .. code:: bash - CFLAGS="-DDEBUG" meson setup builddir + meson setup builddir --buildtype debug meson compile -C builddir Optional dependencies +++++++++++++++++++++ -This package uses `check `_ for unit testing and `uthash `_ for data structures. -It will automatically download and build both libraries, or can use system copies. +This package uses `check `_ for unit +testing and `uthash `_ for data +structures. It will automatically download and build both libraries, +or can use system copies. + To install system copies: On Debian-based Linux distributions: diff --git a/doc/source/introduction.rst b/doc/source/introduction.rst index a144410..eeb2262 100644 --- a/doc/source/introduction.rst +++ b/doc/source/introduction.rst @@ -7,22 +7,34 @@ Supported parts of the standard +++++++++++++++++++++++++++++++ * Part 5 - Data Structures and Encoding + * Part 6 - Data Dictionary + * Part 10 - Media Storage and File Format for Media Interchange -Note that the library does not read the Pixel Data element at once, but instead provides an interface to randomly access individual frame items of Pixel Data elements. -However, the library does not concern itself with decoding the values of frame items. +Note that the library does not read the Pixel Data element at once, but +instead provides an interface to randomly access individual frame items +of Pixel Data elements. However, the library does not concern itself with +decoding the values of frame items. Design goals ++++++++++++ -The library inspires to +The library aims to: * Provide a stable application binary interface (ABI) -* Be highly portable and run on Linux, macOS, and Windows operating systems with different architectures + +* Be highly portable and run on Linux, Unix, macOS, and Windows operating + systems with different architectures + * Be dead simple and free of surprises + * Have no external build or runtime dependencies -* Be easily callable from other languages via a C foreign function interface (FFI) + +* Be easily callable from other languages via a C foreign function interface + (FFI) + * Be fast to compile and produce small binaries + * Be easty to compile to WebAssembly using Emscripten diff --git a/doc/source/tools.rst b/doc/source/tools.rst index 4fb3749..1d4c2d0 100644 --- a/doc/source/tools.rst +++ b/doc/source/tools.rst @@ -4,15 +4,33 @@ Command line tools dcm-dump ++++++++ -The ``dcm-dump`` command line tool reads the metadata of a DICOM Data Set stored in a DICOM Part10 file and prints the metadata to standard output: +The ``dcm-dump`` command line tool reads the metadata of a DICOM Data Set +stored in a DICOM Part10 file and prints the metadata to standard output: .. code:: bash dcm-dump /path/to/file.dcm | grep -e Modality -e ImageType - Refer to the man page of the tool for further instructions: .. code:: bash man dcm-dump + +dcm-getframe +++++++++++++ + +The ``dcm-getframe`` command line tool will read numbered frames from a DICOM +file and print then to `stdout`. Use the ``-o`` flag to write to a file +instead. Frames are numbered from 1 in the order they appear in the PixelData +sequence. + +.. code:: bash + + dcm-getframe /path/to/file.dcm 12 > x.jpg + +Refer to the man page of the tool for further instructions: + +.. code:: bash + + man dcm-getframe diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 7c3aee7..fbc56c7 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -4,17 +4,43 @@ Usage API overview ++++++++++++ +A Filehandle (:c:type:`DcmFilehandle`) enables access of a `DICOM file +`_, +which contains an encoded Data Set representing a SOP Instance. +A Filehandle can be created via :c:func:`dcm_filehandle_create_from_file()` +or :c:func:`dcm_filehandle_create_from_memory()` , and destroyed via +:c:func:`dcm_filehandle_destroy()`. You can make your own load functions +to load from other IO sources, see :c:func:`dcm_filehandle_create()`. + +The content of a Part10 file can be read using various functions. + +The `File Meta Information +`_ +can be accessed via :c:func:`dcm_filehandle_get_file_meta()`. + +The principal metadata of the Data Set can be accessed via +:c:func:`dcm_filehandle_get_metadata_subset()`. This function will stop +read on tags which are likely to take a long time to process. + +You can read all metadata and control read stop using a sequence of calls to +:c:func:`dcm_filehandle_read_metadata()`. + +In case the Data Set contained in a Part10 file represents an Image instance, +individual frames may be read out with :c:func:`dcm_filehandle_read_frame()`. +Use :c:func:`dcm_filehandle_read_frame_position()` to read the frame at a +certain (column, row) position. + A `Data Element `_ -(:c:type:`DcmElement`) is an immutable data container for -storing values. +(:c:type:`DcmElement`) is an immutable data container for storing values. Every data element has a tag indicating its purpose. Tags are 32-bit -unsigned ints with the top 16 bits indicating the group and the bottom 16 the -element. They are usually written in hexadecimal, perhaps 0x00400554, meaning -element 0x554 of group 0x40, or as keywords, in this case `SpecimenUID`. You -can get the tag from its corresponding keyword with :c:func:`dcm_dict_tag_from_keyword()`, -or find the keyword from a tag with :c:func:`dcm_dict_keyword_from_tag()`. +unsigned ints with the top 16 bits indicating the group and the bottom 16 +the element. They are usually written in hexadecimal, perhaps ``0x00400554``, +meaning element ``0x554`` of group ``0x40``, or as keywords, in this case +``"SpecimenUID"``. You can get the tag from its corresponding keyword with +:c:func:`dcm_dict_tag_from_keyword()`, or find the keyword from a tag with +:c:func:`dcm_dict_keyword_from_tag()`. Every Data Element has a `Value Representation (VR) `_, @@ -24,8 +50,8 @@ numeric strings (strings of characters encoding numbers using the decimal or scientific notation), character strings (text of restriction length and character repertoire), or byte strings (unicode). Each VR is represented using a standard C type (e.g,. VR ``"US"`` has type ``uint16_t`` and VR -``"UI"`` has type ``char *``) and additional value constraints may be checked -at runtime (e.g., the maximal capacity of a character string). +``"UI"`` has type ``char *``) and additional value constraints may be +checked at runtime (e.g., the maximal capacity of a character string). The VR must be appropriate for the tag. Use :c:func:`dcm_vr_from_tag()` to find the set of allowed VRs for a tag. Use :c:func:`dcm_is_valid_vr_for_tag()` @@ -39,12 +65,12 @@ a Data Element may thus contain an array of values. A Data Element can be created with :c:func:`dcm_element_create()`, it can have a value assigned to it with eg. -:c:func:`dcm_element_set_value_integer()`, and it can be destroyed with +:c:func:`dcm_element_set_value_integer()`, and it can be destroyed with :c:func:`dcm_element_destroy()`. See `Memory management `_ below for details on pointer ownership. An individual value can be retrieved via the getter functions like -(e.g., :c:func:`dcm_element_get_value_integer()`). Note that in case of +(e.g., :c:func:`dcm_element_get_value_integer()`). Note that in case of character string or binary values, the getter function returns the pointer to the stored character array (``const char *``) and that pointer is only valid @@ -84,7 +110,7 @@ via :c:func:`dcm_sequence_create()` and destroyed via :c:func:`dcm_sequence_destroy()`. Data Sets can be added to a Sequence via :c:func:`dcm_sequence_append()`, removed from a Sequence via :c:func:`dcm_sequence_remove()`, and retrieved from a Sequence via -:c:func:`dcm_sequence_get()`. +:c:func:`dcm_sequence_get()`. When a Data Set is added to a sequence, the sequence takes over ownership of the memory allocated for the Data Set (and consequently of each contained @@ -95,27 +121,6 @@ Data Set is removed from a sequence, the Data Set is destroyed (i.e., the allocated memory is freed). When a Sequence is destroyed, all contained Data Sets are also automatically destroyed. -A Filehandle (:c:type:`DcmFilehandle`) enables access of a `DICOM file -`_, -which contains an encoded Data Set representing a SOP Instance. -A Filehandle can be created via :c:func:`dcm_filehandle_create_from_file()` -or :c:func:`dcm_filehandle_create_from_memory()` , and destroyed via -:c:func:`dcm_filehandle_destroy()`. You can make your own load functions -to load from other IO sources, see :c:func:`dcm_filehandle_create()`. - -The content of a Part10 file can be read -using various functions. The `File Meta Information -`_ -can be read via :c:func:`dcm_filehandle_read_file_meta()`. The metadata -of the Data Set (i.e., all Data Elements with the exception of the Pixel -Data Element) can be read via :c:func:`dcm_filehandle_read_metadata()`. -In case the Data Set contained in a Part10 file represents an Image -instance, individual Frame Items of the Pixel Data Element can be read -via :c:func:`dcm_filehandle_read_frame()` using a Basic Offset Table -(BOT) Item. The BOT Item may either be read from a Filehandle via -:c:func:`dcm_filehandle_read_bot()` or built for a Filehandle via -:c:func:`dcm_filehandle_build_bot()`. - Thread safety +++++++++++++ @@ -141,9 +146,9 @@ Error handling Library functions which can return an error take a double pointer to a :c:type:`DcmError` struct as a first argument. If an error is detected, this pointer will be updated to refer to an error object. You can extract -a :c:type:`DcmErrorCode` with :c:func:`dcm_error_code()`, an error summary -with :c:func:`dcm_error_summary()`, and a detailed error message with -:c:func:`dcm_error_message()`. After presenting the error to the user, +a :c:type:`DcmErrorCode` with :c:func:`dcm_error_get_code()`, an error summary +with :c:func:`dcm_error_get_summary()`, and a detailed error message with +:c:func:`dcm_error_get_message()`. After presenting the error to the user, call :c:func:`dcm_error_clear()` to clear the error pointer and free any allocated memory. @@ -159,14 +164,14 @@ For example: #include int main() { - const char *file_path = "does not exist"; + const char *file_path = "bad-file"; DcmError *error = NULL; DcmFilehandle *filehandle = dcm_filehandle_create_from_file(&error, file_path); if (filehandle == NULL) { - printf("error detected: %s\n", dcm_error_code_str(dcm_error_code(error))); - printf("summary: %s\n", dcm_error_summary(error)); - printf("message: %s\n", dcm_error_message(error)); + printf("error detected: %s\n", dcm_error_code_str(dcm_error_get_code(error))); + printf("summary: %s\n", dcm_error_get_summary(error)); + printf("message: %s\n", dcm_error_get_message(error)); dcm_error_clear(&error); return 1; } @@ -196,30 +201,26 @@ destroyed. If this function fails, ownership does not transfer. libdicom objects can also contain references to data structures allocated by -other programs, for example, arrays of numeric values. +other programs, for example, arrays of numeric values. .. code-block:: c int *values = pointer to array of integers; uint32_t vm = number of ints in array; - if( !dcm_element_set_value_numeric_multi(error, - element, - values, - vm, - true)) { + if( !dcm_element_set_value_numeric_multi(error, element, values, vm, true)) { handle error; } -The final parameter, `steal` sets whether ownership of the pointer to the -array should be "stolen" by libdicom. If it is true, then libdicom will use +The final parameter, `steal`, sets whether ownership of the pointer to the +array should be "stolen" by libdicom. If it is true, then libdicom will use :c:func:`free()` to free the array when the element is freed. If it is false, -libdiom will take a copy of the array. +libdicom will make a copy of the array. Getting started +++++++++++++++ Below is an example for reading metadata from a DICOM Part10 file and -printing it to standard output: +printing an element to standard output: .. code:: c @@ -237,7 +238,8 @@ printing it to standard output: return 1; } - DcmDataSet *metadata = dcm_filehandle_read_metadata(&error, filehandle); + const DcmDataSet *metadata = + dcm_filehandle_get_metadata_subset(&error, filehandle); if (metadata == NULL) { dcm_error_log(error); dcm_error_clear(&error); @@ -245,10 +247,20 @@ printing it to standard output: return 1; } - dcm_dataset_print(metadata, 0); + const char *num_frames; + uint32_t tag = dcm_dict_tag_from_keyword("NumberOfFrames"); + DcmElement *element = dcm_dataset_get(&error, metadata, tag); + if (element == NULL || + !dcm_element_get_value_string(&error, element, 0, &num_frames)) { + dcm_error_log(error); + dcm_error_clear(&error); + dcm_filehandle_destroy(filehandle); + return 1; + } + + printf("NumerOfFrames == %s\n", num_frames); dcm_filehandle_destroy(filehandle); - dcm_dataset_destroy(metadata); return 0; } diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index f0c13dc..842cf94 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -7,11 +7,6 @@ #ifndef DCM_INCLUDED #define DCM_INCLUDED -#if defined(_WIN32) && !defined(__GNUC__) -#include -typedef SSIZE_T ssize_t; -#endif - #ifdef _WIN32 #if DCM_STATIC #define DCM_EXTERN extern @@ -100,6 +95,11 @@ typedef SSIZE_T ssize_t; */ #define DCM_CAPACITY_TM 14 +/** + * Maximum number of characters in values with Value Representation UC. + */ +#define DCM_CAPACITY_UC 4294967294 + /** * Maximum number of characters in values with Value Representation UI. */ @@ -115,36 +115,10 @@ typedef SSIZE_T ssize_t; */ #define DCM_CAPACITY_UT 4294967294 -/** - * Part10 file - */ -typedef struct _DcmFilehandle DcmFilehandle; - -/** - * Data Element - */ -typedef struct _DcmElement DcmElement; - -/** - * Data Set - */ -typedef struct _DcmDataSet DcmDataSet; - -/** - * Sequence of Data Set Items +/* We need forward references for these types. */ typedef struct _DcmSequence DcmSequence; -/** - * Frame Item of Pixel Data Element - */ -typedef struct _DcmFrame DcmFrame; - -/** - * Basic Offset Table (BOT) Item of Pixel Data Element - */ -typedef struct _DcmBOT DcmBOT; - /** * Start up libdicom. * @@ -161,10 +135,25 @@ DCM_EXTERN DCM_CONSTRUCTOR void dcm_init(void); +/* Our copy of getopt, since non-glibc platforms are missing this. + * Used by our tools. + */ +DCM_EXTERN +char *dcm_optarg; +DCM_EXTERN +int dcm_optind, dcm_opterr, dcm_optopt, dcm_optreset; +DCM_EXTERN +int dcm_getopt(int nargc, char * const nargv[], const char *ostr); + +/** + * Error return object. + */ +typedef struct _DcmError DcmError; + /** * Enumeration of error codes. */ -enum _DcmErrorCode { +typedef enum _DcmErrorCode { /** Out of memory */ DCM_ERROR_CODE_NOMEM = 1, /** Invalid parameter */ @@ -173,66 +162,7 @@ enum _DcmErrorCode { DCM_ERROR_CODE_PARSE = 3, /** IO error */ DCM_ERROR_CODE_IO = 4, -}; - -/** - * Error codes - */ -typedef enum _DcmErrorCode DcmErrorCode; - -/** - * An enum of Value Representations. - * - * Value Representations which are not known to libdicom will be coded as - * DCM_VR_ERROR (unknown Value Representation). - * - * Note to maintainers: this enum must match the table in dicom-dict.c, and - * the DcmVRTag enum. As the DICOM standard evolves, numbering must be - * maintained for ABI compatibility. - */ -typedef enum _DcmVR { - // error value, returned for eg. unknown SR strings - DCM_VR_ERROR = -1, - - // allowed VRs for DcmElement - DCM_VR_AE = 0, - DCM_VR_AS, - DCM_VR_AT, - DCM_VR_CS, - DCM_VR_DA, - DCM_VR_DS, - DCM_VR_DT, - DCM_VR_FL, - DCM_VR_FD, - DCM_VR_IS, - DCM_VR_LO, - DCM_VR_LT, - DCM_VR_OB, - DCM_VR_OD, - DCM_VR_OF, - DCM_VR_OW, - DCM_VR_PN, - DCM_VR_SH, - DCM_VR_SL, - DCM_VR_SQ, - DCM_VR_SS, - DCM_VR_ST, - DCM_VR_TM, - DCM_VR_UI, - DCM_VR_UL, - DCM_VR_UN, - DCM_VR_US, - DCM_VR_UT, - DCM_VR_UR, - DCM_VR_UC, - DCM_VR_OL, - DCM_VR_OV, - DCM_VR_SV, - DCM_VR_UV, - - // used to check enums for range errors, add new VRs before this - DCM_VR_LAST -} DcmVR; +} DcmErrorCode; /** * Convert an error code to a human-readable string. @@ -254,11 +184,6 @@ const char *dcm_error_code_str(DcmErrorCode code); DCM_EXTERN const char *dcm_error_code_name(DcmErrorCode code); -/** - * Error return object. - */ -typedef struct _DcmError DcmError; - /** * Set an error. * @@ -286,7 +211,7 @@ DCM_EXTERN void dcm_error_clear(DcmError **error); /** -* Get a summary of the error. + * Get a summary of the error. * * Do not free this result. The pointer will be valid as long as error is * valid. @@ -329,10 +254,47 @@ DcmErrorCode dcm_error_get_code(DcmError *error); DCM_EXTERN void dcm_error_log(DcmError *error); +/** + * Print an error message to stderr. + * + * :param error: Error object + */ +DCM_EXTERN +void dcm_error_print(DcmError *error); + + +/** + * Free an allocated memory area. + * + * Any memory allocated by libdicom and returned to the calling program + * should be freed with this. + * + * :param pointer: Memory area to free + */ +DCM_EXTERN +void dcm_free(void *pointer); + +/** + * Allocate and zero an area of memory. + * + * Any memory which you pass to libdicom and which you ask libdicom to manage + * with a "steal" flag should be allcoatyed with one of the libdicom memory + * allocators. + * + * :param error: Pointer to error object + * :param n: Number of items to allocate + * :param size: Size of each item in bytes + * + * :return: Pointer to memory area + */ +DCM_EXTERN +void *dcm_calloc(DcmError **error, uint64_t n, uint64_t size); + + /** * Enumeration of log levels */ -enum _DcmLogLevel { +typedef enum _DcmLogLevel { /** Critical */ DCM_LOG_CRITICAL = 50, /** Error */ @@ -345,18 +307,34 @@ enum _DcmLogLevel { DCM_LOG_DEBUG = 10, /** Not set (no logging) */ DCM_LOG_NOTSET = 0, -}; +} DcmLogLevel; /** - * Log level + * Set the log level. + * + * :param log_level: New log level. + * :return: previous log level */ -typedef enum _DcmLogLevel DcmLogLevel; +DCM_EXTERN +DcmLogLevel dcm_log_set_level(DcmLogLevel log_level); /** - * Global variable to set log level. + * Log function. See dcm_log_set_logf(). + */ +typedef void (*DcmLogf)(const char *level, const char *format, va_list args); + +/** + * Set the log function. + * + * This function will be used to log any error or warning messages from the + * library. The default DcmLogf function prints messages to stderr. Set to + * NULL to disable all logging. + * + * :param logf: New log function. + * :return: previous log function */ DCM_EXTERN -DcmLogLevel dcm_log_level; +DcmLogf dcm_log_set_logf(DcmLogf logf); /** * Write critical log message to stderr stream. @@ -411,6 +389,100 @@ void dcm_log_debug(const char *format, ...); DCM_EXTERN const char *dcm_get_version(void); +/** + * An enum of Value Representations. + * + * Value Representations which are not known to libdicom will be coded as + * DCM_VR_ERROR (unknown Value Representation). + * + * Note to maintainers: this enum must match the table in dicom-dict.c, and + * the DcmVRTag enum. As the DICOM standard evolves, numbering must be + * maintained for ABI compatibility. + */ +typedef enum _DcmVR { + // error value, returned for eg. unknown SR strings + DCM_VR_ERROR = -1, + + // allowed VRs for DcmElement + DCM_VR_AE = 0, + DCM_VR_AS, + DCM_VR_AT, + DCM_VR_CS, + DCM_VR_DA, + DCM_VR_DS, + DCM_VR_DT, + DCM_VR_FL, + DCM_VR_FD, + DCM_VR_IS, + DCM_VR_LO, + DCM_VR_LT, + DCM_VR_OB, + DCM_VR_OD, + DCM_VR_OF, + DCM_VR_OW, + DCM_VR_PN, + DCM_VR_SH, + DCM_VR_SL, + DCM_VR_SQ, + DCM_VR_SS, + DCM_VR_ST, + DCM_VR_TM, + DCM_VR_UI, + DCM_VR_UL, + DCM_VR_UN, + DCM_VR_US, + DCM_VR_UT, + DCM_VR_UR, + DCM_VR_UC, + DCM_VR_OL, + DCM_VR_OV, + DCM_VR_SV, + DCM_VR_UV, + + // used to check enums for range errors, add new VRs before this + DCM_VR_LAST +} DcmVR; + +/** + * The general class of the value associated with a Value Representation. + * + * DCM_VR_CLASS_STRING_MULTI -- one or more null-terminated strings, cannot + * contain backslash + * + * DCM_VR_CLASS_STRING_SINGLE -- a single null-terminated string, backslash + * allowed + * + * DCM_VR_CLASS_NUMERIC_DECIMAL -- one or more binary floating point numeric + * values, other fields give sizeof(type) + * + * DCM_VR_CLASS_NUMERIC_INTEGER -- one or more binary integer numeric + * values, other fields give sizeof(type) + * + * DCM_VR_CLASS_BINARY -- an uninterpreted array of bytes, length in the + * element header + * + * DCM_VR_CLASS_SEQUENCE -- Value Representation is a sequence + */ +typedef enum _DcmVRClass { + DCM_VR_CLASS_ERROR, + DCM_VR_CLASS_STRING_MULTI, + DCM_VR_CLASS_STRING_SINGLE, + DCM_VR_CLASS_NUMERIC_DECIMAL, + DCM_VR_CLASS_NUMERIC_INTEGER, + DCM_VR_CLASS_BINARY, + DCM_VR_CLASS_SEQUENCE +} DcmVRClass; + +/** + * Find the general class for a particular Value Representation. + * + * :param vr: The Value Representation + * + * :return: The general class of that Value Representation + */ +DCM_EXTERN +DcmVRClass dcm_dict_vr_class(DcmVR vr); + /** * Turn a string Value Representation into an enum value. * @@ -433,7 +505,7 @@ const char *dcm_dict_str_from_vr(DcmVR vr); /** * Look up the Keyword of an Attribute in the Dictionary. - * + * * Returns NULL if the tag is not recognised. * * :param tag: Attribute Tag @@ -456,9 +528,9 @@ DCM_EXTERN uint32_t dcm_dict_tag_from_keyword(const char *keyword); /** - * Find the Value Representation for a tag. + * Find the Value Representation for a tag. * - * This will return DCM_VR_ERROR if the tag is unknown, or does not have a + * This will return DCM_VR_ERROR if the tag is unknown, or does not have a * unique Value Representation. * * :param tag: Attribute Tag @@ -532,8 +604,9 @@ bool dcm_is_encapsulated_transfer_syntax(const char *transfer_syntax_uid); /** - * Data Element + * Data Element. */ +typedef struct _DcmElement DcmElement; /** * Create a Data Element for a tag. @@ -638,7 +711,7 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element); * :param error: Pointer to error object * :param element: Pointer to Data Element * :param index: Zero-based index of value within the Data Element - * :param value: Pointer to return location for value + * :param value: Pointer to return location for value * * :return: true on success */ @@ -673,7 +746,7 @@ bool dcm_element_set_value_string(DcmError **error, bool steal); /** -* Set the value of a Data Element to an array of character strings. + * Set the value of a Data Element to an array of character strings. * * The Data Element must have a Tag that allows for a * character string Value Representation and for a @@ -707,7 +780,7 @@ bool dcm_element_set_value_string_multi(DcmError **error, * :param error: Pointer to error object * :param element: Pointer to Data Element * :param index: Zero-based index of value within the Data Element - * :param value: Pointer to return location for value + * :param value: Pointer to return location for value * * :return: true on success */ @@ -719,7 +792,7 @@ bool dcm_element_get_value_integer(DcmError **error, /** * Set the value of a Data Element to an integer. - * + * * The Data Element must have a Tag that allows for a * integer Value Representation. * If that is not the case, the function will fail. @@ -737,7 +810,7 @@ bool dcm_element_set_value_integer(DcmError **error, /** * Set the value of a Data Element to a number. - * + * * The Data Element must have a Tag that allows for a * numeric Value Representation. * If that is not the case, the function will fail. @@ -768,25 +841,25 @@ bool dcm_element_set_value_numeric_multi(DcmError **error, /** * Get a floating-point value from a Data Element. * - * The Data Element Value Reepresentation may be either single- or + * The Data Element Value Reepresentation may be either single- or * double-precision floating point. * * :param error: Pointer to error object * :param element: Pointer to Data Element * :param index: Zero-based index of value within the Data Element - * :param value: Pointer to return location for value + * :param value: Pointer to return location for value * * :return: true on success */ DCM_EXTERN -bool dcm_element_get_value_floatingpoint(DcmError **error, - const DcmElement *element, - uint32_t index, - double *value); +bool dcm_element_get_value_decimal(DcmError **error, + const DcmElement *element, + uint32_t index, + double *value); /** * Set the value of a Data Element to a floating-point. - * + * * The Data Element must have a Tag that allows for a * floating-point Value Representation. * If that is not the case, the function will fail. @@ -798,9 +871,9 @@ bool dcm_element_get_value_floatingpoint(DcmError **error, * :return: true on success */ DCM_EXTERN -bool dcm_element_set_value_floatingpoint(DcmError **error, - DcmElement *element, - double value); +bool dcm_element_set_value_decimal(DcmError **error, + DcmElement *element, + double value); /** * Get a binary value from a Data Element. @@ -809,21 +882,20 @@ bool dcm_element_set_value_floatingpoint(DcmError **error, * * :param error: Pointer to error object * :param element: Pointer to Data Element - * :param value: Pointer to return location for value + * :param value: Pointer to return location for value * * :return: true on success */ DCM_EXTERN bool dcm_element_get_value_binary(DcmError **error, const DcmElement *element, - const char **value); + const void **value); /** * Set the value of a Data Element to binary data. - * - * The Data Element must have a Tag that allows for a - * binary Value Representation. - * If that is not the case, the function will fail. + * + * The Data Element must have a Tag that allows for a binary Value + * Representation. If that is not the case, the function will fail. * * On success, if `steal` is true, ownership of `value` passes to * `element`, i.e. it will be freed when `element` is destroyed. If `steal` is @@ -840,16 +912,33 @@ bool dcm_element_get_value_binary(DcmError **error, DCM_EXTERN bool dcm_element_set_value_binary(DcmError **error, DcmElement *element, - char *value, + void *value, uint32_t length, bool steal); +/* Set a value for an Element from a generic byte buffer. The byte buffer must + * have been correctly formatted for the VR of this Element. + * + * :param error: Pointer to error object + * :param element: Pointer to Data Element + * :param value: Pointer to value + * :param length: Length in bytes of the value + * :param steal: if true, ownership of the value passes to element + * + * :return: true on success + */ +bool dcm_element_set_value(DcmError **error, + DcmElement *element, + char *value, + uint32_t length, + bool steal); + /** * Get a sequence value from a Data Element. * * :param error: Pointer to error object * :param element: Pointer to Data Element - * :param value: Pointer to return location for value + * :param value: Pointer to return location for value * * :return: true on success */ @@ -858,10 +947,9 @@ bool dcm_element_get_value_sequence(DcmError **error, const DcmElement *element, DcmSequence **value); - /** * Set the value of a Data Element to a Sequence. - * + * * The Data Element must have a Tag that allows for * Value Representation ``"SQ"``. * If that is not the case, the function will fail. @@ -879,6 +967,16 @@ bool dcm_element_set_value_sequence(DcmError **error, DcmElement *element, DcmSequence *value); +/** + * Make a string suitable for display to a user from the value of an element. + * + * The return result must be freed with free(). The result may be NULL. + * + * :return: string to display + */ +DCM_EXTERN +char *dcm_element_value_to_string(const DcmElement *element); + /** * Print a Data Element. * @@ -900,6 +998,7 @@ void dcm_element_destroy(DcmElement *element); /** * Data Set */ +typedef struct _DcmDataSet DcmDataSet; /** * Create an empty Data Set. @@ -977,15 +1076,24 @@ DcmElement *dcm_dataset_get_clone(DcmError **error, /** * Iterate over Data Elements in a Data Set. * - * Does not sort Data Elements, but iterates over them in the order in which - * they were originally inserted into the Data Set. + * The user function should return true to continue looping, or false to + * terminate the loop early. * - * :param dataset: Pointer to Data Set + * The result is true if the whole Data Set returned true, or false if one + * call requested early termination. + * + * The function must not modify the Data Set. + * + * :param seq: Pointer to Data Set * :param fn: Pointer to function that should be called for each Data Element + * :param client: Client data for function + * + * :return: true if all functions return true */ DCM_EXTERN -void dcm_dataset_foreach(const DcmDataSet *dataset, - void (*fn)(const DcmElement *element)); +bool dcm_dataset_foreach(const DcmDataSet *dataset, + bool (*fn)(const DcmElement *element, void *client), + void *client); /** * Fetch a Data Element from a Data Set, or NULL if not present. @@ -1025,7 +1133,8 @@ uint32_t dcm_dataset_count(const DcmDataSet *dataset); * the copy operation fails. */ DCM_EXTERN -void dcm_dataset_copy_tags(const DcmDataSet *dataset, uint32_t *tags, +void dcm_dataset_copy_tags(const DcmDataSet *dataset, + uint32_t *tags, uint32_t n); /** @@ -1070,8 +1179,8 @@ void dcm_dataset_destroy(DcmDataSet *dataset); */ /** - * Create a Sequence, i.e., a collection of Data Set items that represent the - * value of a Data Element with Value Representation SQ (Sequence). + * Create a Sequence, i.e., an ordered list of Data Set items that represent + * the value of a Data Element with Value Representation SQ (Sequence). * * Note that created object represents the value of a Data Element rather * than a Data Element itself. @@ -1112,14 +1221,27 @@ DcmDataSet *dcm_sequence_get(DcmError **error, const DcmSequence *seq, uint32_t index); /** - * Iterate over Data Set items in a Sequence. + * Iterate over Data Sets in a Sequence. + * + * The user function should return true to continue looping, or false to + * terminate the loop early. + * + * The result is true if the whole sequence returned true, or false if one + * call requested early termination. + * + * The function must not modify the seqeucence. * * :param seq: Pointer to Sequence - * :param fn: Pointer to function that should be called for each Data Set item + * :param fn: Pointer to function that should be called for each Data Set + * :param client: Client data for function + * + * :return: Pointer to Data Set item */ DCM_EXTERN -void dcm_sequence_foreach(const DcmSequence *seq, - void (*fn)(const DcmDataSet *item)); +bool dcm_sequence_foreach(const DcmSequence *seq, + bool (*fn)(const DcmDataSet *dataset, + uint32_t index, void *client), + void *client); /** * Remove a Data Set item from a Sequence. @@ -1171,11 +1293,12 @@ void dcm_sequence_destroy(DcmSequence *seq); /** - * Frame + * Frame Item of Pixel Data Element * * Encoded pixels of an individual pixel matrix and associated * descriptive metadata. */ +typedef struct _DcmFrame DcmFrame; /** * Create a Frame. @@ -1358,104 +1481,136 @@ void dcm_frame_destroy(DcmFrame *frame); /** - * Basic Offset Table (BOT). + * Part 10 File */ +typedef struct _DcmFilehandle DcmFilehandle; + +typedef struct _DcmIOMethods DcmIOMethods; /** - * Create a Basic Offset Table. - * - * :param error: Pointer to error object - * :param offsets: Offset of each Frame in the Pixel Data Element - * (measured from the first byte of the first Frame). - * :param num_frames: Number of Frames in the Pixel Data Element - * :first_frame_offset: Offset from pixel_data_offset to the first byte of the - * first frame - * - * The created object takes over ownership of the memory referenced by `offsets` - * and frees it when the object is destroyed or if the creation fails. - * - * :return: Basic Offset Table + * An object we can read from. */ -DCM_EXTERN -DcmBOT *dcm_bot_create(DcmError **error, - ssize_t *offsets, - uint32_t num_frames, - ssize_t first_frame_offset); +typedef struct _DcmIO { + const DcmIOMethods *methods; + // more private fields follow +} DcmIO; /** - * Get number of Frame offsets in the Basic Offset Table. + * A set of IO methods, see dcm_io_create(). + */ +struct _DcmIOMethods { + /** Open an IO object */ + DcmIO *(*open)(DcmError **error, void *client); + + /** Close an IO object */ + void (*close)(DcmIO *io); + + /** Read from an IO object, semantics as POSIX read() */ + int64_t (*read)(DcmError **error, DcmIO *io, char *buffer, int64_t length); + + /** Seek an IO object, semantics as POSIX seek() */ + int64_t (*seek)(DcmError **error, DcmIO *io, int64_t offset, int whence); +}; + +/** + * Create an IO object using a set of IO methods. * - * :param bot: Basic Offset Table + * :param error: Error structure pointer + * :param io: Set of read methods + * :param client: Client data for read methods * - * :return: number of frames + * :return: IO object */ DCM_EXTERN -uint32_t dcm_bot_get_num_frames(const DcmBOT *bot); +DcmIO *dcm_io_create(DcmError **error, + const DcmIOMethods *methods, + void *client); /** - * Get Frame offset in the Basic Offset Table. + * Open a file on disk for IO. * - * :param bot: Basic Offset Table - * :param index: One-based index of Frame in the Pixel Data Element + * :param error: Error structure pointer + * :param filename: Path to the file on disk * - * :return: offset from pixel_data_offset + * :return: IO object */ DCM_EXTERN -ssize_t dcm_bot_get_frame_offset(const DcmBOT *bot, uint32_t index); +DcmIO *dcm_io_create_from_file(DcmError **error, const char *filename); /** - * Print a Basic Offset Table. + * Open an area of memory for IO. + * + * :param error: Error structure pointer + * :param buffer: Pointer to memory area + * :param length: Length of memory area in bytes * - * :param bot: Basic Offset Table + * :return: IO object */ DCM_EXTERN -void dcm_bot_print(const DcmBOT *bot); +DcmIO *dcm_io_create_from_memory(DcmError **error, const char *buffer, + int64_t length); /** - * Destroy a Basic Offset Table. + * Close an IO object. * - * :param bot: Basic Offset Table + * :param io: Pointer to IO object */ DCM_EXTERN -void dcm_bot_destroy(DcmBOT *bot); - +void dcm_io_close(DcmIO *io); /** - * Part 10 File + * Read from an IO object. + * + * Read up to length bytes from the IO object. Returns the number of bytes + * read, or -1 for an error. A return of 0 indicates end of file. + * + * :param error: Pointer to error object + * :param io: Pointer to IO object + * :param buffer: Memory area to read to + * :param length: Size of memory area + * + * :return: Number of bytes read */ +DCM_EXTERN +int64_t dcm_io_read(DcmError **error, DcmIO *io, char *buffer, int64_t length); /** - * A set of IO functions, see dcm_filehandle_create(). + * Seek an IO object. + * + * Set whence to `SEEK_CUR` to seek relative to the current file position, + * `SEEK_END` to seek relative to the end of the file, or `SEEK_SET` to seek + * relative to the start. + * + * Returns the new absolute read position, or -1 for IO error. + * + * :param error: Error structure pointer + * :param io: Pointer to IO object + * :param offset: Seek offset + * :param whence: Seek mode + * + * :return: New read position */ -typedef struct _DcmIO { - /** Open an IO object */ - void *(*open)(DcmError **error, void *client); - /** Close an IO object */ - int (*close)(DcmError **error, void *data); - /** Read from an IO object, semantics as POSIX read() */ - int64_t (*read)(DcmError **error, void *data, char *buffer, int64_t length); - /** Seek an IO object, semantics as POSIX seek() */ - int64_t (*seek)(DcmError **error, void *data, int64_t offset, int whence); -} DcmIO; +DCM_EXTERN +int64_t dcm_io_seek(DcmError **error, DcmIO *io, int64_t offset, int whence); /** - * Create a filehandle that reads using a set of DcmIO functions. + * Create a representation of a DICOM File using an IO object. * - * :param error: Pointer to error object - * :param io: Set of read functions for this DcmFilehandle - * :param client: Client data for read functions + * The File object tracks information like the transfer syntax and the byte + * ordering. + * + * :param error: Error structure pointer + * :param io: IO object to read from * * :return: filehandle */ DCM_EXTERN -DcmFilehandle *dcm_filehandle_create(DcmError **error, - const DcmIO *io, - void *client); +DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io); /** * Open a file on disk as a DcmFilehandle. * - * :param error: Pointer to error object + * :param error: Error structure pointer * :param filepath: Path to the file on disk * * :return: filehandle @@ -1464,10 +1619,11 @@ DCM_EXTERN DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, const char *filepath); + /** * Open an area of memory as a DcmFilehandle. * - * :param error: Pointer to error object + * :param error: Error structure pointer * :param buffer: Pointer to memory area * :param length: Length of memory area in bytes * @@ -1479,11 +1635,27 @@ DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, int64_t length); /** - * Read File Meta Information from a File. + * Destroy a Filehandle. + * + * :param filehandle: File + */ +DCM_EXTERN +void dcm_filehandle_destroy(DcmFilehandle *filehandle); + +/** + * Get File Meta Information from a File. + * + * Reads the File Meta Information and saves it in the File handle. Returns a + * reference to this internal copy of the File Meta Information. * - * Keeps track of the offset of the Data Set relative to the beginning of the - * filehandle to speed up subsequent access, and determines the Transfer - * Syntax in which the contained Data Set is encoded. + * The return result must not be destroyed. Make a clone of it with + * :c:func:`dcm_dataset_clone()` if you need it to remain valid after + * closing the File handle. + * + * After calling this function, the filehandle read point is always + * positioned at the start of the File metadata. + * + * It is safe to call this function many times. * * :param error: Pointer to error object * :param filehandle: Pointer to file handle @@ -1491,79 +1663,143 @@ DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, * :return: File Meta Information */ DCM_EXTERN -DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, - DcmFilehandle *filehandle); +const DcmDataSet *dcm_filehandle_get_file_meta(DcmError **error, + DcmFilehandle *filehandle); + +/** + * Get Transfer Syntax UID for a fileahndle. + * + * :param filehandle: File + * + * :return: UID of the transfer syntax for this File. + */ +DCM_EXTERN +const char *dcm_filehandle_get_transfer_syntax_uid(const DcmFilehandle *filehandle); /** * Read metadata from a File. * - * Keeps track of the offset of the Pixel Data Element relative to the - * beginning of the filehandle to speed up subsequent access to individual - * Frame items. + * Read slide metadata, stopping when one of the tags in the stop list is + * seen. If the stop list pointer is NULL, it will stop on any of the pixel + * data tags. + * + * The return result must be destroyed with :c:func:`dcm_dataset_destroy()`. + * + * After calling this function, the filehandle read point is always + * positioned at the tag that stopped the read. You can call this function + * again with a different stop set to read more of the metadata. * * :param error: Pointer to error object * :param filehandle: File + * :param stop_tags: NULL, or Zero-terminated array of tags to stop on * * :return: metadata */ DCM_EXTERN DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, - DcmFilehandle *filehandle); + DcmFilehandle *filehandle, + const uint32_t *stop_tags); /** - * Read Basic Offset Table from a File. + * Get a fast subset of metadata from a File. + * + * Gets a subset of the File's metadata and saves it in the File handle. + * Returns a reference to this internal copy of the File metadata. + * + * The subset is the part of the DICOM metadata that can be read quickly. It + * is missing tags such as PerFrameFunctionalGroupSequence. Use + * dcm_filehandle_read_metadata() if you need all file metadata. + * + * The return result must not be destroyed. Make a clone of it with + * :c:func:`dcm_dataset_clone()` if you need it to remain valid after + * closing the File handle. + * + * After calling this function, the filehandle read point is always + * positioned at the tag that stopped the read. * - * In case the Pixel Data Element does not contain a Basic Offset Table item, - * but contains an Extended Offset Table element, the value of the Extended - * Offset Table element will be read instead. + * It is safe to call this function many times. * * :param error: Pointer to error object * :param filehandle: File - * :param metadata: Metadata * - * :return: Basic Offset Table + * :return: metadata */ DCM_EXTERN -DcmBOT *dcm_filehandle_read_bot(DcmError **error, DcmFilehandle *filehandle, - DcmDataSet *metadata); +const DcmDataSet *dcm_filehandle_get_metadata_subset(DcmError **error, + DcmFilehandle *filehandle); /** - * Build Basic Offset Table for a File. + * Read everything necessary to fetch frames from the file. + * + * Scans the PixelData sequence and loads the PerFrameFunctionalGroupSequence, + * if present. + * + * This function will be called automatically on the first call to + * :c:func:`dcm_filehandle_read_frame_position()` or + * :c:func:`dcm_filehandle_read_frame()`. It can take some time to execute, + * so it is available as a separate function call in case this delay needs + * to be managed. + * + * After calling this function, the filehandle read point is always + * positioned at the PixelData tag. + * + * It is safe to call this function many times. * * :param error: Pointer to error object * :param filehandle: File - * :param metadata: Metadata * - * :return: Basic Offset Table + * :return: true on success */ DCM_EXTERN -DcmBOT *dcm_filehandle_build_bot(DcmError **error, DcmFilehandle *filehandle, - DcmDataSet *metadata); +bool dcm_filehandle_prepare_read_frame(DcmError **error, + DcmFilehandle *filehandle); /** * Read an individual Frame from a File. * + * Frames are numbered from 1 in the order they appear in the PixelData element. + * * :param error: Pointer to error object * :param filehandle: File - * :param metadata: Metadata - * :param bot: Basic Offset Table - * :param index: One-based offset of the Frame in the Pixel Data Element + * :param index: One-based frame number * * :return: Frame */ DCM_EXTERN DcmFrame *dcm_filehandle_read_frame(DcmError **error, DcmFilehandle *filehandle, - DcmDataSet *metadata, - DcmBOT *bot, - uint32_t index); + uint32_t frame_number); + +/** + * Read the frame at a position in a File. + * + * Read a frame from a File at a specified (column, row), numbered from zero. + * This takes account of any frame positioning given in + * PerFrameFunctionalGroupSequence. + * + * :param error: Pointer to error object + * :param filehandle: File + * :param column: Column number, from 0 + * :param row: Row number, from 0 + * + * :return: Frame + */ +DCM_EXTERN +DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, + DcmFilehandle *filehandle, + uint32_t column, + uint32_t row); /** - * Destroy a File. + * Scan a file and print the entire structure to stdout. * + * :param error: Pointer to error object * :param filehandle: File + * + * :return: true on successful parse, false otherwise. */ DCM_EXTERN -void dcm_filehandle_destroy(DcmFilehandle *filehandle); +bool dcm_filehandle_print(DcmError **error, + DcmFilehandle *filehandle); #endif diff --git a/meson.build b/meson.build index a38f166..a59f8fe 100644 --- a/meson.build +++ b/meson.build @@ -7,8 +7,9 @@ project( 'warning_level=2', ], license : 'MIT', - meson_version : '>=0.50', - version : '0.3.0' + # "check" needs 0.54.1 + meson_version : '>=0.54.1', + version : '1.0.0' ) if not meson.is_subproject() meson.add_dist_script( @@ -33,8 +34,8 @@ endif # 1. Backward-incompatible ABI change: bump major, reset minor and patch # 2. Backward-compatible ABI change: bump minor, reset patch # 3. Other, eg. bugfix: bump patch -abi_version_major = 0 -abi_version_minor = 2 +abi_version_major = 1 +abi_version_minor = 0 abi_version_patch = 0 abi_version = '@0@.@1@.@2@'.format( @@ -148,11 +149,13 @@ install_headers( library_includes = include_directories('include') library_options = ['-DBUILDING_LIBDICOM'] library_sources = [ + 'src/getopt.c', 'src/dicom.c', 'src/dicom-io.c', 'src/dicom-data.c', 'src/dicom-dict.c', 'src/dicom-file.c', + 'src/dicom-parse.c', ] libdicom = library( 'dicom', @@ -188,12 +191,14 @@ executable( 'tools/dcm-dump.c', dependencies : [libdicom_dep], install : true, + install_tag : 'bin', ) executable( 'dcm-getframe', 'tools/dcm-getframe.c', dependencies : [libdicom_dep], install : true, + install_tag : 'bin', ) dcm_dump_man = configure_file( diff --git a/src/dicom-data.c b/src/dicom-data.c index b6c5ff8..8de4b04 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -111,13 +111,6 @@ struct _DcmFrame { }; -struct _DcmBOT { - uint32_t num_frames; - ssize_t *offsets; - ssize_t first_frame_offset; -}; - - struct SequenceItem { DcmDataSet *dataset; }; @@ -179,7 +172,7 @@ DcmElement *dcm_element_create(DcmError **error, uint32_t tag, DcmVR vr) if (!dcm_is_valid_vr_for_tag(vr, tag)) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Incorrect tag", - "Tag %08X does not allow VR %s", + "Tag %08x does not allow VR %s", tag, dcm_dict_str_from_vr(vr)); return NULL; @@ -199,7 +192,7 @@ DcmElement *dcm_element_create(DcmError **error, uint32_t tag, DcmVR vr) void dcm_element_destroy(DcmElement *element) { if (element) { - dcm_log_debug("Destroy Data Element '%08X'.", element->tag); + dcm_log_debug("Destroy Data Element '%08x'.", element->tag); if(element->sequence_pointer) { dcm_sequence_destroy(element->sequence_pointer); } @@ -264,7 +257,7 @@ static bool element_check_index(DcmError **error, if (index >= element->vm) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element index out of range", - "Element tag %08X has VM of %d, index %d is out of range", + "Element tag %08x has VM of %d, index %d is out of range", element->tag, element->vm, index); @@ -278,11 +271,12 @@ static bool element_check_index(DcmError **error, static bool element_check_string(DcmError **error, const DcmElement *element) { - DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass != DCM_CLASS_STRING_MULTI && klass != DCM_CLASS_STRING_SINGLE) { + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + if (vr_class != DCM_VR_CLASS_STRING_MULTI && + vr_class != DCM_VR_CLASS_STRING_SINGLE) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not string", - "Element tag %08X has VR %s with no string value", + "Element tag %08x has VR %s with no string value", element->tag, dcm_dict_str_from_vr(element->vr)); return false; @@ -298,7 +292,7 @@ static bool element_check_assigned(DcmError **error, if (!element->assigned) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element not assigned a value", - "Element tag %08X has not been assigned a value", + "Element tag %08x has not been assigned a value", element->tag); return false; } @@ -313,7 +307,7 @@ static bool element_check_not_assigned(DcmError **error, if (element->assigned) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element assigned twice", - "Element tag %08X has been previously assigned a value", + "Element tag %08x has been previously assigned a value", element->tag); return false; } @@ -363,14 +357,11 @@ static bool element_check_capacity(DcmError **error, size_t length = strlen(value); if (length > capacity) { element->assigned = was_assigned; - dcm_error_set(error, DCM_ERROR_CODE_INVALID, - "Data Element capacity check failed", - "Value of Data Element '%08X' exceeds " - "maximum length of Value Representation", - "(%d)", - element->tag, - capacity); - return false; + dcm_log_warning("Data Element capacity check failed -- " + "Value of Data Element '%08x' exceeds " + "maximum length of Value Representation (%d)", + element->tag, + capacity); } } @@ -382,7 +373,7 @@ static bool element_check_capacity(DcmError **error, static bool dcm_element_validate(DcmError **error, DcmElement *element) { - DcmVRClass klass = dcm_dict_vr_class(element->vr); + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); if (!element_check_not_assigned(error, element)) { return false; @@ -391,23 +382,25 @@ static bool dcm_element_validate(DcmError **error, DcmElement *element) if (!dcm_is_valid_vr_for_tag(element->vr, element->tag)) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element validation failed", - "Bad VR for tag %08X, should be %s", + "Bad VR for tag %08x, should be %s", element->tag, dcm_dict_str_from_vr(element->vr)); return false; } - if (klass == DCM_CLASS_NUMERIC) { + if (vr_class == DCM_VR_CLASS_NUMERIC_DECIMAL || + vr_class == DCM_VR_CLASS_NUMERIC_INTEGER) { if (element->length != element->vm * dcm_dict_vr_size(element->vr)) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element validation failed", - "Bad length for numeric tag %08X", + "Bad length for numeric tag %08x", element->tag); return false; } } - if (klass == DCM_CLASS_STRING_MULTI || klass == DCM_CLASS_STRING_SINGLE) { + if (vr_class == DCM_VR_CLASS_STRING_MULTI || + vr_class == DCM_VR_CLASS_STRING_SINGLE) { uint32_t capacity = dcm_dict_vr_capacity(element->vr); if (!element_check_capacity(error, element, capacity)) { return false; @@ -454,11 +447,11 @@ bool dcm_element_set_value_string_multi(DcmError **error, element->value_pointer = value_copy; } } else { - DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass != DCM_CLASS_STRING_MULTI) { + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + if (vr_class != DCM_VR_CLASS_STRING_MULTI) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not multi-valued string", - "Element tag %08X has VR %s with only a string value", + "Element tag %08x has VR %s with only a string value", element->tag, dcm_dict_str_from_vr(element->vr)); return false; @@ -507,6 +500,45 @@ bool dcm_element_set_value_string_multi(DcmError **error, } +static char **dcm_parse_character_string(DcmError **error, + char *string, uint32_t *vm) +{ + int n_segments = 1; + for (int i = 0; string[i]; i++) { + if (string[i] == '\\') { + n_segments += 1; + } + } + + char **parts = DCM_NEW_ARRAY(error, n_segments, char *); + if (parts == NULL) { + return NULL; + } + + char *p = string; + for (int segment = 0; segment < n_segments; segment++) { + int i; + for (i = 0; p[i] && p[i] != '\\'; i++) + ; + + parts[segment] = DCM_MALLOC(error, i + 1); + if (parts[segment] == NULL) { + dcm_free_string_array(parts, n_segments); + return NULL; + } + + strncpy(parts[segment], p, i); + parts[segment][i] = '\0'; + + p += i + 1; + } + + *vm = n_segments; + + return parts; +} + + bool dcm_element_set_value_string(DcmError **error, DcmElement *element, char *value, @@ -517,8 +549,8 @@ bool dcm_element_set_value_string(DcmError **error, return false; } - DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass == DCM_CLASS_STRING_MULTI) { + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + if (vr_class == DCM_VR_CLASS_STRING_MULTI) { uint32_t vm; char **values = dcm_parse_character_string(error, value, &vm); if (values == NULL) { @@ -596,11 +628,12 @@ static void value_to_value(DcmVR vr, int *result, int *value) static bool element_check_numeric(DcmError **error, const DcmElement *element) { - DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass != DCM_CLASS_NUMERIC) { + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + if (vr_class != DCM_VR_CLASS_NUMERIC_DECIMAL && + vr_class != DCM_VR_CLASS_NUMERIC_INTEGER) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not numeric", - "Element tag %08X is not numeric", + "Element tag %08x is not numeric", element->tag); return false; } @@ -612,10 +645,13 @@ static bool element_check_numeric(DcmError **error, static bool element_check_integer(DcmError **error, const DcmElement *element) { + if (!element_check_numeric(error, element)) { + return false; + } if (element->vr == DCM_VR_FL || element->vr == DCM_VR_FD) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not integer", - "Element tag %08X is not integer", + "Element tag %08x is not integer", element->tag); return false; } @@ -630,7 +666,6 @@ bool dcm_element_get_value_integer(DcmError **error, int64_t *value) { if (!element_check_assigned(error, element) || - !element_check_numeric(error, element) || !element_check_integer(error, element) || !element_check_index(error, element, index)) { return false; @@ -655,7 +690,6 @@ bool dcm_element_set_value_integer(DcmError **error, int64_t value) { if (!element_check_not_assigned(error, element) || - !element_check_numeric(error, element) || !element_check_integer(error, element)) { return false; } @@ -752,7 +786,7 @@ static bool element_check_float(DcmError **error, if (element->vr != DCM_VR_FL && element->vr != DCM_VR_FD) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not float", - "Element tag %08X is not one of the float types", + "Element tag %08x is not one of the float types", element->tag); return false; } @@ -761,10 +795,10 @@ static bool element_check_float(DcmError **error, } -bool dcm_element_get_value_floatingpoint(DcmError **error, - const DcmElement *element, - uint32_t index, - double *value) +bool dcm_element_get_value_decimal(DcmError **error, + const DcmElement *element, + uint32_t index, + double *value) { if (!element_check_assigned(error, element) || !element_check_numeric(error, element) || @@ -787,9 +821,9 @@ bool dcm_element_get_value_floatingpoint(DcmError **error, } -bool dcm_element_set_value_floatingpoint(DcmError **error, - DcmElement *element, - double value) +bool dcm_element_set_value_decimal(DcmError **error, + DcmElement *element, + double value) { if (!element_check_not_assigned(error, element) || !element_check_numeric(error, element) || @@ -815,11 +849,11 @@ bool dcm_element_set_value_floatingpoint(DcmError **error, static bool element_check_binary(DcmError **error, const DcmElement *element) { - DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass != DCM_CLASS_BINARY) { + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + if (vr_class != DCM_VR_CLASS_BINARY) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not binary", - "Element tag %08X does not have a binary value", + "Element tag %08x does not have a binary value", element->tag); return false; } @@ -830,7 +864,7 @@ static bool element_check_binary(DcmError **error, bool dcm_element_get_value_binary(DcmError **error, const DcmElement *element, - const char **value) + const void **value) { if (!element_check_assigned(error, element) || !element_check_binary(error, element)) { @@ -845,7 +879,7 @@ bool dcm_element_get_value_binary(DcmError **error, bool dcm_element_set_value_binary(DcmError **error, DcmElement *element, - char *value, + void *value, uint32_t length, bool steal) { @@ -857,7 +891,7 @@ bool dcm_element_set_value_binary(DcmError **error, if (steal) { element->value.single.bytes = value; } else { - char *value_copy = DCM_NEW_ARRAY(error, length, char); + void *value_copy = DCM_NEW_ARRAY(error, length, char); if (value_copy == NULL) { return false; } @@ -883,16 +917,76 @@ bool dcm_element_set_value_binary(DcmError **error, } +/* Set a value from a generic byte buffer. The byte buffer must have been + * correctly formatted. + */ +bool dcm_element_set_value(DcmError **error, + DcmElement *element, + char *value, + uint32_t length, + bool steal) +{ + size_t size; + + switch (dcm_dict_vr_class(element->vr)) { + case DCM_VR_CLASS_STRING_SINGLE: + case DCM_VR_CLASS_STRING_MULTI: + if (!dcm_element_set_value_string(error, element, value, steal)) { + return false; + } + break; + + case DCM_VR_CLASS_NUMERIC_DECIMAL: + case DCM_VR_CLASS_NUMERIC_INTEGER: + size = dcm_dict_vr_size(element->vr); + if (length % size != 0) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element failed", + "Bad byte length for numeric type"); + return false; + } + if (!dcm_element_set_value_numeric_multi(error, + element, + (int *) value, + length / size, + steal)) { + return false; + } + break; + + case DCM_VR_CLASS_BINARY: + if (!dcm_element_set_value_binary(error, + element, + value, + length, + steal)) { + return false; + } + + break; + + case DCM_VR_CLASS_SEQUENCE: + default: + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element failed", + "Data Element '%08x' has unexpected " + "Value Representation", element->tag); + return false; + } + + return true; +} + // Sequence Data Element static bool element_check_sequence(DcmError **error, const DcmElement *element) { - DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass != DCM_CLASS_SEQUENCE) { + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + if (vr_class != DCM_VR_CLASS_SEQUENCE) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not seeuence", - "Element tag %08X does not have a seeuence value", + "Element tag %08x does not have a seeuence value", element->tag); return false; } @@ -959,7 +1053,7 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) uint32_t i; DcmSequence *from_seq; - dcm_log_debug("Clone Data Element '%08X'.", element->tag); + dcm_log_debug("Clone Data Element '%08x'.", element->tag); DcmElement *clone = dcm_element_create(error, element->tag, element->vr); if (clone == NULL) { @@ -967,9 +1061,9 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) } clone->length = element->length; - DcmVRClass klass = dcm_dict_vr_class(element->vr); - switch (klass) { - case DCM_CLASS_SEQUENCE: + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + switch (vr_class) { + case DCM_VR_CLASS_SEQUENCE: if (!dcm_element_get_value_sequence(error, element, &from_seq)) { dcm_element_destroy(clone); return NULL; @@ -1005,8 +1099,8 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) break; - case DCM_CLASS_STRING_MULTI: - case DCM_CLASS_STRING_SINGLE: + case DCM_VR_CLASS_STRING_MULTI: + case DCM_VR_CLASS_STRING_SINGLE: // all the string types if (element->vm == 1 && element->value.single.str) { clone->value.single.str = dcm_strdup(error, @@ -1040,7 +1134,7 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) break; - case DCM_CLASS_BINARY: + case DCM_VR_CLASS_BINARY: if (element->value.single.bytes) { clone->value.single.bytes = DCM_MALLOC(error, element->length); if (clone->value.single.bytes == NULL) { @@ -1055,7 +1149,8 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) } break; - case DCM_CLASS_NUMERIC: + case DCM_VR_CLASS_NUMERIC_DECIMAL: + case DCM_VR_CLASS_NUMERIC_INTEGER: if (element->vm == 1) { clone->value = element->value; clone->vm = 1; @@ -1091,40 +1186,119 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) // printing elements -static void element_print_integer(const DcmElement *element, - uint32_t index) -{ - int64_t value; - (void) dcm_element_get_value_integer(NULL, element, index, &value); - if (element->vr == DCM_VR_UV) { - printf("%" PRIu64, (uint64_t)value); - } else { - printf("%" PRId64, value); +char *dcm_element_value_to_string(const DcmElement *element) +{ + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + size_t size = dcm_dict_vr_size(element->vr); + + char *result = NULL; + + double d; + int64_t i; + const char *str; + const void *val; + uint32_t n; + + if (element->vm > 1) { + result = dcm_printf_append(result, "["); + } + + for (uint32_t index = 0; index < element->vm; index++) { + switch (vr_class) { + case DCM_VR_CLASS_NUMERIC_DECIMAL: + (void) dcm_element_get_value_decimal(NULL, + element, + index, + &d); + result = dcm_printf_append(result, "%g", d); + break; + + case DCM_VR_CLASS_NUMERIC_INTEGER: + (void) dcm_element_get_value_integer(NULL, + element, + index, + &i); + + if (element->vr == DCM_VR_UV) { + result = dcm_printf_append(result, + "%"PRIu64, + (uint64_t)i); + } else if (element->vr == DCM_VR_AT) { + // a ushort with half of a tag + result = dcm_printf_append(result, + "%04x", + i); + } else { + result = dcm_printf_append(result, "%"PRId64, i); + } + break; + + case DCM_VR_CLASS_STRING_SINGLE: + case DCM_VR_CLASS_STRING_MULTI: + (void) dcm_element_get_value_string(NULL, + element, + index, + &str); + result = dcm_printf_append(result, "%s", str); + break; + + case DCM_VR_CLASS_BINARY: + (void) dcm_element_get_value_binary(NULL, element, &val); + n = MIN(16, dcm_element_get_length(element)); + + for (i = 0; i < n; i++) { + result = dcm_printf_append(result, "%02x", + ((unsigned char *) val)[i]); + if (i % size == size - 1) { + result = dcm_printf_append(result, " "); + } + } + + if (dcm_element_get_length(element) > 16) { + result = dcm_printf_append(result, "..."); + } + break; + + case DCM_VR_CLASS_SEQUENCE: + result = dcm_printf_append(result, ""); + break; + + default: + dcm_log_warning("Unexpected Value Representation."); + } + + if (element->vm > 1) { + if (index == element->vm - 1) { + result = dcm_printf_append(result, "]"); + } else { + result = dcm_printf_append(result, ", "); + } + } } -} + // AT is a two-element ushort array holding a DICOM tag ... print the tag + // name if we can + if (element->vr == DCM_VR_AT && element->vm == 2) { + int64_t grp; + int64_t ele; + (void) dcm_element_get_value_integer(NULL, element, 0, &grp); + (void) dcm_element_get_value_integer(NULL, element, 1, &ele); -static void element_print_float(const DcmElement *element, - uint32_t index) -{ - double value; - (void) dcm_element_get_value_floatingpoint(NULL, element, index, &value); - printf("%g", value); -} + uint32_t tag = grp << 16 | ele; + const char *keyword = dcm_dict_keyword_from_tag(tag); -static void element_print_string(const DcmElement *element, - uint32_t index) -{ - const char *value; - (void) dcm_element_get_value_string(NULL, element, index, &value); - printf("%s", value); + if (keyword) { + result = dcm_printf_append(result, " (%s)", keyword); + } + } + + return result; } void dcm_element_print(const DcmElement *element, int indentation) { - DcmVRClass klass = dcm_dict_vr_class(element->vr); const int num_indent = indentation * 2; const int num_indent_next = (indentation + 1) * 2; @@ -1132,7 +1306,7 @@ void dcm_element_print(const DcmElement *element, int indentation) if (dcm_is_public_tag(element->tag)) { const char *keyword = dcm_dict_keyword_from_tag(element->tag); - printf("%*.*s(%04X,%04X) %s | %s", + printf("%*.*s(%04x,%04x) %s | %s", num_indent, num_indent, " ", @@ -1142,8 +1316,8 @@ void dcm_element_print(const DcmElement *element, int indentation) dcm_dict_str_from_vr(element->vr)); } else { // private tag, or unknown public tag - // in any case, we can't display the keyword - printf("%*.*s (%04X,%04X) | %s", + // in any case, we can't display the keyword + printf("%*.*s (%04x,%04x) | %s", num_indent, num_indent, " ", @@ -1175,43 +1349,12 @@ void dcm_element_print(const DcmElement *element, int indentation) num_indent, " "); } else { - printf(" | %u | ", element->length); - - if (element->vm > 1) { - printf("["); - } - for (i = 0; i < element->vm; i++) { - switch (klass) { - case DCM_CLASS_NUMERIC: - if (element->vr == DCM_VR_FL || element->vr == DCM_VR_FD) { - element_print_float(element, i); - } else { - element_print_integer(element, i); - } - break; - - case DCM_CLASS_STRING_SINGLE: - case DCM_CLASS_STRING_MULTI: - element_print_string(element, i); - break; - - case DCM_CLASS_BINARY: - break; - - case DCM_CLASS_SEQUENCE: - default: - dcm_log_warning("Unexpected Value Representation."); - } - - if (element->vm > 1) { - if (i == (element->vm - 1)) { - printf("]"); - } else { - printf(", "); - } - } + printf(" | %u | %u | ", element->length, element->vm); + char *str = dcm_element_value_to_string(element); + if (str != NULL) { + printf("%s\n", str); + free(str); } - printf("\n"); } } @@ -1290,7 +1433,7 @@ bool dcm_dataset_insert(DcmError **error, if (matched_element) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Element already exists", - "Inserting Data Element '%08X' into Data Set failed", + "Inserting Data Element '%08x' into Data Set failed", element->tag); dcm_element_destroy(element); return false; @@ -1305,13 +1448,13 @@ bool dcm_dataset_insert(DcmError **error, DcmElement *dcm_dataset_get(DcmError **error, const DcmDataSet *dataset, uint32_t tag) { - dcm_log_debug("Get Data Element '%08X' from Data Set.", tag); + dcm_log_debug("Get Data Element '%08x' from Data Set.", tag); DcmElement *element = dcm_dataset_contains(dataset, tag); if (element == NULL) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Could not find Data Element", - "Getting Data Element '%08X' from Data Set failed", + "Getting Data Element '%08x' from Data Set failed", tag); } @@ -1322,7 +1465,7 @@ DcmElement *dcm_dataset_get(DcmError **error, DcmElement *dcm_dataset_get_clone(DcmError **error, const DcmDataSet *dataset, uint32_t tag) { - dcm_log_debug("Copy Data Element '%08X' from Data Set.", tag); + dcm_log_debug("Copy Data Element '%08x' from Data Set.", tag); DcmElement *element = dcm_dataset_get(error, dataset, tag); if (element == NULL) { @@ -1351,14 +1494,19 @@ bool dcm_dataset_remove(DcmError **error, DcmDataSet *dataset, uint32_t tag) } -void dcm_dataset_foreach(const DcmDataSet *dataset, - void (*fn)(const DcmElement *element)) +bool dcm_dataset_foreach(const DcmDataSet *dataset, + bool (*fn)(const DcmElement *element, void *client), + void *client) { DcmElement *element; for(element = dataset->elements; element; element = element->hh.next) { - fn(element); + if (!fn(element, client)) { + return false; + } } + + return true; } @@ -1542,17 +1690,43 @@ DcmDataSet *dcm_sequence_get(DcmError **error, } -void dcm_sequence_foreach(const DcmSequence *seq, - void (*fn)(const DcmDataSet *item)) +DcmDataSet *dcm_sequence_steal(DcmError **error, + const DcmSequence *seq, uint32_t index) { - uint32_t i; + if (!sequence_check_index(error, seq, index)) { + return NULL; + } + struct SequenceItem *seq_item = utarray_eltptr(seq->items, index); + DcmDataSet *result = seq_item->dataset; + //dcm_dataset_lock(result); + seq_item->dataset = NULL; + // this will free the SequenceItem + utarray_erase(seq->items, index, 1); + + return result; +} + + +bool dcm_sequence_foreach(const DcmSequence *seq, + bool (*fn)(const DcmDataSet *item, + uint32_t index, + void *client), + void *client) +{ uint32_t length = utarray_len(seq->items); - for (i = 0; i < length; i++) { - struct SequenceItem *seq_item = utarray_eltptr(seq->items, i); - dcm_dataset_lock(seq_item->dataset); - fn(seq_item->dataset); + for (uint32_t index = 0; index < length; index++) { + struct SequenceItem *seq_item = utarray_eltptr(seq->items, index); + DcmDataSet *dataset = seq_item->dataset; + + dcm_dataset_lock(dataset); + + if (!fn(dataset, index, client)) { + return false; + } } + + return true; } @@ -1784,84 +1958,6 @@ void dcm_frame_destroy(DcmFrame *frame) } -// Basic Offset Table - -DcmBOT *dcm_bot_create(DcmError **error, - ssize_t *offsets, uint32_t num_frames, - ssize_t first_frame_offset) -{ - if (num_frames == 0) { - dcm_error_set(error, DCM_ERROR_CODE_INVALID, - "Constructing Basic Offset Table failed", - "Expected offsets of %ld Frame Items", - num_frames); - free(offsets); - return NULL; - } - - if (offsets == NULL) { - dcm_error_set(error, DCM_ERROR_CODE_INVALID, - "Constructing Basic Offset Table failed", - "No offsets were provided"); - return NULL; - } - DcmBOT *bot = DCM_NEW(error, DcmBOT); - if (bot == NULL) { - free(offsets); - return NULL; - } - bot->num_frames = num_frames; - bot->offsets = offsets; - bot->first_frame_offset = first_frame_offset; - return bot; -} - - -void dcm_bot_print(const DcmBOT *bot) -{ - assert(bot); - uint32_t i; - - printf("["); - for(i = 0; i < bot->num_frames; i++) { - printf("%zd", bot->offsets[i] + bot->first_frame_offset); - if (i == (bot->num_frames - 1)) { - printf("]\n"); - } else { - printf(","); - } - } -} - - -uint32_t dcm_bot_get_num_frames(const DcmBOT *bot) -{ - assert(bot); - return bot->num_frames; -} - - -ssize_t dcm_bot_get_frame_offset(const DcmBOT *bot, uint32_t number) -{ - assert(bot); - assert(number > 0 && number < bot->num_frames + 1); - uint32_t index = number - 1; - return bot->offsets[index] + bot->first_frame_offset; -} - - -void dcm_bot_destroy(DcmBOT *bot) -{ - if (bot) { - if (bot->offsets) { - free(bot->offsets); - } - free(bot); - bot = NULL; - } -} - - bool dcm_is_encapsulated_transfer_syntax(const char *transfer_syntax_uid) { return diff --git a/src/dicom-dict.c b/src/dicom-dict.c index c90597f..3da9e28 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -26,7 +26,7 @@ struct _DcmVRTable { DcmVR vr; char *str; - DcmVRClass klass; + DcmVRClass vr_class; size_t size; uint32_t capacity; int header_length; @@ -37,7 +37,7 @@ struct _DcmVRTable_hash_entry { */ DcmVR vr; char *str; - DcmVRClass klass; + DcmVRClass vr_class; size_t size; uint32_t capacity; int header_length; @@ -49,97 +49,63 @@ struct _DcmVRTable_hash_entry { * * sizeof(value) > 0 -- one or more numeric values of the specified size * - * enum name class sizeof(value) capacity header_length + * enum name class size capacity header_length */ static const struct _DcmVRTable vr_table[] = { - {DCM_VR_AE, "AE", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_AE, 2}, - {DCM_VR_AS, "AS", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_AS, 2}, - {DCM_VR_AT, "AT", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_AT, 2}, - {DCM_VR_CS, "CS", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_CS, 2}, - {DCM_VR_DA, "DA", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_DA, 2}, - {DCM_VR_DS, "DS", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_DS, 2}, - {DCM_VR_DT, "DT", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_DT, 2}, + {DCM_VR_AE, "AE", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_AE, 2}, + {DCM_VR_AS, "AS", DCM_VR_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_AS, 2}, + {DCM_VR_AT, "AT", DCM_VR_CLASS_NUMERIC_INTEGER, 2, DCM_CAPACITY_AT, 2}, + {DCM_VR_CS, "CS", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_CS, 2}, + {DCM_VR_DA, "DA", DCM_VR_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_DA, 2}, + {DCM_VR_DS, "DS", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_DS, 2}, + {DCM_VR_DT, "DT", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_DT, 2}, - {DCM_VR_FL, "FL", DCM_CLASS_NUMERIC, - sizeof(float), 0, 2}, - {DCM_VR_FD, "FD", DCM_CLASS_NUMERIC, - sizeof(double), 0, 2}, + {DCM_VR_FL, "FL", DCM_VR_CLASS_NUMERIC_DECIMAL, 4, 0, 2}, + {DCM_VR_FD, "FD", DCM_VR_CLASS_NUMERIC_DECIMAL, 8, 0, 2}, - {DCM_VR_IS, "IS", DCM_CLASS_STRING_SINGLE, - 0, DCM_CAPACITY_IS, 2}, - {DCM_VR_LO, "LO", DCM_CLASS_STRING_SINGLE, - 0, DCM_CAPACITY_LO, 2}, + {DCM_VR_IS, "IS", DCM_VR_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_IS, 2}, + {DCM_VR_LO, "LO", DCM_VR_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_LO, 2}, - {DCM_VR_LT, "LT", DCM_CLASS_STRING_SINGLE, - 0, DCM_CAPACITY_LT, 2}, + {DCM_VR_LT, "LT", DCM_VR_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_LT, 2}, - {DCM_VR_OB, "OB", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_OB, "OB", DCM_VR_CLASS_BINARY, 1, 0, 4}, - {DCM_VR_OD, "OD", DCM_CLASS_BINARY, - 0, 0, 4}, - {DCM_VR_OF, "OF", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_OD, "OD", DCM_VR_CLASS_BINARY, 8, 0, 4}, + {DCM_VR_OF, "OF", DCM_VR_CLASS_BINARY, 4, 0, 4}, - {DCM_VR_OW, "OW", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_OW, "OW", DCM_VR_CLASS_BINARY, 2, 0, 4}, - {DCM_VR_PN, "PN", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_PN, 2}, - {DCM_VR_SH, "SH", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_SH, 2}, + {DCM_VR_PN, "PN", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_PN, 2}, + {DCM_VR_SH, "SH", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_SH, 2}, - {DCM_VR_SL, "SL", DCM_CLASS_NUMERIC, - sizeof(int32_t), 0, 2}, + {DCM_VR_SL, "SL", DCM_VR_CLASS_NUMERIC_INTEGER, 4, 0, 2}, - {DCM_VR_SQ, "SQ", DCM_CLASS_SEQUENCE, - 0, 0, 4}, + {DCM_VR_SQ, "SQ", DCM_VR_CLASS_SEQUENCE, 0, 0, 4}, - {DCM_VR_SS, "SS", DCM_CLASS_NUMERIC, - sizeof(int16_t), 0, 2}, + {DCM_VR_SS, "SS", DCM_VR_CLASS_NUMERIC_INTEGER, 2, 0, 2}, - {DCM_VR_ST, "ST", DCM_CLASS_STRING_SINGLE, - 0, DCM_CAPACITY_ST, 2}, + {DCM_VR_ST, "ST", DCM_VR_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_ST, 2}, - {DCM_VR_TM, "TM", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_TM, 2}, + {DCM_VR_TM, "TM", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_TM, 2}, - {DCM_VR_UI, "UI", DCM_CLASS_STRING_MULTI, - 0, DCM_CAPACITY_UI, 2}, + {DCM_VR_UI, "UI", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_UI, 2}, - {DCM_VR_UL, "UL", DCM_CLASS_NUMERIC, - sizeof(uint32_t), 0, 2}, + {DCM_VR_UL, "UL", DCM_VR_CLASS_NUMERIC_INTEGER, 4, 0, 2}, - {DCM_VR_UN, "UN", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_UN, "UN", DCM_VR_CLASS_BINARY, 0, 0, 4}, - {DCM_VR_US, "US", DCM_CLASS_NUMERIC, - sizeof(uint16_t), 0, 2}, + {DCM_VR_US, "US", DCM_VR_CLASS_NUMERIC_INTEGER, 2, 0, 2}, - {DCM_VR_UT, "UT", DCM_CLASS_STRING_SINGLE, - 0, DCM_CAPACITY_UT, 4}, - {DCM_VR_UR, "UR", DCM_CLASS_STRING_SINGLE, - 0, DCM_CAPACITY_UR, 4}, + {DCM_VR_UT, "UT", DCM_VR_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_UT, 4}, + {DCM_VR_UR, "UR", DCM_VR_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_UR, 4}, - {DCM_VR_UC, "UC", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_UC, "UC", DCM_VR_CLASS_STRING_MULTI, 0, DCM_CAPACITY_UC, 4}, - {DCM_VR_OL, "OL", DCM_CLASS_BINARY, - 0, 0, 4}, - {DCM_VR_OV, "OV", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_OL, "OL", DCM_VR_CLASS_BINARY, 0, 0, 4}, + {DCM_VR_OV, "OV", DCM_VR_CLASS_BINARY, 0, 0, 4}, - {DCM_VR_SV, "SV", DCM_CLASS_NUMERIC, - sizeof(int64_t), 0, 4}, - {DCM_VR_UV, "UV", DCM_CLASS_NUMERIC, - sizeof(uint64_t), 0, 4}, + {DCM_VR_SV, "SV", DCM_VR_CLASS_NUMERIC_INTEGER, 8, 0, 4}, + {DCM_VR_UV, "UV", DCM_VR_CLASS_NUMERIC_INTEGER, 8, 0, 4}, }; static const int n_vrs = sizeof(vr_table) / sizeof(struct _DcmVRTable); @@ -5187,6 +5153,10 @@ void dcm_init(void) HASH_ADD_STR(attribute_from_keyword_dict, keyword, entry); } } + + if (getenv("DCM_DEBUG")) { + dcm_log_set_level(DCM_LOG_DEBUG); + } } @@ -5239,10 +5209,10 @@ const char *dcm_dict_str_from_vr(DcmVR vr) DcmVRClass dcm_dict_vr_class(DcmVR vr) { if (vr >= 0 && vr < DCM_VR_LAST) { - return vr_table[(int)vr].klass; + return vr_table[(int)vr].vr_class; } - return DCM_CLASS_ERROR; + return DCM_VR_CLASS_ERROR; } @@ -5343,8 +5313,8 @@ bool dcm_is_valid_vr_for_tag(DcmVR vr, uint32_t tag) const struct _DcmAttribute *attribute = attribute_from_tag(tag); if (attribute == NULL) { - // unknown public tag ... we don't include retired tags in our - // dictionary, so we can't check them, but we don't want to fail + // unknown public tag ... we don't include retired tags in our + // dictionary, so we can't check them, but we don't want to fail // for them either return true; } else if (vr == (DcmVR) attribute->vr_tag) { diff --git a/src/dicom-file.c b/src/dicom-file.c index 1596144..7f313e5 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -23,59 +23,60 @@ #include #include "pdicom.h" - -#define TAG_ITEM 0xFFFEE000 -#define TAG_ITEM_DELIM 0xFFFEE00D -#define TAG_SQ_DELIM 0xFFFEE0DD -#define TAG_TRAILING_PADDING 0xFFFCFFFC -#define TAG_EXTENDED_OFFSET_TABLE 0x7FE00001 -#define TAG_PIXEL_DATA 0x7FE00010 -#define TAG_FLOAT_PIXEL_DATA 0x7FE00008 -#define TAG_DOUBLE_PIXEL_DATA 0x7FE00009 - -struct PixelDescription { - uint16_t rows; - uint16_t columns; - uint16_t samples_per_pixel; - uint16_t bits_allocated; - uint16_t bits_stored; - uint16_t high_bit; - uint16_t pixel_representation; - uint16_t planar_configuration; - const char *photometric_interpretation; -}; - +typedef enum _DcmLayout { + DCM_LAYOUT_SPARSE, + DCM_LAYOUT_FULL, + DCM_LAYOUT_UNKNOWN, +} DcmLayout; struct _DcmFilehandle { - const DcmIO *io; - void *fp; - DcmDataSet *meta; - int64_t offset; + DcmIO *io; char *transfer_syntax_uid; + bool implicit; + const uint32_t *stop_tags; + + // start of image metadata + int64_t offset; + // just after read_metadata + int64_t after_read_metadata; + // start of pixel metadata int64_t pixel_data_offset; - uint64_t *extended_offset_table; - bool byteswap; -}; + // distance from pixel metadata to start of first frame + int64_t first_frame_offset; + // the file metadata and selected DICOM metadata + DcmDataSet *file_meta; + DcmDataSet *meta; -/* TRUE for big-endian machines, like PPC. We need to byteswap DICOM - * numeric types in this case. Run time tests for this are much - * simpler to manage when cross-compiling. - */ -static bool is_big_endian(void) -{ - union { - uint32_t i; - char c[4]; - } bint = {0x01020304}; + // image properties we need to track + uint32_t tiles_across; + uint32_t num_frames; + struct PixelDescription desc; + DcmLayout layout; - return bint.c[0] == 1; -} + // both zero-indexed and length num_frames + uint32_t *frame_index; + int64_t *offset_table; + // the last top level tag the scanner saw + uint32_t last_tag; -DcmFilehandle *dcm_filehandle_create(DcmError **error, - const DcmIO *io, - void *client) + // used to count frames as we scan perframefunctionalgroup + uint32_t frame_number; + + // indent for file print + int indent; + + // dataset index for file print + UT_array *index_stack; + + // push and pop these while we parse + UT_array *dataset_stack; + UT_array *sequence_stack; +}; + + +DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) { DcmFilehandle *filehandle = DCM_NEW(error, DcmFilehandle); if (filehandle == NULL) { @@ -83,34 +84,105 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, } filehandle->io = io; - filehandle->fp = NULL; - filehandle->meta = NULL; filehandle->offset = 0; filehandle->transfer_syntax_uid = NULL; filehandle->pixel_data_offset = 0; - filehandle->extended_offset_table = NULL; - filehandle->byteswap = is_big_endian(); + filehandle->last_tag = 0xffffffff; + filehandle->frame_number = 0; + filehandle->layout = DCM_LAYOUT_FULL; + filehandle->frame_index = NULL; + utarray_new(filehandle->index_stack, &ut_int_icd); + utarray_new(filehandle->dataset_stack, &ut_ptr_icd); + utarray_new(filehandle->sequence_stack, &ut_ptr_icd); + + return filehandle; +} + - filehandle->fp = filehandle->io->open(error, client); - if (filehandle->fp == NULL) { - dcm_filehandle_destroy(filehandle); +DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, + const char *filepath) +{ + DcmIO *io = dcm_io_create_from_file(error, filepath); + if (io == NULL) { return NULL; } - return filehandle; + return dcm_filehandle_create(error, io); +} + + +DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, + const char *buffer, + int64_t length) +{ + DcmIO *io = dcm_io_create_from_memory(error, buffer, length); + if (io == NULL) { + return NULL; + } + + return dcm_filehandle_create(error, io); +} + + +static void dcm_filehandle_clear(DcmFilehandle *filehandle) +{ + unsigned int i; + + utarray_clear(filehandle->index_stack); + + // we always need at least 1 item on the index stack + int index = 0; + utarray_push_back(filehandle->index_stack, &index); + + for (i = 0; i < utarray_len(filehandle->dataset_stack); i++) { + DcmDataSet *dataset = *((DcmDataSet **) + utarray_eltptr(filehandle->dataset_stack, i)); + + dcm_dataset_destroy(dataset); + } + + utarray_clear(filehandle->dataset_stack); + + for (i = 0; i < utarray_len(filehandle->sequence_stack); i++) { + DcmSequence *sequence = *((DcmSequence **) + utarray_eltptr(filehandle->sequence_stack, i)); + + dcm_sequence_destroy(sequence); + } + + utarray_clear(filehandle->sequence_stack); } void dcm_filehandle_destroy(DcmFilehandle *filehandle) { if (filehandle) { + dcm_filehandle_clear(filehandle); + if (filehandle->transfer_syntax_uid) { free(filehandle->transfer_syntax_uid); } - if (filehandle->fp) { - (void)filehandle->io->close(NULL, filehandle->fp); - filehandle->fp = NULL; + if (filehandle->frame_index) { + free(filehandle->frame_index); + } + + if (filehandle->offset_table) { + free(filehandle->offset_table); + } + + dcm_io_close(filehandle->io); + + utarray_free(filehandle->index_stack); + utarray_free(filehandle->dataset_stack); + utarray_free(filehandle->sequence_stack); + + if (filehandle->meta) { + dcm_dataset_destroy(filehandle->meta); + } + + if (filehandle->file_meta) { + dcm_dataset_destroy(filehandle->file_meta); } free(filehandle); @@ -121,8 +193,7 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) static int64_t dcm_read(DcmError **error, DcmFilehandle *filehandle, char *buffer, int64_t length, int64_t *position) { - int64_t bytes_read = filehandle->io->read(error, - filehandle->fp, buffer, length); + int64_t bytes_read = dcm_io_read(error, filehandle->io, buffer, length); if (bytes_read < 0) { return bytes_read; } @@ -160,140 +231,234 @@ static bool dcm_require(DcmError **error, DcmFilehandle *filehandle, static bool dcm_seekset(DcmError **error, DcmFilehandle *filehandle, int64_t offset) { - return filehandle->io->seek(error, filehandle->fp, offset, SEEK_SET) >= 0; + return dcm_io_seek(error, filehandle->io, offset, SEEK_SET) >= 0; } -static bool dcm_seekcur(DcmError **error, DcmFilehandle *filehandle, - int64_t offset, int64_t *position) +static bool dcm_offset(DcmError **error, + DcmFilehandle *filehandle, int64_t *offset) { - int64_t new_offset = filehandle->io->seek(error, - filehandle->fp, offset, SEEK_CUR); + int64_t new_offset = dcm_io_seek(error, filehandle->io, 0, SEEK_CUR); if (new_offset < 0) { return false; } - *position += offset; + *offset = new_offset; return true; } -static bool dcm_offset(DcmError **error, - DcmFilehandle *filehandle, int64_t *offset) +static bool get_tag_int(DcmError **error, + const DcmDataSet *dataset, + const char *keyword, + int64_t *result) +{ + uint32_t tag = dcm_dict_tag_from_keyword(keyword); + DcmElement *element = dcm_dataset_get(error, dataset, tag); + return element && + dcm_element_get_value_integer(error, element, 0, result); +} + + +static bool get_tag_str(DcmError **error, + const DcmDataSet *dataset, + const char *keyword, + const char **result) +{ + uint32_t tag = dcm_dict_tag_from_keyword(keyword); + DcmElement *element = dcm_dataset_get(error, dataset, tag); + return element && + dcm_element_get_value_string(error, element, 0, result); +} + + +static bool get_num_frames(DcmError **error, + const DcmDataSet *metadata, + uint32_t *number_of_frames) { - *offset = filehandle->io->seek(error, filehandle->fp, 0, SEEK_CUR); - if (*offset < 0) { + const char *value; + if (!get_tag_str(error, metadata, "NumberOfFrames", &value)) { + return false; + } + + uint32_t num_frames = strtol(value, NULL, 10); + if (num_frames == 0) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Basic Offset Table read failed", + "Value of Data Element 'Number of Frames' is malformed"); return false; } + *number_of_frames = num_frames; + return true; } -static bool dcm_is_eof(DcmFilehandle *filehandle) +static bool get_tiles_across(DcmError **error, + const DcmDataSet *metadata, + uint32_t *tiles_across) { - int64_t position = 0; - bool eof = true; + int64_t width; + int64_t tile_width; - char buffer[1]; - int64_t bytes_read = filehandle->io->read(NULL, filehandle->fp, buffer, 1); - if (bytes_read > 0) { - eof = false; - (void) dcm_seekcur(NULL, filehandle, -1, &position); + if (!get_tag_int(error, metadata, "Columns", &tile_width)) { + return false; } - return eof; + // TotalPixelMatrixColumns is optional and defaults to Columns, ie. one + // tile across + width = tile_width; + (void) get_tag_int(NULL, metadata, "TotalPixelMatrixColumns", &width); + + *tiles_across = width / tile_width + !!(width % tile_width); + + return true; } -static void byteswap(char *data, size_t length, size_t size) +static bool parse_meta_dataset_begin(DcmError **error, + void *client) { - assert(length >= size); + DcmFilehandle *filehandle = (DcmFilehandle *) client; - if (size > 1) { - assert(length % size == 0); - assert(size % 2 == 0); + DcmDataSet *dataset = dcm_dataset_create(error); + if (dataset == NULL) { + return false; + } - size_t half_size = size / 2; + utarray_push_back(filehandle->dataset_stack, &dataset); - for (size_t i = 0; i < length; i += size) { - for (size_t j = 0; j < half_size; j++) { - char *p = data + i; - char t = p[j]; - p[j] = p[size - j - 1]; - p[size - j - 1] = t; - } - } - } + return true; } -static bool read_uint16(DcmError **error, DcmFilehandle *filehandle, - uint16_t *value, int64_t *position) +static bool parse_meta_dataset_end(DcmError **error, + void *client) { - union { - uint16_t i; - char c[2]; - } buffer; + DcmFilehandle *filehandle = (DcmFilehandle *) client; - if (!dcm_require(error, filehandle, buffer.c, 2, position)) { + DcmDataSet *dataset = *((DcmDataSet **) + utarray_back(filehandle->dataset_stack)); + DcmSequence *sequence = *((DcmSequence **) + utarray_back(filehandle->sequence_stack)); + + if (!dcm_sequence_append(error, sequence, dataset)) { return false; } - if (filehandle->byteswap) { - byteswap(buffer.c, 2, 2); + utarray_pop_back(filehandle->dataset_stack); + + return true; +} + + +static bool parse_meta_sequence_begin(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + USED(tag); + USED(vr); + USED(length); + + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + DcmSequence *sequence = dcm_sequence_create(error); + if (sequence == NULL) { + return false; } - *value = buffer.i; + utarray_push_back(filehandle->sequence_stack, &sequence); return true; } -static bool read_uint32(DcmError **error, DcmFilehandle *filehandle, - uint32_t *value, int64_t *position) +static bool parse_meta_sequence_end(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) { - union { - uint32_t i; - char c[4]; - } buffer; + USED(length); - if (!dcm_require(error, filehandle, buffer.c, 4, position)) { + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + DcmElement *element = dcm_element_create(error, tag, vr); + if (element == NULL) { return false; } - if (filehandle->byteswap) { - byteswap(buffer.c, 4, 4); + DcmSequence *sequence = *((DcmSequence **) + utarray_back(filehandle->sequence_stack)); + if (!dcm_element_set_value_sequence(error, element, sequence)) { + dcm_element_destroy(element); + return false; } - *value = buffer.i; + utarray_pop_back(filehandle->sequence_stack); + + DcmDataSet *dataset = *((DcmDataSet **) + utarray_back(filehandle->dataset_stack)); + if (!dcm_dataset_insert(error, dataset, element)) { + dcm_element_destroy(element); + return false; + } return true; } -static bool read_tag(DcmError **error, DcmFilehandle *filehandle, - uint32_t *value, int64_t *position) +static bool parse_meta_element_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) { - uint16_t group, elem; + DcmFilehandle *filehandle = (DcmFilehandle *) client; - if (!read_uint16(error, filehandle, &group, position) || - !read_uint16(error, filehandle, &elem, position)) { + DcmElement *element = dcm_element_create(error, tag, vr); + if (element == NULL) { return false; } - *value = ((uint32_t)group << 16) | elem; + DcmDataSet *dataset = *((DcmDataSet **) + utarray_back(filehandle->dataset_stack)); + if (!dcm_element_set_value(error, element, value, length, false) || + !dcm_dataset_insert(error, dataset, element)) { + dcm_element_destroy(element); + return false; + } return true; } -static bool read_iheader(DcmError **error, DcmFilehandle *filehandle, - uint32_t *item_tag, uint32_t *item_length, int64_t *position) +static bool parse_preamble(DcmError **error, + DcmFilehandle *filehandle, + int64_t *position) { - if (!read_tag(error, filehandle, item_tag, position) || - !read_uint32(error, filehandle, item_length, position)) { + *position = 128; + if (!dcm_seekset(error, filehandle, *position)) { + return false; + } + + // DICOM Prefix + char prefix[5]; + if (!dcm_require(error, + filehandle, prefix, sizeof(prefix) - 1, position)) { + return false; + } + prefix[4] = '\0'; + + if (strcmp(prefix, "DICM") != 0) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading of File Meta Information failed", + "Prefix 'DICM' not found."); return false; } @@ -301,1074 +466,926 @@ static bool read_iheader(DcmError **error, DcmFilehandle *filehandle, } -char **dcm_parse_character_string(DcmError **error, - char *string, uint32_t *vm) +static DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, + DcmFilehandle *filehandle) { - int n_segments = 1; - for (int i = 0; string[i]; i++) { - if (string[i] == '\\') { - n_segments += 1; - } + static DcmParse parse = { + .dataset_begin = parse_meta_dataset_begin, + .dataset_end = parse_meta_dataset_end, + .sequence_begin = parse_meta_sequence_begin, + .sequence_end = parse_meta_sequence_end, + .element_create = parse_meta_element_create, + .stop = NULL, + }; + + // skip File Preamble + int64_t position = 0; + if (!parse_preamble(error, filehandle, &position)) { + return NULL; } - char **parts = DCM_NEW_ARRAY(error, n_segments, char *); - if (parts == NULL) { + dcm_filehandle_clear(filehandle); + DcmSequence *sequence = dcm_sequence_create(error); + if (sequence == NULL) { return NULL; } + utarray_push_back(filehandle->sequence_stack, &sequence); - char *p = string; - for (int segment = 0; segment < n_segments; segment++) { - int i; - for (i = 0; p[i] && p[i] != '\\'; i++) - ; + // parse all of the first group + if (!dcm_parse_group(error, + filehandle->io, + false, + &parse, + filehandle)) { + return false; + } - parts[segment] = DCM_MALLOC(error, i + 1); - if (parts[segment] == NULL) { - dcm_free_string_array(parts, n_segments); - return NULL; - } + /* Sanity check. We should have parsed a single dataset into the sequence + * we put on the stack. + */ + if (utarray_len(filehandle->dataset_stack) != 0 || + utarray_len(filehandle->sequence_stack) != 1) { + abort(); + } - strncpy(parts[segment], p, i); - parts[segment][i] = '\0'; + // we must read sequence back off the stack since it may have been + // realloced + sequence = *((DcmSequence **) utarray_back(filehandle->sequence_stack)); + if (dcm_sequence_count(sequence) != 1) { + abort(); + } - p += i + 1; + DcmDataSet *file_meta = dcm_sequence_get(error, sequence, 0); + if (file_meta == NULL ) { + return false; } - *vm = n_segments; + // steal file_meta to stop it being destroyed + (void) dcm_sequence_steal(NULL, sequence, 0); + dcm_filehandle_clear(filehandle); - return parts; + return file_meta; } -static DcmElement *read_element_header(DcmError **error, - DcmFilehandle *filehandle, - uint32_t *length, - int64_t *position, - bool implicit) +const DcmDataSet *dcm_filehandle_get_file_meta(DcmError **error, + DcmFilehandle *filehandle) { - uint32_t tag; - if (!read_tag(error, filehandle, &tag, position)) { - return NULL; - } + if (filehandle->file_meta == NULL) { + DcmDataSet *file_meta = + dcm_filehandle_read_file_meta(error, filehandle); + if (file_meta == NULL) { + return NULL; + } - DcmVR vr; - if (implicit) { - // this can be an ambiguious VR, eg. pixeldata is allowed in implicit - // mode and has to be disambiguated later from other tags - vr = dcm_vr_from_tag(tag); - if (vr == DCM_VR_ERROR) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading of Data Element header failed", - "Tag %08X not allowed in implicit mode", tag); + // record the start point for the image metadata + if (!dcm_offset(error, filehandle, &filehandle->offset)) { + dcm_dataset_destroy(file_meta); + return NULL; } - if (!read_uint32(error, filehandle, length, position)) { + DcmElement *element = dcm_dataset_get(error, file_meta, 0x00020010); + if (element == NULL) { + dcm_dataset_destroy(file_meta); return NULL; } - } else { - // Value Representation - char vr_str[3]; - if (!dcm_require(error, filehandle, vr_str, 2, position)) { + + const char *transfer_syntax_uid; + if (!dcm_element_get_value_string(error, + element, + 0, + &transfer_syntax_uid)) { + dcm_dataset_destroy(file_meta); return NULL; } - vr_str[2] = '\0'; - vr = dcm_dict_vr_from_str(vr_str); - if (!dcm_is_valid_vr_for_tag(vr, tag)) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading of Data Element header failed", - "Tag %08X cannot have VR '%s'", tag, vr_str); + filehandle->transfer_syntax_uid = + dcm_strdup(error, transfer_syntax_uid); + if (filehandle->transfer_syntax_uid == NULL) { + dcm_dataset_destroy(file_meta); return NULL; } - if (dcm_dict_vr_header_length(vr) == 2) { - // These VRs have a short length of only two bytes - uint16_t short_length; - if (!read_uint16(error, filehandle, &short_length, position)) { - return NULL; - } - *length = (uint32_t) short_length; - } else { - // Other VRs have two reserved bytes before length of four bytes - uint16_t reserved; - if (!read_uint16(error, filehandle, &reserved, position) || - !read_uint32(error, filehandle, length, position)) { - return NULL; - } + if (strcmp(filehandle->transfer_syntax_uid, "1.2.840.10008.1.2") == 0) { + filehandle->implicit = true; + } - if (reserved != 0x0000) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading of Data Element header failed", - "Unexpected value for reserved bytes " - "of Data Element %08X with VR '%s'.", - tag, vr); - return NULL; - } + filehandle->desc.transfer_syntax_uid = filehandle->transfer_syntax_uid; + + filehandle->file_meta = file_meta; + } else { + // move the read point to the start of the slide metadata + if (!dcm_seekset(error, filehandle, filehandle->offset)) { + return NULL; } } - return dcm_element_create(error, tag, vr); + return filehandle->file_meta; } -// fwd ref this -static DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit); +const char *dcm_filehandle_get_transfer_syntax_uid(const DcmFilehandle *filehandle) +{ + return filehandle->transfer_syntax_uid; +} -static bool read_element_sequence(DcmError **error, - DcmFilehandle *filehandle, - DcmSequence *sequence, - uint32_t length, - int64_t *position, - bool implicit) + +static bool parse_meta_stop(void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) { - int index = 0; - int64_t seq_position = 0; - while (seq_position < length) { - dcm_log_debug("Read Item #%d.", index); - uint32_t item_tag; - uint32_t item_length; - if (!read_iheader(error, filehandle, - &item_tag, &item_length, &seq_position)) { - return false; - } + DcmFilehandle *filehandle = (DcmFilehandle *) client; - if (item_tag == TAG_SQ_DELIM) { - dcm_log_debug("Stop reading Data Element. " - "Encountered Sequence Delimination Tag."); - break; - } + USED(vr); + USED(length); - if (item_tag != TAG_ITEM) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading of Data Element failed", - "Expected tag '%08X' instead of '%08X' " - "for Item #%d", - TAG_ITEM, - item_tag, - index); - return false; - } + filehandle->last_tag = tag; - if (length == 0xFFFFFFFF) { - dcm_log_debug("Item #%d has undefined length.", index); - } else { - dcm_log_debug("Item #%d has defined length %d.", - index, item_length); + for (int i = 0; filehandle->stop_tags[i]; i++) { + if (tag == filehandle->stop_tags[i]) { + return true; } + } - DcmDataSet *dataset = dcm_dataset_create(error); - if (dataset == NULL) { - return false; - } + return false; +} - int64_t item_position = 0; - while (item_position < item_length) { - // peek the next tag - if (!read_tag(error, filehandle, &item_tag, &item_position)) { - dcm_dataset_destroy(dataset); - return false; - } - if (item_tag == TAG_ITEM_DELIM) { - // step over the tag length - dcm_log_debug("Stop reading Item #%d. " - "Encountered Item Delimination Tag.", - index); - if (!dcm_seekcur(error, filehandle, 4, &item_position)) { - dcm_dataset_destroy(dataset); - return false; - } - - break; - } +static bool set_pixel_description(DcmError **error, + const DcmDataSet *metadata, + struct PixelDescription *desc) +{ + DcmElement *element; + int64_t value; + const char *string; - // back to start of element - if (!dcm_seekcur(error, filehandle, -4, &item_position)) { - dcm_dataset_destroy(dataset); - return false; - } + element = dcm_dataset_get(error, metadata, 0x00280010); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->rows = value; - DcmElement *element = read_element(error, filehandle, - &item_position, implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return false; - } + element = dcm_dataset_get(error, metadata, 0x00280011); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->columns = value; - if (!dcm_dataset_insert(error, dataset, element)) { - dcm_dataset_destroy(dataset); - dcm_element_destroy(element); - return false; - } - } + element = dcm_dataset_get(error, metadata, 0x00280002); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->samples_per_pixel = value; - seq_position += item_position; + element = dcm_dataset_get(error, metadata, 0x00280100); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->bits_allocated = value; - if (!dcm_sequence_append(error, sequence, dataset)) { - dcm_dataset_destroy(dataset); - } + element = dcm_dataset_get(error, metadata, 0x00280101); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->bits_stored = value; + + element = dcm_dataset_get(error, metadata, 0x00280103); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->pixel_representation = value; - index += 1; + element = dcm_dataset_get(error, metadata, 0x00280006); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; } + desc->planar_configuration = value; - *position += seq_position; + element = dcm_dataset_get(error, metadata, 0x00280004); + if (element == NULL || + !dcm_element_get_value_string(error, element, 0, &string)) { + return false; + } + desc->photometric_interpretation = string; return true; } -static bool read_element_body(DcmError **error, - DcmElement *element, - DcmFilehandle *filehandle, - uint32_t length, - int64_t *position, - bool implicit) +DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, + DcmFilehandle *filehandle, + const uint32_t *stop_tags) { - uint32_t tag = dcm_element_get_tag(element); - DcmVR vr = dcm_element_get_vr(element); - DcmVRClass klass = dcm_dict_vr_class(vr); - size_t size = dcm_dict_vr_size(vr); - char *value; + // by default, we don't stop anywhere (except pixeldata) + static const uint32_t default_stop_tags[] = { + TAG_PIXEL_DATA, + TAG_FLOAT_PIXEL_DATA, + TAG_DOUBLE_PIXEL_DATA, + 0, + }; + + static DcmParse parse = { + // we don't need to define the pixeldata callbacks since we have no + // concrete representation for them + .dataset_begin = parse_meta_dataset_begin, + .dataset_end = parse_meta_dataset_end, + .sequence_begin = parse_meta_sequence_begin, + .sequence_end = parse_meta_sequence_end, + .element_create = parse_meta_element_create, + .stop = parse_meta_stop, + }; + + // only get the file_meta if it's not there ... we don't want to rewind + // filehandle every time + if (filehandle->file_meta == NULL) { + const DcmDataSet *file_meta = dcm_filehandle_get_file_meta(error, + filehandle); + if (file_meta == NULL) { + return NULL; + } + } - dcm_log_debug("Read Data Element body '%08X'", tag); + dcm_filehandle_clear(filehandle); + filehandle->stop_tags = stop_tags == NULL ? default_stop_tags : stop_tags; + DcmSequence *sequence = dcm_sequence_create(error); + if (sequence == NULL) { + return NULL; + } + utarray_push_back(filehandle->sequence_stack, &sequence); - switch (klass) { - case DCM_CLASS_STRING_SINGLE: - case DCM_CLASS_STRING_MULTI: - value = DCM_MALLOC(error, length + 1); - if (value == NULL) { - return false; - } - - if (!dcm_require(error, filehandle, value, length, position)) { - free(value); - return false; - } - value[length] = '\0'; - - if (length > 0) { - if (vr != DCM_VR_UI) { - if (isspace(value[length - 1])) { - value[length - 1] = '\0'; - } - } - } - - if (!dcm_element_set_value_string(error, element, value, true)) { - free(value); - return false; - } - - break; + if (!dcm_parse_dataset(error, + filehandle->io, + filehandle->implicit, + &parse, + filehandle)) { + return false; + } - case DCM_CLASS_NUMERIC: - if (length % size != 0) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading of Data Element failed", - "Bad length for tag '%08X'", - tag); - return false; - } - uint32_t vm = length / size; + /* Sanity check. We should have parsed a single dataset into the sequence + * we put on the stack. + */ + if (utarray_len(filehandle->dataset_stack) != 0 || + utarray_len(filehandle->sequence_stack) != 1) { + abort(); + } - char *values = DCM_MALLOC(error, length); - if (values == NULL) { - return false; - } + // we must read sequence back off the stack since it may have been + // realloced + sequence = *((DcmSequence **) utarray_back(filehandle->sequence_stack)); + if (dcm_sequence_count(sequence) != 1) { + abort(); + } - if (!dcm_require(error, filehandle, values, length, position)) { - free(values); - return false; - } + DcmDataSet *meta = dcm_sequence_get(error, sequence, 0); + if (meta == NULL ) { + return false; + } - if (filehandle->byteswap) { - byteswap(values, length, size); - } + // steal meta to stop it being destroyed + (void) dcm_sequence_steal(NULL, sequence, 0); + dcm_filehandle_clear(filehandle); - if( !dcm_element_set_value_numeric_multi(error, - element, - (int *) values, - vm, - true)) { - free(values); - return false; - } + return meta; +} - break; - case DCM_CLASS_BINARY: - value = DCM_MALLOC(error, length); - if (value == NULL) { - return false; - } +const DcmDataSet *dcm_filehandle_get_metadata_subset(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, + }; + + if (filehandle->meta == NULL) { + // always rewind the filehandle + const DcmDataSet *file_meta = dcm_filehandle_get_file_meta(error, + filehandle); + if (file_meta == NULL) { + return NULL; + } - if (!dcm_require(error, filehandle, value, length, position)) { - free(value); - return false; - } + DcmDataSet *meta = dcm_filehandle_read_metadata(error, + filehandle, + stop_tags); - if( !dcm_element_set_value_binary(error, element, - value, length, true)) { - free(value); - return false; - } + // record the position of the tag that stopped the read + if (!dcm_offset(error, filehandle, &filehandle->after_read_metadata)) { + dcm_dataset_destroy(meta); + return NULL; + } - break; + // useful values for later + if (!get_tiles_across(error, meta, &filehandle->tiles_across) || + !get_num_frames(error, meta, &filehandle->num_frames) || + !set_pixel_description(error, meta, &filehandle->desc)) { + dcm_dataset_destroy(meta); + return false; + } - case DCM_CLASS_SEQUENCE: - if (length == 0xFFFFFFFF) { - dcm_log_debug("Sequence of Data Element '%08X' " - "has undefined length.", - tag); + // we support sparse and full tile layout, defaulting to full if no type + // is specified + const char *type; + if (get_tag_str(NULL, meta, "DimensionOrganizationType", &type)) { + if (strcmp(type, "TILED_SPARSE") == 0 || strcmp(type, "3D") == 0) { + filehandle->layout = DCM_LAYOUT_SPARSE; + } else if (strcmp(type, "TILED_FULL") == 0) { + filehandle->layout = DCM_LAYOUT_FULL; } else { - dcm_log_debug("Sequence of Data Element '%08X' " - "has defined length %d.", - tag, length); - } - - DcmSequence *seq = dcm_sequence_create(error); - if (seq == NULL) { - return NULL; + filehandle->layout = DCM_LAYOUT_UNKNOWN; } + } - int64_t seq_position = 0; - if (!read_element_sequence(error, - filehandle, seq, length, - &seq_position, implicit)) { - dcm_sequence_destroy(seq); - return false; - } - *position += seq_position; - - if (!dcm_element_set_value_sequence(error, element, seq)) { - return false; - } - - break; - - default: - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading of Data Element failed", - "Data Element '%08X' has unexpected " - "Value Representation", tag); - return false; + filehandle->meta = meta; + } else { + // move the read point to the tag we stopped read on + if (!dcm_seekset(error, filehandle, filehandle->after_read_metadata)) { + return NULL; + } } - return true; + return filehandle->meta; } -DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit) +static bool parse_frame_index_element_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) { - DcmElement *element; + USED(error); - uint32_t length; - element = read_element_header(error, - filehandle, &length, position, implicit); - if (element == NULL) { - return NULL; - } + DcmFilehandle *filehandle = (DcmFilehandle *) client; - if (!read_element_body(error, element, filehandle, - length, position, implicit)) { - dcm_element_destroy(element); - return NULL; + if (vr == DCM_VR_UL && length == 8 && tag == TAG_DIMENSION_INDEX_VALUES) { + // it will have already been byteswapped for us, if necessary + uint32_t *ul = (uint32_t *) value; + uint32_t col = ul[0]; + uint32_t row = ul[1]; + uint32_t index = (col - 1) + (row - 1) * filehandle->tiles_across; + + if (index < filehandle->num_frames) { + filehandle->frame_index[index] = filehandle->frame_number; + filehandle->frame_number += 1; + } } - return element; + return true; } -DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, - DcmFilehandle *filehandle) +static bool parse_frame_index_stop(void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) { - const bool implicit = false; + DcmFilehandle *filehandle = (DcmFilehandle *) client; - int64_t position; - uint16_t group_number; - DcmElement *element; + USED(vr); + USED(length); - DcmDataSet *file_meta = dcm_dataset_create(error); - if (file_meta == NULL) { - return NULL; - } - - position = 0; + filehandle->last_tag = tag; - // File Preamble - char preamble[129]; - if (!dcm_require(error, - filehandle, preamble, sizeof(preamble) - 1, &position)) { - dcm_dataset_destroy(file_meta); - return NULL; - } - preamble[128] = '\0'; + return tag != TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE; +} - // DICOM Prefix - char prefix[5]; - if (!dcm_require(error, - filehandle, prefix, sizeof(prefix) - 1, &position)) { - dcm_dataset_destroy(file_meta); - return NULL; - } - prefix[4] = '\0'; - if (strcmp(prefix, "DICM") != 0) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading of File Meta Information failed", - "Prefix 'DICM' not found."); - dcm_dataset_destroy(file_meta); - return NULL; +static bool read_frame_index(DcmError **error, + DcmFilehandle *filehandle) +{ + static DcmParse parse = { + .element_create = parse_frame_index_element_create, + .stop = parse_frame_index_stop, + }; + + filehandle->frame_index = DCM_NEW_ARRAY(error, + filehandle->num_frames, + uint32_t); + if (filehandle->frame_index == NULL) { + return false; } - position = 0; - - // File Meta Information Group Length - element = read_element(error, filehandle, &position, implicit); - if (element == NULL) { - dcm_dataset_destroy(file_meta); - return NULL; + // we may not have all frames ... set to missing initially + for (uint32_t i = 0; i < filehandle->num_frames; i++) { + filehandle->frame_index[i] = 0xffffffff; } - int64_t group_length; - if (!dcm_element_get_value_integer(error, element, 0, &group_length)) { - dcm_element_destroy(element); - dcm_dataset_destroy(file_meta); - return NULL; + // parse just the per-frame stuff + filehandle->frame_number = 0; + if (!dcm_parse_dataset(error, + filehandle->io, + filehandle->implicit, + &parse, + filehandle)) { + return false; } - dcm_element_destroy(element); - - // File Meta Information Version - element = read_element(error, filehandle, &position, implicit); - if (element == NULL) { - dcm_dataset_destroy(file_meta); - return NULL; - } + return true; +} - dcm_element_destroy(element); - while (position < group_length) { - uint32_t length; - element = read_element_header(error, - filehandle, &length, &position, implicit); - if (element == NULL) { - dcm_dataset_destroy(file_meta); - return NULL; - } +static bool parse_skip_to_index(void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; - group_number = dcm_element_get_group_number(element); - if (group_number != 0x0002) { - dcm_element_destroy(element); - break; - } + USED(vr); + USED(length); - if (!read_element_body(error, element, filehandle, - length, &position, implicit) || - !dcm_dataset_insert(error, file_meta, element)) { - dcm_element_destroy(element); - dcm_dataset_destroy(file_meta); - return NULL; - } - } + filehandle->last_tag = tag; - if (!dcm_offset(error, filehandle, &filehandle->offset)) { - dcm_dataset_destroy(file_meta); - return NULL; - } + return tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE || + tag == TAG_PIXEL_DATA || + tag == TAG_FLOAT_PIXEL_DATA || + tag == TAG_DOUBLE_PIXEL_DATA; +} - element = dcm_dataset_get(error, file_meta, 0x00020010); - if (element == NULL) { - filehandle->offset = 0; - dcm_dataset_destroy(file_meta); - return NULL; - } - const char *transfer_syntax_uid; - if (!dcm_element_get_value_string(error, - element, 0, &transfer_syntax_uid)) { - filehandle->offset = 0; - dcm_dataset_destroy(file_meta); - return NULL; - } +static bool read_skip_to_index(DcmError **error, + DcmFilehandle *filehandle) - filehandle->transfer_syntax_uid = dcm_strdup(error, transfer_syntax_uid); - if (filehandle->transfer_syntax_uid == NULL) { - filehandle->offset = 0; - dcm_dataset_destroy(file_meta); - return NULL; +{ + static DcmParse parse = { + .stop = parse_skip_to_index, + }; + + if (!dcm_parse_dataset(error, + filehandle->io, + filehandle->implicit, + &parse, + filehandle)) { + return false; } - dcm_dataset_lock(file_meta); - - return file_meta; + return true; } -DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, - DcmFilehandle *filehandle) +bool dcm_filehandle_prepare_read_frame(DcmError **error, + DcmFilehandle *filehandle) { - bool implicit; - - if (filehandle->offset == 0) { - DcmDataSet *meta = dcm_filehandle_read_file_meta(error, filehandle); - if (meta== NULL) { - return NULL; + if (filehandle->offset_table == NULL) { + // move to the first of our stop tags + if (dcm_filehandle_get_metadata_subset(error, filehandle) == NULL) { + return false; } - dcm_dataset_destroy(meta); - } - if (!dcm_seekset(error, filehandle, filehandle->offset)) { - return NULL; - } - - implicit = false; - if (filehandle->transfer_syntax_uid) { - if (strcmp(filehandle->transfer_syntax_uid, "1.2.840.10008.1.2") == 0) { - implicit = true; + if (filehandle->layout == DCM_LAYOUT_UNKNOWN) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading PixelData failed", + "Unsupported DimensionOrganisationType."); + return false; } - } - DcmDataSet *dataset = dcm_dataset_create(error); - if (dataset == NULL) { - return NULL; - } + // we may have previously stopped for many reasons ... skip ahead to per + // frame functional group, or pixel data + if (!read_skip_to_index(error, filehandle)) { + return false; + } - int64_t position = 0; - for (;;) { - if (dcm_is_eof(filehandle)) { - dcm_log_info("Stop reading Data Set. Reached end of filehandle."); - break; + // if we're on per frame func, read that in + if (filehandle->last_tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE && + !read_frame_index(error, filehandle)) { + return false; } - uint32_t length; - DcmElement *element = read_element_header(error, - filehandle, - &length, - &position, - implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return NULL; + // and we must now be on pixel data + if (filehandle->last_tag != TAG_PIXEL_DATA && + filehandle->last_tag != TAG_FLOAT_PIXEL_DATA && + filehandle->last_tag != TAG_DOUBLE_PIXEL_DATA) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading PixelData failed", + "Could not determine offset of Pixel Data Element."); + return false; } - uint32_t tag = dcm_element_get_tag(element); - if (tag == TAG_TRAILING_PADDING) { - dcm_log_info("Stop reading Data Set", - "Encountered Data Set Trailing Tag"); - dcm_element_destroy(element); - break; + if (!dcm_offset(error, filehandle, &filehandle->pixel_data_offset)) { + return false; } - if (tag == TAG_PIXEL_DATA || - tag == TAG_FLOAT_PIXEL_DATA || - tag == TAG_DOUBLE_PIXEL_DATA) { - dcm_log_debug("Stop reading Data Set. " - "Encountered Tag of Pixel Data Element."); - dcm_element_destroy(element); - - // Set filehandle pointer to the first byte of the - // pixel data element - if (implicit) { - // Tag: 4 bytes, Value Length: 4 bytes - if (!dcm_seekcur(error, filehandle, -8, &position)) { - dcm_dataset_destroy(dataset); - return NULL; - } + // read the BOT, or build it + dcm_log_debug("Reading PixelData."); + filehandle->offset_table = DCM_NEW_ARRAY(error, + filehandle->num_frames, + int64_t); + if (filehandle->offset_table == NULL) { + return false; + } - } else { - // Tag: 4 bytes, VR: 2 bytes + 2 bytes, Value Length: 4 bytes - if (!dcm_seekcur(error, filehandle, -12, &position)) { - dcm_dataset_destroy(dataset); - return NULL; - } + const char *syntax = dcm_filehandle_get_transfer_syntax_uid(filehandle); + if (dcm_is_encapsulated_transfer_syntax(syntax)) { + // read the bot if available, otherwise parse pixeldata to find + // offsets + if (!dcm_parse_pixeldata_offsets(error, + filehandle->io, + filehandle->implicit, + &filehandle->first_frame_offset, + filehandle->offset_table, + filehandle->num_frames)) { + return false; } - - if (!dcm_offset(error, - filehandle, &filehandle->pixel_data_offset)) { - dcm_dataset_destroy(dataset); - return NULL; + } else { + for (uint32_t i = 0; i < filehandle->num_frames; i++) { + filehandle->offset_table[i] = i * + filehandle->desc.rows * + filehandle->desc.columns * + filehandle->desc.samples_per_pixel; } - break; - } - - if (dcm_element_get_group_number(element) == 0x0002) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading of Data Set failed", - "Encountered File Meta Information group"); - dcm_element_destroy(element); - dcm_dataset_destroy(dataset); - return NULL; + // Header of Pixel Data Element + filehandle->first_frame_offset = 12; } - - if (!read_element_body(error, element, filehandle, - length, &position, implicit) || - !dcm_dataset_insert(error, dataset, element)) { - dcm_element_destroy(element); - dcm_dataset_destroy(dataset); - return NULL; + } else { + // always position at pixel_data + if (!dcm_seekset(error, filehandle, filehandle->pixel_data_offset)) { + return false; } } - dcm_dataset_lock(dataset); - - return dataset; -} - - -static bool get_num_frames(DcmError **error, - const DcmDataSet *metadata, - uint32_t *number_of_frames) -{ - const uint32_t tag = 0x00280008; - const char *value; - uint32_t num_frames; - - DcmElement *element = dcm_dataset_get(error, metadata, tag); - if (element == NULL || - !dcm_element_get_value_string(error, element, 0, &value)) { - return false; - } - - num_frames = (uint32_t) strtol(value, NULL, 10); - if (num_frames == 0) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Basic Offset Table read failed", - "Value of Data Element 'Number of Frames' is malformed"); - return false; - } - - *number_of_frames = num_frames; - return true; } -DcmBOT *dcm_filehandle_read_bot(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata) +DcmFrame *dcm_filehandle_read_frame(DcmError **error, + DcmFilehandle *filehandle, + uint32_t frame_number) { - uint64_t value; - - dcm_log_debug("Reading Basic Offset Table."); + dcm_log_debug("Read frame number #%u.", frame_number); - if (!dcm_is_encapsulated_transfer_syntax(filehandle->transfer_syntax_uid)) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "Data Set with transfer syntax '%s' should not contain " - "a Basic Offset Table because it is not encapsulated", - filehandle->transfer_syntax_uid); + if (!dcm_filehandle_prepare_read_frame(error, filehandle)) { return NULL; } - uint32_t num_frames; - if (!get_num_frames(error, metadata, &num_frames)) { + if (frame_number == 0) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Frame Item failed", + "Frame Number must be non-zero"); return NULL; } - - if (filehandle->pixel_data_offset == 0) { + if (frame_number > filehandle->num_frames) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "Could not determine offset of Pixel Data Element. " - "Read metadata first"); + "Reading Frame Item failed", + "Frame Number must be less than %u", + filehandle->num_frames); return NULL; } - if (!dcm_seekset(error, filehandle, filehandle->pixel_data_offset)) { + // we are zero-based from here on + uint32_t i = frame_number - 1; + + ssize_t total_frame_offset = filehandle->pixel_data_offset + + filehandle->first_frame_offset + + filehandle->offset_table[i]; + if (!dcm_seekset(error, filehandle, total_frame_offset)) { return NULL; } - // measure distance to first frame from pixel_data_offset - int64_t position = 0; - DcmElement *element; uint32_t length; - element = read_element_header(error, filehandle, &length, &position, false); - uint32_t tag = dcm_element_get_tag(element); - dcm_element_destroy(element); - - if (tag != TAG_PIXEL_DATA && - tag != TAG_FLOAT_PIXEL_DATA && - tag != TAG_DOUBLE_PIXEL_DATA) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "File pointer not positioned at Pixel Data Element"); + char *frame_data = dcm_parse_frame(error, + filehandle->io, + filehandle->implicit, + &filehandle->desc, + &length); + if (frame_data == NULL) { return NULL; } - // The header of the BOT Item - if (!read_iheader(error, filehandle, &tag, &length, &position)) { + return dcm_frame_create(error, + frame_number, + frame_data, + length, + filehandle->desc.rows, + filehandle->desc.columns, + filehandle->desc.samples_per_pixel, + filehandle->desc.bits_allocated, + filehandle->desc.bits_stored, + filehandle->desc.pixel_representation, + filehandle->desc.planar_configuration, + filehandle->desc.photometric_interpretation, + filehandle->desc.transfer_syntax_uid); +} + + +DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, + DcmFilehandle *filehandle, + uint32_t column, + uint32_t row) +{ + dcm_log_debug("Read frame position (%u, %u)", column, row); + + if (!dcm_filehandle_prepare_read_frame(error, filehandle)) { return NULL; } - if (tag != TAG_ITEM) { + + if (column >= filehandle->tiles_across) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "Unexpected Tag found for Basic Offset Table Item"); + "Reading Frame position failed", + "Column must be less than %u", + filehandle->tiles_across); return NULL; } - ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); - if (offsets == NULL) { + uint32_t index = column + row * filehandle->tiles_across; + + if (index >= filehandle->num_frames) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Frame position failed", + "Row must be less than %u", + filehandle->num_frames / filehandle->tiles_across); return NULL; } - // The BOT Item must be present, but the value is optional - ssize_t first_frame_offset; - if (length > 0) { - dcm_log_info("Read Basic Offset Table value."); - - // Read offset values from BOT Item value - // FIXME .. could do this with a single require to a uint32_t array, - // see numeric array read above - for (uint32_t i = 0; i < num_frames; i++) { - uint32_t ui32; - if (!read_uint32(error, filehandle, &ui32, &position)) { - free(offsets); - return NULL; - } - - uint64_t value = ui32; - if (value == TAG_ITEM) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "Encountered unexpected Item Tag " - "in Basic Offset Table"); - free(offsets); - return NULL; - } - - offsets[i] = value; - } - - // and that's the offset to the item header on the first frame - first_frame_offset = position; - } else { - dcm_log_info("Basic Offset Table is empty"); - // Handle Extended Offset Table attribute - const DcmElement *eot_element = dcm_dataset_contains(metadata, - 0x7FE00001); - if (eot_element == NULL) { + if (filehandle->layout == DCM_LAYOUT_SPARSE) { + index = filehandle->frame_index[index]; + if (index == 0xffffffff) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "No Basic Offset Table, " - "and no Extended Offset Table"); - free(offsets); + "Reading Frame position failed", + "No Frame at position (%u, %u)", column, row); return NULL; } + } - dcm_log_info("Found Extended Offset Table."); + // read_frame() numbers from 1 + return dcm_filehandle_read_frame(error, filehandle, index + 1); +} - const char *blob; - if (!dcm_element_get_value_binary(error, eot_element, &blob)) { - free(offsets); - return NULL; - } - for (uint32_t i = 0; i < num_frames; i++) { - char *end_ptr; - value = (uint64_t) strtoull(blob, &end_ptr, 64); - // strtoull returns 0 in case of error - // FIXME and also sets end_ptr to blob - if (value == 0 && i > 0) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "Failed to parse value of Extended Offset " - "Table element for frame #%d", i + 1); - free(offsets); - return NULL; - } - offsets[i] = value; - blob = end_ptr; - } +static bool print_dataset_begin(DcmError **error, + void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + int *index = (int *) utarray_back(filehandle->index_stack); + + USED(error); - // FIXME is this correct? - first_frame_offset = position; + *index += 1; + + if (filehandle->indent > 0) { + printf("%*.*s---Item #%d---\n", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + *index); } - return dcm_bot_create(error, offsets, num_frames, first_frame_offset); + return true; } -static bool set_pixel_description(DcmError **error, - struct PixelDescription *desc, - const DcmDataSet *metadata) +static bool print_sequence_begin(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) { - DcmElement *element; - int64_t value; - const char *string; + DcmFilehandle *filehandle = (DcmFilehandle *) client; - element = dcm_dataset_get(error, metadata, 0x00280010); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->rows = value; + USED(error); + USED(vr); + USED(length); - element = dcm_dataset_get(error, metadata, 0x00280011); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->columns = value; + printf("%*.*s(%04x,%04x) ", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + (tag & 0xffff0000) >> 16, + tag >> 16); - element = dcm_dataset_get(error, metadata, 0x00280002); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; + if (dcm_is_public_tag(tag)) { + printf("%s ", dcm_dict_keyword_from_tag(tag)); } - desc->samples_per_pixel = value; - element = dcm_dataset_get(error, metadata, 0x00280100); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->bits_allocated = value; + printf("[\n"); - element = dcm_dataset_get(error, metadata, 0x00280101); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->bits_stored = value; + filehandle->indent += 1; + int index = 0; + utarray_push_back(filehandle->index_stack, &index); - element = dcm_dataset_get(error, metadata, 0x00280103); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->pixel_representation = value; + return true; +} - element = dcm_dataset_get(error, metadata, 0x00280006); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->planar_configuration = value; - element = dcm_dataset_get(error, metadata, 0x00280004); - if (element == NULL || - !dcm_element_get_value_string(error, element, 0, &string)) { - return false; - } - desc->photometric_interpretation = string; +static bool print_sequence_end(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + USED(error); + USED(tag); + USED(vr); + USED(length); + + filehandle->indent -= 1; + utarray_pop_back(filehandle->index_stack); + + printf("%*.*s]\n", + filehandle->indent * 2, + filehandle->indent * 2, + " "); return true; } -DcmBOT *dcm_filehandle_build_bot(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata) +static bool print_pixeldata_begin(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) { - uint64_t i; + DcmFilehandle *filehandle = (DcmFilehandle *) client; - dcm_log_debug("Building Basic Offset Table."); + USED(error); - uint32_t num_frames; - if (!get_num_frames(error, metadata, &num_frames)) { - return NULL; - } + printf("%*.*s(%04x,%04x) ", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + (tag & 0xffff0000) >> 16, + tag >> 16); - if (filehandle->pixel_data_offset == 0) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "Could not determine offset of Pixel Data Element. " - "Read metadata first."); - return NULL; + if (dcm_is_public_tag(tag)) { + printf("%s ", dcm_dict_keyword_from_tag(tag)); } - if (!dcm_seekset(error, filehandle, filehandle->pixel_data_offset)) { - return NULL; - } + printf("| %s | %u ", dcm_dict_str_from_vr(vr), length); - // we measure offsets from this point - int64_t position = 0; + printf("[\n"); - uint32_t length; - DcmElement *element = read_element_header(error, - filehandle, - &length, - &position, - false); - uint32_t tag = dcm_element_get_tag(element); - dcm_element_destroy(element); - - if (tag != TAG_PIXEL_DATA && - tag != TAG_FLOAT_PIXEL_DATA && - tag != TAG_DOUBLE_PIXEL_DATA) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Building Basic Offset Table failed", - "Pixel data offset not positioned at Pixel Data Element"); - return NULL; - } + filehandle->indent += 1; + int index = 0; + utarray_push_back(filehandle->index_stack, &index); - ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); - if (offsets == NULL) { - return NULL; - } + return true; +} - ssize_t first_frame_offset; - if (dcm_is_encapsulated_transfer_syntax(filehandle->transfer_syntax_uid)) { - uint32_t length; - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - free(offsets); - return NULL; - } +static bool print_pixeldata_end(DcmError **error, void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; - if (tag != TAG_ITEM) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Building Basic Offset Table failed", - "Unexpected Tag found for Basic Offset Table Item"); - free(offsets); - return NULL; - } + USED(error); - // Move filehandlepointer to the first byte of first Frame item - if (!dcm_seekcur(error, filehandle, length, &position)) { - free(offsets); - } + filehandle->indent -= 1; + utarray_pop_back(filehandle->index_stack); - // and that's the offset to the first frame - first_frame_offset = position; + printf("%*.*s]\n", + filehandle->indent * 2, + filehandle->indent * 2, + " "); - // now measure positions from the start of the first frame - position = 0; + return true; +} - i = 0; - while (true) { - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - free(offsets); - return NULL; - } - if (tag == TAG_SQ_DELIM) { - break; - } +static bool print_element_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; - if (tag != TAG_ITEM) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Building Basic Offset Table failed", - "Frame Item #%d has wrong Tag '%08X'", - i + 1, - tag); - free(offsets); - return NULL; - } + USED(error); - if (dcm_is_eof(filehandle)) { - break; - } + printf("%*.*s(%04x,%04x) ", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + (tag & 0xffff0000) >> 16, + tag & 0xffff); - // step back to the start of the item for this frame - offsets[i] = position - 8; + if (dcm_is_public_tag(tag)) { + printf("%s ", dcm_dict_keyword_from_tag(tag)); + } - if (!dcm_seekcur(error, filehandle, length, &position)) { - free(offsets); - return NULL; - } + printf("| %s | %u ", dcm_dict_str_from_vr(vr), length); - i += 1; + // make an element so we can make a printable string (if possible) + DcmElement *element = dcm_element_create(NULL, tag, vr); + if (element != NULL) { + char *str; + if (dcm_element_set_value(NULL, element, value, length, false) && + (str = dcm_element_value_to_string(element))) { + printf("| %u | %s\n", dcm_element_get_vm(element), str); + free(str); } - if (i != num_frames) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Building Basic Offset Table failed", - "Found incorrect number of Frame Items"); - free(offsets); - return NULL; - } - } else { - struct PixelDescription desc; - if (!set_pixel_description(error, &desc, metadata)) { - free(offsets); - return NULL; - } - - for (i = 0; i < num_frames; i++) { - offsets[i] = i * desc.rows * desc.columns * desc.samples_per_pixel; - } - - // Header of Pixel Data Element - first_frame_offset = 10; + dcm_element_destroy(element); } - return dcm_bot_create(error, offsets, num_frames, first_frame_offset); + return true; } -DcmFrame *dcm_filehandle_read_frame(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata, - DcmBOT *bot, - uint32_t number) +static bool print_pixeldata_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) { - uint32_t length; + DcmFilehandle *filehandle = (DcmFilehandle *) client; + size_t size = dcm_dict_vr_size(vr); + int *index = (int *) utarray_back(filehandle->index_stack); - dcm_log_debug("Read Frame Item #%d.", number); - if (number == 0) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Frame Item failed", - "Frame Number must be positive"); - return NULL; - } + USED(error); + USED(tag); - ssize_t frame_offset = dcm_bot_get_frame_offset(bot, number); - ssize_t total_frame_offset = filehandle->pixel_data_offset + frame_offset; - if (!dcm_seekset(error, filehandle, total_frame_offset)) { - return NULL; + printf("%*.*sframe %d ", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + *index); + + printf("| %u | ", length); + + uint32_t n = MIN(16, length); + for (uint32_t i = 0; i < n; i++) { + printf("%02x", value[i]); + + if (i % size == size - 1) { + printf(" "); + } } - struct PixelDescription desc; - if (!set_pixel_description(error, &desc, metadata)) { - return NULL; + if (length > 16) { + printf("..."); } - int64_t position = 0; - if (dcm_is_encapsulated_transfer_syntax(filehandle->transfer_syntax_uid)) { - uint32_t tag; - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - return NULL; - } + printf("\n"); - if (tag != TAG_ITEM) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Frame Item failed", - "No Item Tag found for Frame Item #%d", - number); - return NULL; - } - } else { - length = desc.rows * desc.columns * desc.samples_per_pixel; + *index += 1; + + return true; +} + +bool dcm_filehandle_print(DcmError **error, + DcmFilehandle *filehandle) +{ + static DcmParse parse = { + .dataset_begin = print_dataset_begin, + .sequence_begin = print_sequence_begin, + .sequence_end = print_sequence_end, + .pixeldata_begin = print_pixeldata_begin, + .pixeldata_end = print_pixeldata_end, + .element_create = print_element_create, + .pixeldata_create = print_pixeldata_create, + .stop = NULL, + }; + + // skip File Preamble + int64_t position = 0; + filehandle->indent = 0; + dcm_filehandle_clear(filehandle); + if (!parse_preamble(error, filehandle, &position)) { + return false; } - char *value = DCM_MALLOC(error, length); - if (value == NULL) { - return NULL; + // print the first group + printf("===File Meta Information===\n"); + dcm_log_info("Read File Meta Information"); + if (!dcm_parse_group(error, + filehandle->io, + false, + &parse, + filehandle)) { + return false; } - if (!dcm_require(error, filehandle, value, length, &position)) { - free(value); - return NULL; + + // print the rest of the file + printf("===Dataset===\n"); + dcm_log_info("Read metadata"); + if (!dcm_parse_dataset(error, + filehandle->io, + // FIXME we should check transfer syntax for this + false, + &parse, + filehandle)) { + return false; } - return dcm_frame_create(error, - number, - value, - length, - desc.rows, - desc.columns, - desc.samples_per_pixel, - desc.bits_allocated, - desc.bits_stored, - desc.pixel_representation, - desc.planar_configuration, - desc.photometric_interpretation, - filehandle->transfer_syntax_uid); + return true; } diff --git a/src/dicom-io.c b/src/dicom-io.c index 14a08bc..0ac70e0 100644 --- a/src/dicom-io.c +++ b/src/dicom-io.c @@ -38,56 +38,44 @@ #define BUFFER_SIZE (4096) typedef struct _DcmIOFile { + DcmIOMethods *methods; + + // private fields int fd; - char *filehandlename; + char *filename; char input_buffer[BUFFER_SIZE]; int bytes_in_buffer; int read_point; } DcmIOFile; -static int dcm_io_close_filehandle(DcmError **error, void *data) +static void dcm_io_close_file(DcmIO *io) { - DcmIOFile *io_filehandle = (DcmIOFile *) data; - - int close_errno = 0; - - if (io_filehandle->fd != -1) { - if (close(io_filehandle->fd)) { - close_errno = errno; - } + DcmIOFile *file = (DcmIOFile *) io; - io_filehandle->fd = -1; - - if (close_errno) { - dcm_error_set(error, DCM_ERROR_CODE_IO, - "Unable to close filehandle", - "Unable to close %s - %s", - io_filehandle->filehandlename, strerror(close_errno)); - } + if (file->fd != -1) { + (void) close(file->fd); } - free(io_filehandle->filehandlename); - free(io_filehandle); - - return close_errno; + free(file->filename); + free(file); } -static void *dcm_io_open_filehandle(DcmError **error, void *client) +static DcmIO *dcm_io_open_file(DcmError **error, void *client) { - DcmIOFile *io_filehandle = DCM_NEW(error, DcmIOFile); - if (io_filehandle == NULL) { + DcmIOFile *file = DCM_NEW(error, DcmIOFile); + if (file == NULL) { return NULL; } // The "not set" value for fd - io_filehandle->fd = -1; + file->fd = -1; - const char *filehandlename = (const char *) client; - io_filehandle->filehandlename = dcm_strdup(error, filehandlename); - if (io_filehandle->filehandlename == NULL) { - dcm_io_close_filehandle(error, io_filehandle); + const char *filename = (const char *) client; + file->filename = dcm_strdup(error, filename); + if (file->filename == NULL) { + dcm_io_close_file((DcmIO *)file); return NULL; } @@ -99,7 +87,7 @@ static void *dcm_io_open_filehandle(DcmError **error, void *client) // #define _SH_DENYWR 0x20 int shflag = 0x20; int pmode = 0; - open_errno = _sopen_s(&io_filehandle->fd, io_filehandle->filehandlename, + open_errno = _sopen_s(&file->fd, file->filename, oflag, shflag, pmode); #else int flags = O_RDONLY; @@ -108,41 +96,41 @@ static void *dcm_io_open_filehandle(DcmError **error, void *client) #endif mode_t mode = 0; do - io_filehandle->fd = open(io_filehandle->filehandlename, flags, mode); - while (io_filehandle->fd == -1 && errno == EINTR); + file->fd = open(file->filename, flags, mode); + while (file->fd == -1 && errno == EINTR); open_errno = errno; #endif - if (io_filehandle->fd == -1) { + if (file->fd == -1) { dcm_error_set(error, DCM_ERROR_CODE_IO, "Unable to open filehandle", - "Unable to open %s - %s", io_filehandle->filehandlename, strerror(open_errno)); - dcm_io_close_filehandle(error, io_filehandle); + "Unable to open %s - %s", file->filename, strerror(open_errno)); + dcm_io_close_file((DcmIO *)file); return NULL; } - return io_filehandle; + return (DcmIO *)file; } -static int64_t read_filehandle(DcmError **error, DcmIOFile *io_filehandle, +static int64_t read_file(DcmError **error, DcmIOFile *file, char *buffer, int64_t length) { int64_t bytes_read; #ifdef _WIN32 - bytes_read = _read(io_filehandle->fd, buffer, length); + bytes_read = _read(file->fd, buffer, length); #else do { - bytes_read = read(io_filehandle->fd, buffer, length); + bytes_read = read(file->fd, buffer, length); } while (bytes_read < 0 && errno == EINTR); #endif if (bytes_read < 0) { dcm_error_set(error, DCM_ERROR_CODE_IO, - "Unable to read from filehandle", - "Unable to read %s - %s", io_filehandle->filehandlename, strerror(errno)); + "Unable to read from file", + "Unable to read %s - %s", file->filename, strerror(errno)); } return bytes_read; @@ -152,55 +140,55 @@ static int64_t read_filehandle(DcmError **error, DcmIOFile *io_filehandle, /* Refill the input buffer. * -1 on error, 0 on EOF, otherwise bytes read. */ -static int64_t refill(DcmError **error, DcmIOFile *io_filehandle) +static int64_t refill(DcmError **error, DcmIOFile *file) { // buffer should be empty coming in - assert(io_filehandle->bytes_in_buffer - io_filehandle->read_point == 0); + assert(file->bytes_in_buffer - file->read_point == 0); - int64_t bytes_read = read_filehandle(error, io_filehandle, - io_filehandle->input_buffer, BUFFER_SIZE); + int64_t bytes_read = read_file(error, file, + file->input_buffer, BUFFER_SIZE); if (bytes_read < 0) { return bytes_read; } - io_filehandle->read_point = 0; - io_filehandle->bytes_in_buffer = bytes_read; + file->read_point = 0; + file->bytes_in_buffer = bytes_read; return bytes_read; } -static int64_t dcm_io_read_filehandle(DcmError **error, void *data, +static int64_t dcm_io_read_file(DcmError **error, DcmIO *io, char *buffer, int64_t length) { - DcmIOFile *io_filehandle = (DcmIOFile *) data; + DcmIOFile *file = (DcmIOFile *) io; int64_t bytes_read = 0; while (length > 0) { /* Refill the input buffer if it's empty. */ - if (io_filehandle->bytes_in_buffer - io_filehandle->read_point == 0) { - int64_t refill_bytes = refill(error, io_filehandle); + if (file->bytes_in_buffer - file->read_point == 0) { + int64_t refill_bytes = refill(error, file); if (refill_bytes < 0) { return refill_bytes; } else if (refill_bytes == 0) { - // we may be read some bytes in a previous loop + // we maybe read some bytes in a previous loop return bytes_read; } } /* Read what we can from the buffer. */ - int bytes_available = io_filehandle->bytes_in_buffer - io_filehandle->read_point; + int bytes_available = file->bytes_in_buffer - file->read_point; int bytes_to_copy = MIN(bytes_available, length); memcpy(buffer, - io_filehandle->input_buffer + io_filehandle->read_point, + file->input_buffer + file->read_point, bytes_to_copy); length -= bytes_to_copy; buffer += bytes_to_copy; - io_filehandle->read_point += bytes_to_copy; + file->read_point += bytes_to_copy; bytes_read += bytes_to_copy; } @@ -208,121 +196,134 @@ static int64_t dcm_io_read_filehandle(DcmError **error, void *data, } -static int64_t dcm_io_seek_filehandle(DcmError **error, void *data, +static int64_t dcm_io_seek_file(DcmError **error, DcmIO *io, int64_t offset, int whence) { - DcmIOFile *io_filehandle = (DcmIOFile *) data; + DcmIOFile *file = (DcmIOFile *) io; /* We've read ahead by some number of buffered bytes, so first undo that, * then do the seek from the true position. */ int64_t new_offset; - int64_t bytes_ahead = io_filehandle->bytes_in_buffer - io_filehandle->read_point; + int64_t bytes_ahead = file->bytes_in_buffer - file->read_point; if (bytes_ahead > 0) { #ifdef _WIN32 - new_offset = _lseeki64(io_filehandle->fd, -bytes_ahead, SEEK_CUR); + new_offset = _lseeki64(file->fd, -bytes_ahead, SEEK_CUR); #else - new_offset = lseek(io_filehandle->fd, -bytes_ahead, SEEK_CUR); + new_offset = lseek(file->fd, -bytes_ahead, SEEK_CUR); #endif if (new_offset < 0) { dcm_error_set(error, DCM_ERROR_CODE_IO, - "Unable to seek filehandle", - "Unable to seek %s - %s", io_filehandle->filehandlename, strerror(errno)); + "Unable to seek file", + "Unable to seek %s - %s", file->filename, strerror(errno)); } } #ifdef _WIN32 - new_offset = _lseeki64(io_filehandle->fd, offset, whence); + new_offset = _lseeki64(file->fd, offset, whence); #else - new_offset = lseek(io_filehandle->fd, offset, whence); + new_offset = lseek(file->fd, offset, whence); #endif if (new_offset < 0) { dcm_error_set(error, DCM_ERROR_CODE_IO, - "Unable to seek filehandle", - "Unable to seek %s - %s", io_filehandle->filehandlename, strerror(errno)); + "Unable to seek file", + "Unable to seek %s - %s", file->filename, strerror(errno)); } /* Empty the buffer, since we may now be at a different position. */ - io_filehandle->bytes_in_buffer = 0; - io_filehandle->read_point = 0; + file->bytes_in_buffer = 0; + file->read_point = 0; return new_offset; } -DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, - const char *filehandle_path) +DcmIO *dcm_io_create(DcmError **error, + const DcmIOMethods *methods, + void *client) { - static DcmIO io = { - dcm_io_open_filehandle, - dcm_io_close_filehandle, - dcm_io_read_filehandle, - dcm_io_seek_filehandle, + DcmIO *io = methods->open(error, client); + if (io == NULL) { + return NULL; + } + io->methods = methods; + + return io; +} + + +DcmIO *dcm_io_create_from_file(DcmError **error, const char *filename) +{ + static DcmIOMethods methods = { + dcm_io_open_file, + dcm_io_close_file, + dcm_io_read_file, + dcm_io_seek_file, }; - return dcm_filehandle_create(error, &io, (void *) filehandle_path); + return dcm_io_create(error, &methods, (void *) filename); } typedef struct _DcmIOMemory { + DcmIOMethods *methods; + + // private fields const char *buffer; int64_t length; int64_t read_point; } DcmIOMemory; -static int dcm_io_close_memory(DcmError **error, void *data) +static void dcm_io_close_memory(DcmIO *io) { - DcmIOMemory *io_memory = (DcmIOMemory *) data; + DcmIOMemory *memory = (DcmIOMemory *) io; - USED(error); - free(io_memory); - - return 0; + free(memory); } -static void *dcm_io_open_memory(DcmError **error, void *client) +static DcmIO *dcm_io_open_memory(DcmError **error, void *client) { DcmIOMemory *params = (DcmIOMemory *)client; - DcmIOMemory *io_memory = DCM_NEW(error, DcmIOMemory); - if (io_memory == NULL) { + DcmIOMemory *memory = DCM_NEW(error, DcmIOMemory); + if (memory == NULL) { return NULL; } - io_memory->buffer = params->buffer; - io_memory->length = params->length; + memory->buffer = params->buffer; + memory->length = params->length; - return io_memory; + return (DcmIO *) memory; } -static int64_t dcm_io_read_memory(DcmError **error, void *data, +static int64_t dcm_io_read_memory(DcmError **error, DcmIO *io, char *buffer, int64_t length) { - DcmIOMemory *io_memory = (DcmIOMemory *) data; + DcmIOMemory *memory = (DcmIOMemory *) io; USED(error); - int bytes_available = io_memory->length - io_memory->read_point; + int bytes_available = memory->length - memory->read_point; int bytes_to_copy = MIN(bytes_available, length); memcpy(buffer, - io_memory->buffer + io_memory->read_point, + memory->buffer + memory->read_point, bytes_to_copy); - io_memory->read_point += bytes_to_copy; + memory->read_point += bytes_to_copy; return bytes_to_copy; } -static int64_t dcm_io_seek_memory(DcmError **error, void *data, +static int64_t dcm_io_seek_memory(DcmError **error, DcmIO *io, int64_t offset, int whence) { - DcmIOMemory *io_memory = (DcmIOMemory *) data; + DcmIOMemory *memory = (DcmIOMemory *) io; int64_t new_offset; @@ -333,11 +334,11 @@ static int64_t dcm_io_seek_memory(DcmError **error, void *data, break; case SEEK_CUR: - new_offset = io_memory->read_point + offset; + new_offset = memory->read_point + offset; break; case SEEK_END: - new_offset = io_memory->length + offset; + new_offset = memory->length + offset; break; default: @@ -347,17 +348,17 @@ static int64_t dcm_io_seek_memory(DcmError **error, void *data, return -1; } - io_memory->read_point = MAX(0, MIN(new_offset, io_memory->length)); + memory->read_point = MAX(0, MIN(new_offset, memory->length)); - return io_memory->read_point; + return memory->read_point; } -DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, - const char *buffer, - int64_t length) +DcmIO *dcm_io_create_from_memory(DcmError **error, + const char *buffer, + int64_t length) { - static DcmIO io = { + static DcmIOMethods methods = { dcm_io_open_memory, dcm_io_close_memory, dcm_io_read_memory, @@ -365,10 +366,36 @@ DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, }; DcmIOMemory memory = { + &methods, buffer, length, 0 }; - return dcm_filehandle_create(error, &io, &memory); + return dcm_io_create(error, &methods, &memory); +} + + +void dcm_io_close(DcmIO *io) +{ + io->methods->close(io); } + + +int64_t dcm_io_read(DcmError **error, + DcmIO *io, + char *buffer, + int64_t length) +{ + return io->methods->read(error, io, buffer, length); +} + + +int64_t dcm_io_seek(DcmError **error, + DcmIO *io, + int64_t offset, + int whence) +{ + return io->methods->seek(error, io, offset, whence); +} + diff --git a/src/dicom-parse.c b/src/dicom-parse.c new file mode 100644 index 0000000..c90bd68 --- /dev/null +++ b/src/dicom-parse.c @@ -0,0 +1,1047 @@ +/* + * Implementation of Part 10 of the DICOM standard: Media Storage and File + * Format for Media Interchange. + */ + +#include "config.h" + +#ifdef _WIN32 +// the Windows CRT considers strdup and strcpy unsafe +#define _CRT_SECURE_NO_WARNINGS +// and deprecates strdup +#define strdup(v) _strdup(v) +#endif + +#include +#include +#include +#include +#include + +#include "utarray.h" + +#include +#include "pdicom.h" + + +/* The size of the buffer we use for reading smaller element values. This is + * large enough for most VRs. + */ +#define INPUT_BUFFER_SIZE (256) + + +typedef struct _DcmParseState { + DcmError **error; + DcmIO *io; + bool implicit; + bool big_endian; + const DcmParse *parse; + void *client; + + DcmDataSet *meta; + int64_t offset; + int64_t pixel_data_offset; + uint64_t *extended_offset_table; +} DcmParseState; + + +static int64_t dcm_read(DcmParseState *state, + char *buffer, int64_t length, int64_t *position) +{ + int64_t bytes_read = dcm_io_read(state->error, state->io, buffer, length); + if (bytes_read < 0) { + return bytes_read; + } + + *position += bytes_read; + + return bytes_read; +} + + +static bool dcm_require(DcmParseState *state, + char *buffer, int64_t length, int64_t *position) +{ + while (length > 0) { + int64_t bytes_read = dcm_read(state, buffer, length, position); + + if (bytes_read < 0) { + return false; + } else if (bytes_read == 0) { + dcm_error_set(state->error, DCM_ERROR_CODE_IO, + "End of filehandle", + "Needed %zd bytes beyond end of filehandle", length); + return false; + } + + buffer += bytes_read; + length -= bytes_read; + } + + return true; +} + + +static bool dcm_seekcur(DcmParseState *state, int64_t offset, int64_t *position) +{ + int64_t new_offset = dcm_io_seek(state->error, state->io, offset, SEEK_CUR); + if (new_offset < 0) { + return false; + } + + *position += offset; + + return true; +} + + +static bool dcm_is_eof(DcmParseState *state) +{ + bool eof = true; + + char buffer[1]; + int64_t bytes_read = dcm_io_read(NULL, state->io, buffer, 1); + if (bytes_read > 0) { + eof = false; + int64_t position = 0; + (void) dcm_seekcur(state, -1, &position); + } + + return eof; +} + + +/* TRUE for big-endian machines, like PPC. We need to byteswap DICOM + * numeric types in this case. Run time tests for this are much + * simpler to manage when cross-compiling. + */ +static bool is_big_endian(void) +{ + union { + uint32_t i; + char c[4]; + } bint = {0x01020304}; + + return bint.c[0] == 1; +} + + +#define SWAP16(V) \ + ((uint16_t) ( \ + (uint16_t) ((uint16_t) (V) >> 8) | \ + (uint16_t) ((uint16_t) (V) << 8) \ + )) + +#define SWAP32(V) \ + ((uint32_t) ( \ + (((uint32_t) (V) & UINT32_C(0x000000ff)) << 24) | \ + (((uint32_t) (V) & UINT32_C(0x0000ff00)) << 8) | \ + (((uint32_t) (V) & UINT32_C(0x00ff0000)) >> 8) | \ + (((uint32_t) (V) & UINT32_C(0xff000000)) >> 24) \ + )) + +#define SWAP64(V) \ + ((uint64_t) ( \ + (((uint64_t) (V) & UINT64_C(0x00000000000000ff)) << 56) | \ + (((uint64_t) (V) & UINT64_C(0x000000000000ff00)) << 40) | \ + (((uint64_t) (V) & UINT64_C(0x0000000000ff0000)) << 24) | \ + (((uint64_t) (V) & UINT64_C(0x00000000ff000000)) << 8) | \ + (((uint64_t) (V) & UINT64_C(0x000000ff00000000)) >> 8) | \ + (((uint64_t) (V) & UINT64_C(0x0000ff0000000000)) >> 24) | \ + (((uint64_t) (V) & UINT64_C(0x00ff000000000000)) >> 40) | \ + (((uint64_t) (V) & UINT64_C(0xff00000000000000)) >> 56) \ + )) + +static void byteswap(char *data, size_t length, size_t size) +{ + // only swap if the data is "swappable" + if (length >= size && length % size == 0) { + size_t n_elements = length / size; + + switch (size) { + case 2: + for (size_t i = 0; i < n_elements; i++) { + uint16_t *v = &((uint16_t *) data)[i]; + *v = SWAP16(*v); + } + break; + + case 4: + for (size_t i = 0; i < n_elements; i++) { + uint32_t *v = &((uint32_t *) data)[i]; + *v = SWAP32(*v); + } + break; + + case 8: + for (size_t i = 0; i < n_elements; i++) { + uint64_t *v = &((uint64_t *) data)[i]; + *v = SWAP64(*v); + } + break; + + default: + break; + } + } +} + + +static bool read_uint16(DcmParseState *state, + uint16_t *value, int64_t *position) +{ + union { + uint16_t i; + char c[2]; + } buffer; + + if (!dcm_require(state, buffer.c, 2, position)) { + return false; + } + + if (state->big_endian) { + byteswap(buffer.c, 2, 2); + } + + *value = buffer.i; + + return true; +} + + +static bool read_uint32(DcmParseState *state, + uint32_t *value, int64_t *position) +{ + union { + uint32_t i; + char c[4]; + } buffer; + + if (!dcm_require(state, buffer.c, 4, position)) { + return false; + } + + if (state->big_endian) { + byteswap(buffer.c, 4, 4); + } + + *value = buffer.i; + + return true; +} + + +static bool read_tag(DcmParseState *state, uint32_t *tag, int64_t *position) +{ + uint16_t group, elem; + + if (!read_uint16(state, &group, position) || + !read_uint16(state, &elem, position)) { + return false; + } + + *tag = ((uint32_t)group << 16) | elem; + + return true; +} + + +/* This is used recursively. + */ +static bool parse_element(DcmParseState *state, + int64_t *position); + + +static bool parse_element_header(DcmParseState *state, + uint32_t *tag, + DcmVR *vr, + uint32_t *length, + int64_t *position) +{ + if (!read_tag(state, tag, position)) { + return false; + } + + if (state->implicit) { + // this can be an ambiguous VR, eg. pixeldata is allowed in implicit + // mode and has to be disambiguated later from other tags + *vr = dcm_vr_from_tag(*tag); + if (*vr == DCM_VR_ERROR) { + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element header failed", + "Tag %08x not allowed in implicit mode", *tag); + return false; + } + + if (!read_uint32(state, length, position)) { + return false; + } + } else { + // Value Representation + char vr_str[3]; + if (!dcm_require(state, vr_str, 2, position)) { + return false; + } + vr_str[2] = '\0'; + *vr = dcm_dict_vr_from_str(vr_str); + + if (!dcm_is_valid_vr_for_tag(*vr, *tag)) { + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element header failed", + "Tag %08x cannot have VR '%s'", *tag, vr_str); + return false; + } + + if (dcm_dict_vr_header_length(*vr) == 2) { + // These VRs have a short length of only two bytes + uint16_t short_length; + if (!read_uint16(state, &short_length, position)) { + return false; + } + *length = (uint32_t) short_length; + } else { + // Other VRs have two reserved bytes before length of four bytes + uint16_t reserved; + if (!read_uint16(state, &reserved, position) || + !read_uint32(state, length, position)) { + return false; + } + + if (reserved != 0x0000) { + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element header failed", + "Unexpected value for reserved bytes " + "of Data Element %08x with VR '%s'.", + tag, vr); + return false; + } + } + } + + return true; +} + + +static bool parse_element_sequence(DcmParseState *state, + uint32_t seq_tag, + DcmVR seq_vr, + uint32_t seq_length, + int64_t *position) +{ + if (state->parse->sequence_begin && + !state->parse->sequence_begin(state->error, + state->client, + seq_tag, + seq_vr, + seq_length)) { + return false; + } + + int index = 0; + while (*position < seq_length) { + dcm_log_debug("Read Item #%d.", index); + uint32_t item_tag; + uint32_t item_length; + if (!read_tag(state, &item_tag, position) || + !read_uint32(state, &item_length, position)) { + return false; + } + + if (item_tag == TAG_SQ_DELIM) { + dcm_log_debug("Stop reading Data Element. " + "Encountered Sequence Delimination Tag."); + break; + } + + if (item_tag != TAG_ITEM) { + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element failed", + "Expected tag '%08x' instead of '%08x' " + "for Item #%d", + TAG_ITEM, + item_tag, + index); + return false; + } + + if (item_length == 0xFFFFFFFF) { + dcm_log_debug("Item #%d has undefined length.", index); + } else { + dcm_log_debug("Item #%d has defined length %d.", + index, item_length); + } + + if (state->parse->dataset_begin && + !state->parse->dataset_begin(state->error, state->client)) { + return false; + } + + int64_t item_position = 0; + while (item_position < item_length) { + // peek the next tag + if (!read_tag(state, &item_tag, &item_position)) { + return false; + } + + if (item_tag == TAG_ITEM_DELIM) { + dcm_log_debug("Stop reading Item #%d. " + "Encountered Item Delimination Tag.", + index); + // step over the tag length + if (!dcm_seekcur(state, 4, &item_position)) { + return false; + } + + break; + } + + // back to start of element + if (!dcm_seekcur(state, -4, &item_position)) { + return false; + } + + if (!parse_element(state, &item_position)) { + return false; + } + } + + *position += item_position; + + if (state->parse->dataset_end && + !state->parse->dataset_end(state->error, state->client)) { + return false; + } + + index += 1; + } + + if (state->parse->sequence_end && + !state->parse->sequence_end(state->error, + state->client, + seq_tag, + seq_vr, + seq_length)) { + return false; + } + + return true; +} + + +static bool parse_pixeldata_item(DcmParseState *state, + uint32_t tag, + DcmVR vr, + uint32_t length, + uint32_t item_length, + int64_t *position) +{ + // a read buffer on the stack for small objects + char input_buffer[INPUT_BUFFER_SIZE]; + char *value; + char *value_free = NULL; + + USED(tag); + + // read to our stack buffer, if possible + if (item_length > INPUT_BUFFER_SIZE) { + value = value_free = DCM_MALLOC(state->error, item_length); + if (value_free == NULL) { + return false; + } + } else { + value = input_buffer; + } + + if (!dcm_require(state, value, item_length, position)) { + if (value_free != NULL) { + free(value_free); + } + return false; + } + + // native (not encapsulated) pixeldata is always little-endian and needs + // byteswapping on big-endian machines + if (length != 0xffffffff && state->big_endian) { + byteswap(value, item_length, dcm_dict_vr_size(vr)); + } + + if (state->parse->pixeldata_create && + !state->parse->pixeldata_create(state->error, + state->client, + tag, + vr, + value, + item_length)) { + if (value_free != NULL) { + free(value_free); + } + return false; + } + + if (value_free != NULL) { + free(value_free); + } + + return true; +} + + +static bool parse_pixeldata(DcmParseState *state, + uint32_t tag, + DcmVR vr, + uint32_t length, + int64_t *position) +{ + if (state->parse->pixeldata_begin && + !state->parse->pixeldata_begin(state->error, + state->client, + tag, + vr, + length)) { + return false; + } + + if (length == 0xffffffff) { + // a sequence of encapsulated pixeldata items + for (int index = 0; true; index++) { + uint32_t item_tag; + uint32_t item_length; + + dcm_log_debug("Read Item #%d.", index); + if (!read_tag(state, &item_tag, position) || + !read_uint32(state, &item_length, position)) { + return false; + } + + if (item_tag == TAG_SQ_DELIM) { + dcm_log_debug("Stop reading Data Element. " + "Encountered Sequence Delimination Tag."); + break; + } + + if (item_tag != TAG_ITEM) { + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element failed", + "Expected tag '%08x' instead of '%08x' " + "for Item #%d", + TAG_ITEM, + item_tag, + index); + return false; + } + + if (!parse_pixeldata_item(state, + tag, + vr, + length, + item_length, + position)) { + return false; + } + } + } else { + // a single native pixeldata item + if (!parse_pixeldata_item(state, tag, vr, length, length, position)) { + return false; + } + } + + if (state->parse->pixeldata_end && + !state->parse->pixeldata_end(state->error, + state->client)) { + return false; + } + + return true; +} + + +static bool parse_element_body(DcmParseState *state, + uint32_t tag, + DcmVR vr, + uint32_t length, + int64_t *position) +{ + DcmVRClass vr_class = dcm_dict_vr_class(vr); + size_t size = dcm_dict_vr_size(vr); + char *value; + + char *value_free = NULL; + char input_buffer[INPUT_BUFFER_SIZE]; + + /* We treat pixeldata as a special case so we can handle encapsulated + * image sequences. + */ + if (tag == TAG_PIXEL_DATA || + tag == TAG_FLOAT_PIXEL_DATA || + tag == TAG_DOUBLE_PIXEL_DATA) { + return parse_pixeldata(state, tag, vr, length, position); + } + + dcm_log_debug("Read Data Element body '%08x'", tag); + + switch (vr_class) { + case DCM_VR_CLASS_STRING_SINGLE: + case DCM_VR_CLASS_STRING_MULTI: + case DCM_VR_CLASS_NUMERIC_DECIMAL: + case DCM_VR_CLASS_NUMERIC_INTEGER: + case DCM_VR_CLASS_BINARY: + if (vr_class == DCM_VR_CLASS_NUMERIC_DECIMAL || + vr_class == DCM_VR_CLASS_NUMERIC_INTEGER) { + // all numeric classes have a size + if (length % size != 0) { + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element failed", + "Bad length for tag '%08x'", + tag); + return false; + } + } + + // read to a static char buffer, if possible + if ((int64_t) length + 1 >= INPUT_BUFFER_SIZE) { + value = value_free = DCM_MALLOC(state->error, (size_t) length + 1); + if (value == NULL) { + return false; + } + } else { + value = input_buffer; + } + + + if (!dcm_require(state, value, length, position)) { + if (value_free != NULL) { + free(value_free); + } + return false; + } + value[length] = '\0'; + + if (length > 0) { + if (vr != DCM_VR_UI) { + if (isspace(value[length - 1])) { + value[length - 1] = '\0'; + } + } + } + + if (vr_class == DCM_VR_CLASS_NUMERIC_DECIMAL || + vr_class == DCM_VR_CLASS_NUMERIC_INTEGER) { + if (state->big_endian) { + byteswap(value, length, size); + } + } + + if (state->parse->element_create && + !state->parse->element_create(state->error, + state->client, + tag, + vr, + value, + length)) { + if (value_free != NULL) { + free(value_free); + } + return false; + } + + if (value_free != NULL) { + free(value_free); + } + + break; + + case DCM_VR_CLASS_SEQUENCE: + if (length == 0xFFFFFFFF) { + dcm_log_debug("Sequence of Data Element '%08x' " + "has undefined length.", + tag); + } else { + dcm_log_debug("Sequence of Data Element '%08x' " + "has defined length %d.", + tag, length); + } + + int64_t seq_position = 0; + if (!parse_element_sequence(state, + tag, + vr, + length, + &seq_position)) { + return false; + } + *position += seq_position; + + break; + + default: + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, + "Reading of Data Element failed", + "Data Element '%08x' has unexpected " + "Value Representation", tag); + return false; + } + + return true; +} + + +static bool parse_element(DcmParseState *state, + int64_t *position) +{ + uint32_t tag; + DcmVR vr; + uint32_t length; + if (!parse_element_header(state, &tag, &vr, &length, position) || + !parse_element_body(state, tag, vr, length, position)) { + return false; + } + + return true; +} + +/* Top-level datasets don't have an enclosing length, and can broken by a + * stop function. + */ +static bool parse_toplevel_dataset(DcmParseState *state, + int64_t *position) +{ + if (state->parse->dataset_begin && + !state->parse->dataset_begin(state->error, state->client)) { + return false; + } + + for (;;) { + if (dcm_is_eof(state)) { + dcm_log_info("Stop reading Data Set. Reached end of filehandle."); + break; + } + + uint32_t tag; + DcmVR vr; + uint32_t length; + int64_t element_start = 0; + if (!parse_element_header(state, &tag, &vr, &length, &element_start)) { + return false; + } + + if (tag == TAG_TRAILING_PADDING) { + dcm_log_info("Stop reading Data Set", + "Encountered Data Set Trailing Tag"); + break; + } + + if (state->parse->stop && + state->parse->stop(state->client, tag, vr, length)) { + // seek back to the start of this element + if (!dcm_seekcur(state, -element_start, &element_start)) { + return false; + } + break; + } + + *position += element_start; + + if (!parse_element_body(state, tag, vr, length, position)) { + return false; + } + } + + if (state->parse->dataset_end && + !state->parse->dataset_end(state->error, state->client)) { + return false; + } + + return true; +} + + +/* Parse a dataset from a filehandle. + */ +bool dcm_parse_dataset(DcmError **error, + DcmIO *io, + bool implicit, + const DcmParse *parse, + void *client) +{ + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .big_endian = is_big_endian(), + .parse = parse, + .client = client + }; + + int64_t position = 0; + if (!parse_toplevel_dataset(&state, &position)) { + return false; + } + + return true; +} + + +/* Parse a group. A length element, followed by a list of elements. + */ +bool dcm_parse_group(DcmError **error, + DcmIO *io, + bool implicit, + const DcmParse *parse, + void *client) +{ + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .big_endian = is_big_endian(), + .parse = parse, + .client = client + }; + + int64_t position = 0; + + /* Groups start with (xxxx0000, UL, 4), meaning a 32-bit length value. + */ + uint32_t tag; + DcmVR vr; + uint32_t length; + if (!parse_element_header(&state, &tag, &vr, &length, &position)) { + return false; + } + uint16_t element_number = tag & 0xffff; + uint16_t group_number = tag >> 16; + if (element_number != 0x0000 || vr != DCM_VR_UL || length != 4) { + dcm_error_set(state.error, DCM_ERROR_CODE_PARSE, + "Reading of Group failed", + "Bad Group length element"); + return false; + } + uint32_t group_length; + if (!read_uint32(&state, &group_length, &position)) { + return false; + } + + // parse the elements in the group to a dataset + if (state.parse->dataset_begin && + !state.parse->dataset_begin(state.error, state.client)) { + return false; + } + + while (position < group_length) { + int64_t element_start = 0; + if (!parse_element_header(&state, &tag, &vr, &length, &element_start)) { + return false; + } + + // stop if we read the first tag of the group beyond, + // or if the stop function triggers + if ((tag >> 16) != group_number || + (state.parse->stop && + state.parse->stop(state.client, tag, vr, length))) { + // seek back to the start of this element + if (!dcm_seekcur(&state, -element_start, &element_start)) { + return false; + } + + break; + } + + position += element_start; + + if (!parse_element_body(&state, tag, vr, length, &position)) { + return false; + } + } + + if (state.parse->dataset_end && + !state.parse->dataset_end(state.error, state.client)) { + return false; + } + + return true; +} + + +/* Walk pixeldata and set up offsets. We use the BOT, if present, otherwise we + * have to scan the whole thing. + * + * Each offset is the seek from the start of pixeldata to the ITEM for that + * frame. + */ +bool dcm_parse_pixeldata_offsets(DcmError **error, + DcmIO *io, + bool implicit, + int64_t *first_frame_offset, + int64_t *offsets, + int num_frames) +{ + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .big_endian = is_big_endian() + }; + + int64_t position = 0; + + uint32_t tag; + DcmVR vr; + uint32_t length; + uint32_t value; + if (!parse_element_header(&state, &tag, &vr, &length, &position)) { + return false; + } + + if (tag != TAG_PIXEL_DATA && + tag != TAG_FLOAT_PIXEL_DATA && + tag != TAG_DOUBLE_PIXEL_DATA) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Parsing PixelData failed", + "File pointer not positioned at Pixel Data Element"); + return false; + } + + // The header of the 0th item (the BOT) + if (!read_tag(&state, &tag, &position) || + !read_uint32(&state, &length, &position)) { + return false; + } + if (tag != TAG_ITEM) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "Unexpected Tag found for Basic Offset Table Item"); + return false; + } + + if (length > 0) { + // There is a non-zero length BOT, use that + dcm_log_info("Read Basic Offset Table value."); + + // Read offset values from BOT Item value + // FIXME .. could do this with a single require to a uint32_t array, + // see numeric array read above + for (int i = 0; i < num_frames; i++) { + if (!read_uint32(&state, &value, &position)) { + return false; + } + if (value == TAG_ITEM) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "Encountered unexpected Item Tag " + "in Basic Offset Table"); + return false; + } + + offsets[i] = value; + } + + // and that's the offset to the item header on the first frame + *first_frame_offset = position; + + // the next thing should be the tag for frame 1 + if (!read_tag(&state, &tag, &position) || + tag != TAG_ITEM) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "Basic Offset Table too large"); + return false; + } + } else { + // the BOT is missing, we must scan pixeldata to find the position of + // each frame + + // we could use our generic parser above ^^ but we have a special loop + // here as an optimisation (we can skip over the pixel data itself) + + // 0 in the BOT is the offset to the start of frame 1, ie. here + *first_frame_offset = position; + + position = 0; + for (int i = 0; i < num_frames; i++) { + if (!read_tag(&state, &tag, &position) || + !read_uint32(&state, &length, &position)) { + return false; + } + + if (tag == TAG_SQ_DELIM) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "Too few frames in PixelData."); + return false; + } + + if (tag != TAG_ITEM) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Building Basic Offset Table failed", + "Frame Item #%d has wrong Tag '%08x'", + i + 1, + tag); + return false; + } + + // step back to the start of the item for this frame + offsets[i] = position - 8; + + // and seek forward over the value + if (!dcm_seekcur(&state, length, &position)) { + return false; + } + } + + // the next thing should be the end of sequence tag + if (!read_tag(&state, &tag, &position) || + tag != TAG_SQ_DELIM) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "Too many frames in PixelData"); + return false; + } + } + + return true; +} + +char *dcm_parse_frame(DcmError **error, + DcmIO *io, + bool implicit, + struct PixelDescription *desc, + uint32_t *length) +{ + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .big_endian = is_big_endian(), + }; + + int64_t position = 0; + + if (dcm_is_encapsulated_transfer_syntax(desc->transfer_syntax_uid)) { + uint32_t tag; + if (!read_tag(&state, &tag, &position) || + !read_uint32(&state, length, &position)) { + return NULL; + } + + 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; + } + } else { + *length = desc->rows * desc->columns * desc->samples_per_pixel; + } + + char *value = DCM_MALLOC(error, *length); + if (value == NULL) { + return NULL; + } + if (!dcm_require(&state, value, *length, &position)) { + free(value); + return NULL; + } + + return value; +} diff --git a/src/dicom.c b/src/dicom.c index 6ebe4a7..bc8d945 100644 --- a/src/dicom.c +++ b/src/dicom.c @@ -23,10 +23,15 @@ #include #include "pdicom.h" +// we need a namedspaced free for language bindings +void dcm_free(void *pointer) +{ + free(pointer); +} -void *dcm_calloc(DcmError **error, size_t n, size_t size) +void *dcm_calloc(DcmError **error, uint64_t n, uint64_t size) { - /* malloc(0) behaviour depends on the platform heap implementation. It can + /* malloc(0) behaviour depends on the platform heap implementation. It can * return either a valid pointer that can't be dereferenced, or NULL. * * We need to be able to support dcm_calloc(0), since VM == 0 is allowed, @@ -34,7 +39,7 @@ void *dcm_calloc(DcmError **error, size_t n, size_t size) * out of memory. * * Instead, force n == 0 to n == 1. This means we will always get a valid - * pointer from calloc, a NULL return always means out of memory, and we + * pointer from calloc, a NULL return always means out of memory, and we * can always free the result. */ void *result = calloc(n == 0 ? 1 : n, size); @@ -48,6 +53,19 @@ void *dcm_calloc(DcmError **error, size_t n, size_t size) } +void *dcm_realloc(DcmError **error, void *ptr, uint64_t size) +{ + void *result = realloc(ptr, size); + if (!result) { + dcm_error_set(error, DCM_ERROR_CODE_NOMEM, + "Out of memory", + "Failed to allocate %zd bytes", size); + return NULL; + } + return result; +} + + char *dcm_strdup(DcmError **error, const char *str) { if (str == NULL) { @@ -65,6 +83,42 @@ char *dcm_strdup(DcmError **error, const char *str) } +char *dcm_printf_append(char *str, const char *format, ...) +{ + va_list(args); + + // find size required for new text + va_start(args, format); + ssize_t n = vsnprintf(NULL, 0, format, args); + if (n < 0) { + // some very old libcs will return -1 for truncation + return NULL; + } + va_end(args); + + // size of old text + if (str == NULL) { + str = dcm_strdup(NULL, ""); + if (str == NULL) { + return NULL; + } + } + size_t old_len = strlen(str); + + // new space, copy and render + char *new_str = dcm_realloc(NULL, str, old_len + n + 1); + if (new_str == NULL) { + free(str); + return NULL; + } + va_start(args, format); + vsnprintf(new_str + old_len, n + 1, format, args); + va_end(args); + + return new_str; +} + + void dcm_free_string_array(char **array, int n) { for (int i = 0; i < n; i++) { @@ -140,7 +194,7 @@ static void dcm_error_free(DcmError *error) } -static DcmError *dcm_error_newf(DcmErrorCode code, +static DcmError *dcm_error_newf(DcmErrorCode code, const char *summary, const char *format, va_list ap) { DcmError *error; @@ -159,7 +213,7 @@ static DcmError *dcm_error_newf(DcmErrorCode code, } -void dcm_error_set(DcmError **error, DcmErrorCode code, +void dcm_error_set(DcmError **error, DcmErrorCode code, const char *summary, const char *format, ...) { if (error && *error) { @@ -182,7 +236,7 @@ void dcm_error_set(DcmError **error, DcmErrorCode code, DcmError *local_error = dcm_error_newf(code, summary, format, ap); va_end(ap); - dcm_log_debug("%s: %s - %s", + dcm_log_debug("%s: %s - %s", dcm_error_code_str(local_error->code), local_error->summary, local_error->message); @@ -230,7 +284,32 @@ void dcm_error_log(DcmError *error) } -DcmLogLevel dcm_log_level = DCM_LOG_NOTSET; +void dcm_error_print(DcmError *error) +{ + if (error) { + fprintf(stderr, "%s: %s - %s\n", + dcm_error_code_str(error->code), + error->summary, + error->message); + } +} + + +static DcmLogLevel dcm_log_level = DCM_LOG_NOTSET; + +DcmLogLevel dcm_log_set_level(DcmLogLevel log_level) +{ + DcmLogLevel previous_log_level; + + previous_log_level = dcm_log_level; + + if ((dcm_log_level >= DCM_LOG_NOTSET) && + (dcm_log_level <= DCM_LOG_CRITICAL)) { + dcm_log_level = log_level; + } + + return previous_log_level; +} #ifndef _WIN32 @@ -243,7 +322,8 @@ static int ctime_s(char *buf, size_t size, const time_t *time) } #endif -static void dcm_logf(const char *level, const char *format, va_list args) +static void dcm_default_logf(const char *level, const char *format, + va_list args) { time_t now; time(&now); @@ -256,6 +336,27 @@ static void dcm_logf(const char *level, const char *format, va_list args) } +static DcmLogf dcm_current_logf = dcm_default_logf; + +DcmLogf dcm_log_set_logf(DcmLogf logf) +{ + DcmLogf previous_logf; + + previous_logf = dcm_current_logf; + dcm_current_logf = logf; + + return previous_logf; +} + + +static void dcm_logf(const char *level, const char *format, va_list args) +{ + if (dcm_current_logf) { + dcm_current_logf(level, format, args); + } +} + + void dcm_log_critical(const char *format, ...) { if (dcm_log_level <= DCM_LOG_CRITICAL) { diff --git a/src/getopt.c b/src/getopt.c new file mode 100644 index 0000000..774c74c --- /dev/null +++ b/src/getopt.c @@ -0,0 +1,75 @@ +/* Windows is missing getopt(), so we include a version of this function. + */ + +#include +#include + +#include + +#include "pdicom.h" + +#define BADCH (int)'#' +#define BADARG (int)':' +#define EMSG "" + +int dcm_opterr = 1, /* if error message should be printed */ + dcm_optind = 1, /* index into parent argv vector */ + dcm_optopt, /* character checked for validity */ + dcm_optreset; /* reset getopt */ +char *dcm_optarg; /* argument associated with option */ + +int dcm_getopt(int nargc, char * const nargv[], const char *ostr) +{ + static char *place = EMSG; /* option letter processing */ + const char *oli; /* option letter list index */ + + if (dcm_optreset || !*place) { /* update scanning pointer */ + dcm_optreset = 0; + if (dcm_optind >= nargc || *(place = nargv[dcm_optind]) != '-') { + place = EMSG; + return (-1); + } + if (place[1] && *++place == '-') { /* found "--" */ + ++dcm_optind; + place = EMSG; + return (-1); + } + } /* option letter okay? */ + if ((dcm_optopt = (int)*place++) == (int)':' || + !(oli = strchr(ostr, dcm_optopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means -1. + */ + if (dcm_optopt == (int)'-') + return (-1); + if (!*place) + ++dcm_optind; + if (dcm_opterr && *ostr != ':') + (void)printf("illegal option -- %c\n", dcm_optopt); + return (BADCH); + } + if (*++oli != ':') { /* don't need argument */ + dcm_optarg = NULL; + if (!*place) + ++dcm_optind; + } + else { /* need an argument */ + if (*place) /* no white space */ + dcm_optarg = place; + else if (nargc <= ++dcm_optind) { /* no arg */ + place = EMSG; + if (*ostr == ':') + return (BADARG); + if (dcm_opterr) + (void)printf("option requires an argument -- %c\n", dcm_optopt); + return (BADCH); + } + else /* white space */ + dcm_optarg = nargv[dcm_optind]; + place = EMSG; + ++dcm_optind; + } + return (dcm_optopt); /* dump back option letter */ +} + diff --git a/src/pdicom.h b/src/pdicom.h index 135dea1..e282631 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -1,6 +1,11 @@ /* Declarations not in the public API. */ +#if defined(_WIN32) && !defined(__GNUC__) +#include +typedef SSIZE_T ssize_t; +#endif + #ifndef NDEBUG # define DCM_DEBUG_ONLY( ... ) __VA_ARGS__ #else @@ -20,7 +25,7 @@ if (!(CONDITION)) { \ dcm_error_set((ERROR), DCM_ERROR_CODE_INVALID, \ "Test fail", \ - "%s:%s (%d)" \ + "%s:%s (%d)" \ __FILE__, __FUNCTION__, __LINE__); \ return RETURN_VALUE; \ } @@ -29,7 +34,7 @@ if (!(CONDITION)) { \ dcm_error_set((ERROR), DCM_ERROR_CODE_INVALID, \ "Test fail", \ - "%s:%s (%d)" \ + "%s:%s (%d)" \ __FILE__, __FUNCTION__, __LINE__); \ return; \ } @@ -38,51 +43,123 @@ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define USED(x) (void)(x) - -void *dcm_calloc(DcmError **error, size_t n, size_t size); - +#define TAG_DIMENSION_INDEX_VALUES 0x00209157 +#define TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE 0x00480200 +#define TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE 0x52009230 +#define TAG_EXTENDED_OFFSET_TABLE 0x7FE00001 +#define TAG_FLOAT_PIXEL_DATA 0x7FE00008 +#define TAG_DOUBLE_PIXEL_DATA 0x7FE00009 +#define TAG_PIXEL_DATA 0x7FE00010 +#define TAG_TRAILING_PADDING 0xFFFCFFFC +#define TAG_ITEM 0xFFFEE000 +#define TAG_ITEM_DELIM 0xFFFEE00D +#define TAG_SQ_DELIM 0xFFFEE0DD + +void *dcm_realloc(DcmError **error, void *ptr, uint64_t size); char *dcm_strdup(DcmError **error, const char *str); +char *dcm_printf_append(char *str, const char *format, ...); void dcm_free_string_array(char **strings, int n); -/* The general class of the value associated with a VR. - * - * STRING_MULTI -- one or more null-terminated strings, cannot contain backslash - * - * STRING_SINGLE -- a single null-terminated string, backslash allowed - * - * NUMERIC -- one or more binary numeric values (float etc.), other fields - * give sizeof(type) - * - * BINARY -- an uninterpreted array of bytes, length in the element header - */ -typedef enum _DcmVRClass { - DCM_CLASS_ERROR, - DCM_CLASS_STRING_MULTI, - DCM_CLASS_STRING_SINGLE, - DCM_CLASS_NUMERIC, - DCM_CLASS_BINARY, - DCM_CLASS_SEQUENCE -} DcmVRClass; - size_t dcm_dict_vr_size(DcmVR vr); -DcmVRClass dcm_dict_vr_class(DcmVR vr); uint32_t dcm_dict_vr_capacity(DcmVR vr); int dcm_dict_vr_header_length(DcmVR vr); -char **dcm_parse_character_string(DcmError **error, - char *string, uint32_t *vm); - #define DCM_SWITCH_NUMERIC(VR, OPERATION) \ switch (VR) { \ + case DCM_VR_AT: OPERATION(uint16_t); break; \ case DCM_VR_FL: OPERATION(float); break; \ - case DCM_VR_FD: OPERATION(double); break; \ - case DCM_VR_SL: OPERATION(int32_t); break; \ - case DCM_VR_SS: OPERATION(int16_t); break; \ - case DCM_VR_UL: OPERATION(uint32_t); break; \ - case DCM_VR_US: OPERATION(uint16_t); break; \ - case DCM_VR_SV: OPERATION(int64_t); break; \ - case DCM_VR_UV: OPERATION(uint64_t); break; \ - default: break; \ + case DCM_VR_FD: OPERATION(double); break; \ + case DCM_VR_SL: OPERATION(int32_t); break; \ + case DCM_VR_SS: OPERATION(int16_t); break; \ + case DCM_VR_UL: OPERATION(uint32_t); break; \ + case DCM_VR_US: OPERATION(uint16_t); break; \ + case DCM_VR_SV: OPERATION(int64_t); break; \ + case DCM_VR_UV: OPERATION(uint64_t); break; \ + default: break; \ } +DcmDataSet *dcm_sequence_steal(DcmError **error, + const DcmSequence *seq, uint32_t index); + +typedef struct _DcmParse { + bool (*dataset_begin)(DcmError **, void *client); + bool (*dataset_end)(DcmError **, void *client); + + bool (*sequence_begin)(DcmError **, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length); + bool (*sequence_end)(DcmError **, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length); + + bool (*pixeldata_begin)(DcmError **, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length); + bool (*pixeldata_end)(DcmError **, void *client); + + bool (*element_create)(DcmError **, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length); + + bool (*pixeldata_create)(DcmError **, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length); + + bool (*stop)(void *client, + uint32_t tag, + DcmVR vr, + uint32_t length); +} DcmParse; + +DCM_EXTERN +bool dcm_parse_dataset(DcmError **error, + DcmIO *io, + bool implicit, + const DcmParse *parse, + void *client); + +DCM_EXTERN +bool dcm_parse_group(DcmError **error, + DcmIO *io, + bool implicit, + const DcmParse *parse, + void *client); + +bool dcm_parse_pixeldata_offsets(DcmError **error, + DcmIO *io, + bool implicit, + int64_t *first_frame_offset, + int64_t *offsets, + int num_frames); + +struct PixelDescription { + uint16_t rows; + uint16_t columns; + uint16_t samples_per_pixel; + uint16_t bits_allocated; + uint16_t bits_stored; + uint16_t high_bit; + uint16_t pixel_representation; + uint16_t planar_configuration; + const char *photometric_interpretation; + const char *transfer_syntax_uid; +}; + +char *dcm_parse_frame(DcmError **error, + DcmIO *io, + bool implicit, + struct PixelDescription *desc, + uint32_t *length); diff --git a/tests/check_dicom.c b/tests/check_dicom.c index d4cf980..87cd22b 100644 --- a/tests/check_dicom.c +++ b/tests/check_dicom.c @@ -49,7 +49,7 @@ static size_t compute_length_of_string_value(const char *value) } -static size_t compute_length_of_string_value_multi(char **values, +static size_t compute_length_of_string_value_multi(char **values, uint32_t vm) { size_t length = 0; @@ -98,8 +98,8 @@ static char *load_file_to_memory(const char *name, long *length_out) long total_read = 0; while (total_read < length) { - size_t bytes_read = fread(result + total_read, - 1, + size_t bytes_read = fread(result + total_read, + 1, length - total_read, fp); total_read += bytes_read; @@ -117,7 +117,7 @@ START_TEST(test_error) { DcmError *error = NULL; - DcmFilehandle *filehandle = dcm_filehandle_create_from_file(&error, + DcmFilehandle *filehandle = dcm_filehandle_create_from_file(&error, "banana"); ck_assert_ptr_null(filehandle); ck_assert_ptr_nonnull(error); @@ -135,10 +135,13 @@ END_TEST START_TEST(test_log_level) { - ck_assert_int_eq(dcm_log_level, DCM_LOG_NOTSET); + DcmLogLevel previous_log_level; - dcm_log_level = DCM_LOG_INFO; - ck_assert_int_eq(dcm_log_level, DCM_LOG_INFO); + previous_log_level = dcm_log_set_level(DCM_LOG_INFO); + ck_assert_int_eq(previous_log_level, DCM_LOG_NOTSET); + + previous_log_level = dcm_log_set_level(DCM_LOG_INFO); + ck_assert_int_eq(previous_log_level, DCM_LOG_INFO); } END_TEST @@ -502,8 +505,7 @@ START_TEST(test_element_US_multivalue) uint32_t vm = sizeof(value) / sizeof(value[0]); DcmElement *element = dcm_element_create(NULL, tag, DCM_VR_US); - (void) dcm_element_set_value_numeric_multi(NULL, - element, (int*) value, vm, false); + (void) dcm_element_set_value_numeric_multi(NULL, element, value, vm, false); ck_assert_int_eq(dcm_element_get_tag(element), tag); ck_assert_int_eq(dcm_element_get_vr(element), DCM_VR_US); @@ -530,8 +532,7 @@ START_TEST(test_element_US_multivalue_empty) uint32_t vm = sizeof(value) / sizeof(value[0]); DcmElement *element = dcm_element_create(NULL, tag, DCM_VR_US); - (void) dcm_element_set_value_numeric_multi(NULL, - element, (int*) &value, vm, false); + (void) dcm_element_set_value_numeric_multi(NULL, element, &value, vm, false); ck_assert_int_eq(dcm_element_get_tag(element), tag); ck_assert_int_eq(dcm_element_get_vr(element), DCM_VR_US); @@ -644,12 +645,12 @@ START_TEST(test_file_sm_image_file_meta) DcmElement *element; char *file_path = fixture_path("data/test_files/sm_image.dcm"); - DcmFilehandle *filehandle = + DcmFilehandle *filehandle = dcm_filehandle_create_from_file(NULL, file_path); free(file_path); ck_assert_ptr_nonnull(filehandle); - DcmDataSet *meta = dcm_filehandle_read_file_meta(NULL, filehandle); + const DcmDataSet *meta = dcm_filehandle_get_file_meta(NULL, filehandle); ck_assert_ptr_nonnull(meta); // Transfer Syntax UID @@ -664,7 +665,6 @@ START_TEST(test_file_sm_image_file_meta) dcm_dataset_print(meta, 0); - dcm_dataset_destroy(meta); dcm_filehandle_destroy(filehandle); } END_TEST @@ -674,12 +674,13 @@ START_TEST(test_file_sm_image_metadata) { char *file_path = fixture_path("data/test_files/sm_image.dcm"); - DcmFilehandle *filehandle = + DcmFilehandle *filehandle = dcm_filehandle_create_from_file(NULL, file_path); free(file_path); ck_assert_ptr_nonnull(filehandle); - DcmDataSet *metadata = dcm_filehandle_read_metadata(NULL, filehandle); + const DcmDataSet *metadata = + dcm_filehandle_get_metadata_subset(NULL, filehandle); ck_assert_ptr_nonnull(metadata); // SOP Class UID @@ -690,7 +691,6 @@ START_TEST(test_file_sm_image_metadata) dcm_dataset_print(metadata, 0); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); } END_TEST @@ -701,27 +701,19 @@ START_TEST(test_file_sm_image_frame) const uint32_t frame_number = 1; char *file_path = fixture_path("data/test_files/sm_image.dcm"); - DcmFilehandle *filehandle = + DcmFilehandle *filehandle = dcm_filehandle_create_from_file(NULL, file_path); free(file_path); ck_assert_ptr_nonnull(filehandle); - DcmDataSet *metadata = dcm_filehandle_read_metadata(NULL, filehandle); + const DcmDataSet *metadata = + dcm_filehandle_get_metadata_subset(NULL, filehandle); ck_assert_ptr_nonnull(metadata); - dcm_log_level = DCM_LOG_INFO; - DcmError *error = NULL; - DcmBOT *bot = dcm_filehandle_build_bot(&error, filehandle, metadata); - if (!bot) { - dcm_error_log(error); - dcm_error_clear(&error); - abort(); - } - ck_assert_ptr_nonnull(bot); - ck_assert_uint_eq(dcm_bot_get_num_frames(bot), 25); + ck_assert_int_ne(dcm_filehandle_prepare_read_frame(NULL, filehandle), 0); - DcmFrame *frame = dcm_filehandle_read_frame(NULL, - filehandle, metadata, bot, + DcmFrame *frame = dcm_filehandle_read_frame(NULL, + filehandle, frame_number); ck_assert_uint_eq(dcm_frame_get_number(frame), frame_number); ck_assert_uint_eq(dcm_frame_get_rows(frame), 10); @@ -737,8 +729,6 @@ START_TEST(test_file_sm_image_frame) "1.2.840.10008.1.2.1"); dcm_frame_destroy(frame); - dcm_bot_destroy(bot); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); } END_TEST @@ -753,11 +743,11 @@ START_TEST(test_file_sm_image_file_meta_memory) char *memory = load_file_to_memory("data/test_files/sm_image.dcm", &length); ck_assert_ptr_nonnull(memory); - DcmFilehandle *filehandle = + DcmFilehandle *filehandle = dcm_filehandle_create_from_memory(NULL, memory, length); ck_assert_ptr_nonnull(filehandle); - DcmDataSet *meta = dcm_filehandle_read_file_meta(NULL, filehandle); + const DcmDataSet *meta = dcm_filehandle_get_file_meta(NULL, filehandle); // Transfer Syntax UID element = dcm_dataset_get(NULL, meta, 0x00020010); @@ -771,7 +761,6 @@ START_TEST(test_file_sm_image_file_meta_memory) dcm_dataset_print(meta, 0); - dcm_dataset_destroy(meta); dcm_filehandle_destroy(filehandle); free(memory); } @@ -859,6 +848,8 @@ static Suite *create_file_suite(void) int main(void) { + dcm_init(); + SRunner *runner = srunner_create(create_main_suite()); srunner_add_suite(runner, create_data_suite()); srunner_add_suite(runner, create_file_suite()); diff --git a/tools/dcm-dump.c b/tools/dcm-dump.c index 4a48af6..2678deb 100644 --- a/tools/dcm-dump.c +++ b/tools/dcm-dump.c @@ -7,78 +7,57 @@ #include -static const char usage[] = "usage: dcm-dump [-v] [-V] [-h] FILE_PATH\n"; +static const char usage[] = "usage: dcm-dump [-v] [-V] [-h] FILE_PATH ..."; -int main(int argc, char *argv[]) +int main(int argc, char *argv[]) { - int i; - const char *file_path = NULL; - DcmError *error = NULL; - DcmDataSet *metadata = NULL; - DcmDataSet *meta = NULL; - DcmFilehandle *filehandle = NULL; + int i, c; - dcm_log_level = DCM_LOG_ERROR; + dcm_init(); - for (i = 1; i < argc && argv[i][0] == '-'; i++) { - switch (argv[i][1]) { + while ((c = dcm_getopt(argc, argv, "h?Vv")) != -1) { + switch (c) { case 'h': + case '?': printf("%s\n", usage); return EXIT_SUCCESS; - case 'V': + + case 'v': printf("%s\n", dcm_get_version()); return EXIT_SUCCESS; - case 'v': - dcm_log_level = DCM_LOG_INFO; + + case 'V': + dcm_log_set_level(DCM_LOG_INFO); break; + + case '#': default: - fprintf(stderr, "%s\n", usage); return EXIT_FAILURE; } } - if ((i + 1) != argc) { - fprintf(stderr, "%s\n", usage); - return EXIT_FAILURE; - } - file_path = argv[i]; - - dcm_log_info("Read file '%s'", file_path); - filehandle = dcm_filehandle_create_from_file(&error, file_path); - if (filehandle == NULL) { - dcm_error_log(error); - dcm_error_clear(&error); - return EXIT_FAILURE; - } - dcm_log_info("Read File Meta Information"); - meta = dcm_filehandle_read_file_meta(&error, filehandle); - if (meta == NULL) { - dcm_error_log(error); - dcm_error_clear(&error); - dcm_filehandle_destroy(filehandle); - return EXIT_FAILURE; - } - - printf("===File Meta Information===\n"); - dcm_dataset_print(meta, 0); + for (i = dcm_optind; i < argc; i++) { + DcmError *error = NULL; + DcmFilehandle *filehandle = NULL; + + dcm_log_info("Read file '%s'", argv[i]); + filehandle = dcm_filehandle_create_from_file(&error, argv[i]); + if (filehandle == NULL) { + dcm_error_print(error); + dcm_error_clear(&error); + return EXIT_FAILURE; + } + + if (!dcm_filehandle_print(&error, filehandle)) { + dcm_error_print(error); + dcm_error_clear(&error); + dcm_filehandle_destroy(filehandle); + return EXIT_FAILURE; + } - dcm_log_info("Read metadata"); - metadata = dcm_filehandle_read_metadata(&error, filehandle); - if (metadata == NULL) { - dcm_error_log(error); - dcm_error_clear(&error); dcm_filehandle_destroy(filehandle); - dcm_dataset_destroy(meta); - return EXIT_FAILURE; } - printf("===Dataset===\n"); - dcm_dataset_print(metadata, 0); - - dcm_filehandle_destroy(filehandle); - dcm_dataset_destroy(meta); - dcm_dataset_destroy(metadata); - return EXIT_SUCCESS; } diff --git a/tools/dcm-getframe.c b/tools/dcm-getframe.c index 375aea1..d43628b 100644 --- a/tools/dcm-getframe.c +++ b/tools/dcm-getframe.c @@ -11,104 +11,67 @@ static const char usage[] = "usage: " - "dcm-getframe [-v] [-V] [-h] [-o OUTPUT-FILE] FILE_PATH FRAME_NUMBER\n"; + "dcm-getframe [-v] [-V] [-h] [-o OUTPUT-FILE] FILE_PATH FRAME_NUMBER"; -int main(int argc, char *argv[]) +int main(int argc, char *argv[]) { - DcmError *error = NULL; - char *output_filehandle = NULL; - int i; + char *output_file = NULL; + + int c; - dcm_log_level = DCM_LOG_ERROR; + dcm_init(); - for (i = 1; i < argc && argv[i][0] == '-'; i++) { - switch (argv[i][1]) { + while ((c = dcm_getopt(argc, argv, "h?Vvo:")) != -1) { + switch (c) { case 'h': + case '?': printf("%s\n", usage); return EXIT_SUCCESS; - case 'V': + + case 'v': printf("%s\n", dcm_get_version()); return EXIT_SUCCESS; - case 'v': - dcm_log_level = DCM_LOG_INFO; + + case 'V': + dcm_log_set_level(DCM_LOG_INFO); break; + case 'o': - output_filehandle = argv[i + 1]; - i += 1; + output_file = dcm_optarg; break; + + case '#': default: - fprintf(stderr, "%s\n", usage); return EXIT_FAILURE; } } - if ((i + 2) != argc) { + DcmError *error = NULL; + + if (dcm_optind + 2 != argc) { fprintf(stderr, "%s\n", usage); return EXIT_FAILURE; } - const char *file_path = argv[i]; + const char *input_file = argv[dcm_optind]; + int frame_number = atoi(argv[dcm_optind + 1]); - uint32_t frame_number = atoi(argv[i + 1]); - - dcm_log_info("Read filehandle '%s'", file_path); - DcmFilehandle *filehandle = dcm_filehandle_create_from_file(&error, - file_path); + dcm_log_info("Read filehandle '%s'", input_file); + DcmFilehandle *filehandle = dcm_filehandle_create_from_file(&error, + input_file); if (filehandle == NULL) { - dcm_error_log(error); - dcm_error_clear(&error); - return EXIT_FAILURE; - } - - dcm_log_info("Read metadata"); - DcmDataSet *metadata = dcm_filehandle_read_metadata(&error, filehandle); - if (metadata == NULL) { - dcm_error_log(error); - dcm_error_clear(&error); - dcm_filehandle_destroy(filehandle); - return EXIT_FAILURE; - } - - dcm_log_info("Read BOT"); - DcmBOT *bot = dcm_filehandle_read_bot(&error, filehandle, metadata); - if (bot == NULL) { - /* Try to build the BOT instead. - */ + dcm_error_print(error); dcm_error_clear(&error); - dcm_log_info("Build BOT"); - bot = dcm_filehandle_build_bot(&error, filehandle, metadata); - } - if (bot == NULL) { - dcm_error_log(error); - dcm_error_clear(&error); - dcm_dataset_destroy(metadata); - dcm_filehandle_destroy(filehandle); - return EXIT_FAILURE; - } - - uint32_t num_frames = dcm_bot_get_num_frames(bot); - if (frame_number < 1 || frame_number > num_frames) { - dcm_error_set(&error, DCM_ERROR_CODE_INVALID, - "Bad frame number", - "Frame number must be between 1 and %d", - num_frames); - dcm_error_log(error); - dcm_error_clear(&error); - dcm_bot_destroy(bot); - dcm_dataset_destroy(metadata); - dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; } dcm_log_info("Read frame %u", frame_number); - DcmFrame *frame = dcm_filehandle_read_frame(&error, - filehandle, metadata, - bot, frame_number); + DcmFrame *frame = dcm_filehandle_read_frame(&error, + filehandle, + frame_number); if (frame == NULL) { - dcm_error_log(error); + dcm_error_print(error); dcm_error_clear(&error); - dcm_bot_destroy(bot); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; } @@ -120,27 +83,32 @@ int main(int argc, char *argv[]) dcm_log_info("length = %u bytes", frame_length); dcm_log_info("rows = %u", dcm_frame_get_rows(frame)); dcm_log_info("columns = %u", dcm_frame_get_columns(frame)); - dcm_log_info("samples per pixel = %u", + dcm_log_info("samples per pixel = %u", dcm_frame_get_samples_per_pixel(frame)); dcm_log_info("bits allocated = %u", dcm_frame_get_bits_allocated(frame)); dcm_log_info("bits stored = %u", dcm_frame_get_bits_stored(frame)); dcm_log_info("high bit = %u", dcm_frame_get_high_bit(frame)); - dcm_log_info("pixel representation = %u", + dcm_log_info("pixel representation = %u", dcm_frame_get_pixel_representation(frame)); - dcm_log_info("planar configuration = %u", + dcm_log_info("planar configuration = %u", dcm_frame_get_planar_configuration(frame)); - dcm_log_info("photometric interpretation = %s", + dcm_log_info("photometric interpretation = %s", dcm_frame_get_photometric_interpretation(frame)); - dcm_log_info("transfer syntax uid = %s", + dcm_log_info("transfer syntax uid = %s", dcm_frame_get_transfer_syntax_uid(frame)); FILE *output_fp; - if (output_filehandle != NULL) { - output_fp = fopen(output_filehandle, "wb"); + if (output_file != NULL) { + output_fp = fopen(output_file, "wb"); if (output_fp == NULL) { dcm_error_set(&error, DCM_ERROR_CODE_INVALID, "Bad output filehandle name", - "Unable to open %s for output", output_filehandle); + "Unable to open %s for output", output_file); + dcm_error_print(error); + dcm_error_clear(&error); + dcm_frame_destroy(frame); + dcm_filehandle_destroy(filehandle); + return EXIT_FAILURE; } } else @@ -148,14 +116,12 @@ int main(int argc, char *argv[]) fwrite(frame_value, 1, frame_length, output_fp); - if (output_filehandle != NULL) { + if (output_file != NULL) { fclose(output_fp); output_fp = NULL; } dcm_frame_destroy(frame); - dcm_bot_destroy(bot); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); return EXIT_SUCCESS;