From dd30f137ec7e9e1dda002e7df30e02cc421314c0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 16 Dec 2022 17:40:04 +0000 Subject: [PATCH 01/82] start revising log/error API --- .editorconfig | 15 ++++++++++++ include/dicom.h | 60 +++++++++++++++++++++++++++++++++------------ src/dicom.c | 54 ++++++++++++++++++++++++++++++++++++++-- tests/check_dicom.c | 9 ++++--- tools/dcm-dump.c | 7 +++--- 5 files changed, 122 insertions(+), 23 deletions(-) create mode 100644 .editorconfig 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/include/dicom.h b/include/dicom.h index f8a40be..afe0ea3 100644 --- a/include/dicom.h +++ b/include/dicom.h @@ -32,17 +32,6 @@ typedef SSIZE_T ssize_t; #define DCM_ARRAY_ZEROS(N, TYPE) \ (TYPE *) dcm_calloc(N, sizeof(TYPE)) -/** - * Allocate and initialize a block of memory. - * - * :param n: Number of items. - * :param size: Number of bytes per item. - * - * :return: Pointer to allocated memory. - */ -DCM_EXTERN -void *dcm_calloc(size_t n, size_t size); - /** * Maximum number of characters in values with Value Representation AE. @@ -159,6 +148,25 @@ typedef struct _DcmFrame DcmFrame; */ typedef struct _DcmBOT DcmBOT; +/** + * Log level + */ +typedef enum _DcmLogLevel DcmLogLevel; + +/** + * Log function. See dcm_log_set_logf(). + */ +typedef void (*DcmLogf)(const char *level, const char *format, va_list args); + +/** + * An error object + */ +typedef struct _DcmError { + char *domain; + int code; + char *message; +} DcmError; + /** * Enumeration of log levels @@ -179,15 +187,37 @@ enum _DcmLogLevel { }; /** - * Log level + * Allocate and initialize a block of memory. + * + * :param n: Number of items. + * :param size: Number of bytes per item. + * + * :return: Pointer to allocated memory. */ -typedef enum _DcmLogLevel DcmLogLevel; +DCM_EXTERN +void *dcm_calloc(size_t n, size_t size); /** - * Global variable to set log level. + * Set the log level. + * + * :param log_level: New log level. + * :return: previous log level + */ +DCM_EXTERN +DcmLogLevel dcm_log_set_level(DcmLogLevel log_level); + +/** + * 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. diff --git a/src/dicom.c b/src/dicom.c index db26965..89b619a 100644 --- a/src/dicom.c +++ b/src/dicom.c @@ -31,7 +31,21 @@ const char *dcm_get_version(void) } -DcmLogLevel dcm_log_level = DCM_LOG_NOTSET; +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 @@ -43,7 +57,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); @@ -56,6 +71,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) { @@ -109,3 +145,17 @@ void dcm_log_debug(const char *format, ...) va_end(args); } } + + +DcmError *dcm_error_newf(const char *domain, int code, const char *format, + va_list args) { + DcmError *error; + + error = DCM_NEW (DcmError); + if (error == NULL) { + } +} + + +DcmError *dcm_error_new(const char *domain, int code, const char *format, ...) { +} diff --git a/tests/check_dicom.c b/tests/check_dicom.c index 427a9b3..84d679f 100644 --- a/tests/check_dicom.c +++ b/tests/check_dicom.c @@ -47,10 +47,13 @@ static size_t compute_length_of_string_value_multi(char **values, uint32_t vm) 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 diff --git a/tools/dcm-dump.c b/tools/dcm-dump.c index 416704a..0cb4fd7 100644 --- a/tools/dcm-dump.c +++ b/tools/dcm-dump.c @@ -17,8 +17,9 @@ int main(int argc, char *argv[]) { DcmDataSet *metadata = NULL; DcmDataSet *file_meta = NULL; DcmFile *file = NULL; + DcmError *error = NULL; - dcm_log_level = DCM_LOG_ERROR; + dcm_log_set_level(DCM_LOG_ERROR); for (i = 1; i < argc && argv[i][0] == '-'; i++) { @@ -30,7 +31,7 @@ int main(int argc, char *argv[]) { printf("%s\n", dcm_get_version()); return EXIT_SUCCESS; case 'v': - dcm_log_level = DCM_LOG_INFO; + dcm_log_set_level(DCM_LOG_INFO); break; default: fprintf(stderr, "%s\n", usage); @@ -45,7 +46,7 @@ int main(int argc, char *argv[]) { file_path = argv[i]; dcm_log_info("Read file '%s'", file_path); - file = dcm_file_create(file_path, 'r'); + file = dcm_file_create(&error, file_path, 'r'); if (file == NULL) { dcm_log_error("Reading file '%s' failed.", file_path); return EXIT_FAILURE; From ab7fe006b3b096875d8d2f93f46789067c2e21c9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 24 Apr 2023 17:21:59 +0100 Subject: [PATCH 02/82] allow for optional FileMetaInformationVersion Some DICOMS (eg. Leica) don't have this element. --- TODO | 1 + src/dicom-file.c | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/TODO b/TODO index 6b798c8..ccd93db 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,7 @@ - need to be able to set a stop-at function on filehandles +- split parser from AST builder # bot support diff --git a/src/dicom-file.c b/src/dicom-file.c index 1596144..58b54f3 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -745,15 +745,6 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, 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; - } - - dcm_element_destroy(element); - while (position < group_length) { uint32_t length; element = read_element_header(error, @@ -770,8 +761,20 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, } if (!read_element_body(error, element, filehandle, - length, &position, implicit) || - !dcm_dataset_insert(error, file_meta, element)) { + length, &position, implicit)) { + dcm_element_destroy(element); + dcm_dataset_destroy(file_meta); + return NULL; + } + + // many DICOMs have a FileMetaInformationVersion element, but + // not all ... ignore the version number if present + if (dcm_element_get_tag(element) == 0x00020001) { + dcm_element_destroy(element); + continue; + } + + if (!dcm_dataset_insert(error, file_meta, element)) { dcm_element_destroy(element); dcm_dataset_destroy(file_meta); return NULL; From a7cfd9c34dc044621005674c3c9561427c90966c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 1 May 2023 14:02:39 +0100 Subject: [PATCH 03/82] almost done --- TODO | 2 + src/dicom-file.c | 12 +- src/dicom-parse.c | 1768 +++++++++++++++++++++++++++++++++++++++++++++ src/pdicom.h | 27 + 4 files changed, 1803 insertions(+), 6 deletions(-) create mode 100644 src/dicom-parse.c diff --git a/TODO b/TODO index ccd93db..ebe089d 100644 --- a/TODO +++ b/TODO @@ -4,6 +4,8 @@ - split parser from AST builder +- add get_vr_max_size() and use a static buffer to read value when possible + # bot support - need more tests and test images diff --git a/src/dicom-file.c b/src/dicom-file.c index 58b54f3..3ee7ee6 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -412,10 +412,10 @@ static DcmElement *read_element_header(DcmError **error, // fwd ref this -static DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit); +static DcmElement *parse_element(DcmError **error, + DcmFilehandle *filehandle, + int64_t *position, + bool implicit); static bool read_element_sequence(DcmError **error, DcmFilehandle *filehandle, @@ -430,8 +430,8 @@ static bool read_element_sequence(DcmError **error, 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)) { + if (!read_tag(state->error, state->filehandle, &item_tag, position) || + !read_uint32(state->error, state->filehandle, &item_length, position)) { return false; } diff --git a/src/dicom-parse.c b/src/dicom-parse.c new file mode 100644 index 0000000..c91723d --- /dev/null +++ b/src/dicom-parse.c @@ -0,0 +1,1768 @@ +/* + * 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" + + +#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; +}; + + +struct _DcmFilehandle { + const DcmIO *io; + void *fp; + DcmDataSet *meta; + int64_t offset; + char *transfer_syntax_uid; + int64_t pixel_data_offset; + uint64_t *extended_offset_table; + bool byteswap; +}; + + +/* 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; +} + + +DcmFilehandle *dcm_filehandle_create(DcmError **error, + const DcmIO *io, + void *client) +{ + DcmFilehandle *filehandle = DCM_NEW(error, DcmFilehandle); + if (filehandle == NULL) { + return NULL; + } + + 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->fp = filehandle->io->open(error, client); + if (filehandle->fp == NULL) { + dcm_filehandle_destroy(filehandle); + return NULL; + } + + return filehandle; +} + + +void dcm_filehandle_destroy(DcmFilehandle *filehandle) +{ + if (filehandle) { + if (filehandle->transfer_syntax_uid) { + free(filehandle->transfer_syntax_uid); + } + + if (filehandle->fp) { + (void)filehandle->io->close(NULL, filehandle->fp); + filehandle->fp = NULL; + } + + free(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); + if (bytes_read < 0) { + return bytes_read; + } + + *position += bytes_read; + + return bytes_read; +} + + +static bool dcm_require(DcmError **error, DcmFilehandle *filehandle, + char *buffer, int64_t length, int64_t *position) +{ + while (length > 0) { + int64_t bytes_read = dcm_read(error, + filehandle, buffer, length, position); + + if (bytes_read < 0) { + return false; + } else if (bytes_read == 0) { + dcm_error_set(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_seekset(DcmError **error, + DcmFilehandle *filehandle, int64_t offset) +{ + return filehandle->io->seek(error, filehandle->fp, offset, SEEK_SET) >= 0; +} + + +static bool dcm_seekcur(DcmError **error, DcmFilehandle *filehandle, + int64_t offset, int64_t *position) +{ + int64_t new_offset = filehandle->io->seek(error, + filehandle->fp, offset, SEEK_CUR); + if (new_offset < 0) { + return false; + } + + *position += offset; + + return true; +} + + +static bool dcm_offset(DcmError **error, + DcmFilehandle *filehandle, int64_t *offset) +{ + *offset = filehandle->io->seek(error, filehandle->fp, 0, SEEK_CUR); + if (*offset < 0) { + return false; + } + + return true; +} + + +static bool dcm_is_eof(DcmFilehandle *filehandle) +{ + int64_t position = 0; + bool eof = true; + + 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); + } + + return eof; +} + + +static void byteswap(char *data, size_t length, size_t size) +{ + assert(length >= size); + + if (size > 1) { + assert(length % size == 0); + assert(size % 2 == 0); + + size_t half_size = size / 2; + + 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; + } + } + } +} + + +static bool read_uint16(DcmError **error, DcmFilehandle *filehandle, + uint16_t *value, int64_t *position) +{ + union { + uint16_t i; + char c[2]; + } buffer; + + if (!dcm_require(error, filehandle, buffer.c, 2, position)) { + return false; + } + + if (filehandle->byteswap) { + byteswap(buffer.c, 2, 2); + } + + *value = buffer.i; + + return true; +} + + +static bool read_uint32(DcmError **error, DcmFilehandle *filehandle, + uint32_t *value, int64_t *position) +{ + union { + uint32_t i; + char c[4]; + } buffer; + + if (!dcm_require(error, filehandle, buffer.c, 4, position)) { + return false; + } + + if (filehandle->byteswap) { + byteswap(buffer.c, 4, 4); + } + + *value = buffer.i; + + return true; +} + + +static bool read_tag(DcmError **error, DcmFilehandle *filehandle, + uint32_t *value, int64_t *position) +{ + uint16_t group, elem; + + if (!read_uint16(error, filehandle, &group, position) || + !read_uint16(error, filehandle, &elem, position)) { + return false; + } + + *value = ((uint32_t)group << 16) | elem; + + return true; +} + + +static bool read_iheader(DcmError **error, DcmFilehandle *filehandle, + uint32_t *item_tag, uint32_t *item_length, int64_t *position) +{ + if (!read_tag(error, filehandle, item_tag, position) || + !read_uint32(error, filehandle, item_length, position)) { + return false; + } + + return true; +} + + +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; +} + + +static DcmElement *read_element_header(DcmError **error, + DcmFilehandle *filehandle, + uint32_t *length, + int64_t *position, + bool implicit) +{ + uint32_t tag; + if (!read_tag(error, filehandle, &tag, position)) { + 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); + } + + if (!read_uint32(error, filehandle, length, position)) { + return NULL; + } + } else { + // Value Representation + char vr_str[3]; + if (!dcm_require(error, filehandle, vr_str, 2, position)) { + 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); + 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 (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; + } + } + } + + return dcm_element_create(error, tag, vr); +} + + +// fwd ref this +static DcmElement *read_element(DcmError **error, + DcmFilehandle *filehandle, + int64_t *position, + bool implicit); + +static bool read_element_sequence(DcmError **error, + DcmFilehandle *filehandle, + DcmSequence *sequence, + uint32_t length, + int64_t *position, + bool implicit) +{ + 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; + } + + 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(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 (length == 0xFFFFFFFF) { + dcm_log_debug("Item #%d has undefined length.", index); + } else { + dcm_log_debug("Item #%d has defined length %d.", + index, item_length); + } + + DcmDataSet *dataset = dcm_dataset_create(error); + if (dataset == NULL) { + 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; + } + + // back to start of element + if (!dcm_seekcur(error, filehandle, -4, &item_position)) { + dcm_dataset_destroy(dataset); + return false; + } + + DcmElement *element = read_element(error, filehandle, + &item_position, implicit); + if (element == NULL) { + dcm_dataset_destroy(dataset); + return false; + } + + if (!dcm_dataset_insert(error, dataset, element)) { + dcm_dataset_destroy(dataset); + dcm_element_destroy(element); + return false; + } + } + + seq_position += item_position; + + if (!dcm_sequence_append(error, sequence, dataset)) { + dcm_dataset_destroy(dataset); + } + + index += 1; + } + + *position += seq_position; + + return true; +} + + +static bool read_element_body(DcmError **error, + DcmElement *element, + DcmFilehandle *filehandle, + uint32_t length, + int64_t *position, + bool implicit) +{ + 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; + + dcm_log_debug("Read Data Element body '%08X'", tag); + + 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; + + 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; + + char *values = DCM_MALLOC(error, length); + if (values == NULL) { + return false; + } + + if (!dcm_require(error, filehandle, values, length, position)) { + free(values); + return false; + } + + if (filehandle->byteswap) { + byteswap(values, length, size); + } + + if( !dcm_element_set_value_numeric_multi(error, + element, + (int *) values, + vm, + true)) { + free(values); + return false; + } + + break; + + case DCM_CLASS_BINARY: + value = DCM_MALLOC(error, length); + if (value == NULL) { + return false; + } + + if (!dcm_require(error, filehandle, value, length, position)) { + free(value); + return false; + } + + if( !dcm_element_set_value_binary(error, element, + value, length, true)) { + free(value); + return false; + } + + break; + + case DCM_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); + } + + DcmSequence *seq = dcm_sequence_create(error); + if (seq == NULL) { + return NULL; + } + + 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; + } + + return true; +} + + +DcmElement *read_element(DcmError **error, + DcmFilehandle *filehandle, + int64_t *position, + bool implicit) +{ + DcmElement *element; + + uint32_t length; + element = read_element_header(error, + filehandle, &length, position, implicit); + if (element == NULL) { + return NULL; + } + + if (!read_element_body(error, element, filehandle, + length, position, implicit)) { + dcm_element_destroy(element); + return NULL; + } + + return element; +} + + +DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, + DcmFilehandle *filehandle) +{ + const bool implicit = false; + + int64_t position; + uint16_t group_number; + DcmElement *element; + + DcmDataSet *file_meta = dcm_dataset_create(error); + if (file_meta == NULL) { + return NULL; + } + + position = 0; + + // 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'; + + // 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; + } + + position = 0; + + // File Meta Information Group Length + element = read_element(error, filehandle, &position, implicit); + if (element == NULL) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + 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; + } + + 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; + } + + group_number = dcm_element_get_group_number(element); + if (group_number != 0x0002) { + dcm_element_destroy(element); + break; + } + + if (!read_element_body(error, element, filehandle, + length, &position, implicit)) { + dcm_element_destroy(element); + dcm_dataset_destroy(file_meta); + return NULL; + } + + // many DICOMs have a FileMetaInformationVersion element, but + // not all ... ignore the version number if present + if (dcm_element_get_tag(element) == 0x00020001) { + dcm_element_destroy(element); + continue; + } + + if (!dcm_dataset_insert(error, file_meta, element)) { + dcm_element_destroy(element); + dcm_dataset_destroy(file_meta); + return NULL; + } + } + + if (!dcm_offset(error, filehandle, &filehandle->offset)) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + 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; + } + + 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; + } + + dcm_dataset_lock(file_meta); + + return file_meta; +} + + +DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, + DcmFilehandle *filehandle) +{ + bool implicit; + + if (filehandle->offset == 0) { + DcmDataSet *meta = dcm_filehandle_read_file_meta(error, filehandle); + if (meta== NULL) { + return NULL; + } + 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; + } + } + + DcmDataSet *dataset = dcm_dataset_create(error); + if (dataset == NULL) { + return NULL; + } + + int64_t position = 0; + for (;;) { + if (dcm_is_eof(filehandle)) { + dcm_log_info("Stop reading Data Set. Reached end of filehandle."); + break; + } + + uint32_t length; + DcmElement *element = read_element_header(error, + filehandle, + &length, + &position, + implicit); + if (element == NULL) { + dcm_dataset_destroy(dataset); + return NULL; + } + 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 (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; + } + + } 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; + } + } + + if (!dcm_offset(error, + filehandle, &filehandle->pixel_data_offset)) { + dcm_dataset_destroy(dataset); + return NULL; + } + + 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; + } + + 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; + } + } + + 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) +{ + uint64_t value; + + dcm_log_debug("Reading Basic Offset Table."); + + 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); + return NULL; + } + + uint32_t num_frames; + if (!get_num_frames(error, metadata, &num_frames)) { + return NULL; + } + + 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_seekset(error, filehandle, filehandle->pixel_data_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"); + return NULL; + } + + // The header of the BOT Item + if (!read_iheader(error, filehandle, &tag, &length, &position)) { + return NULL; + } + 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 NULL; + } + + ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); + if (offsets == NULL) { + 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) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "No Basic Offset Table, " + "and no Extended Offset Table"); + free(offsets); + return NULL; + } + + dcm_log_info("Found Extended Offset Table."); + + 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; + } + + // FIXME is this correct? + first_frame_offset = position; + } + + return dcm_bot_create(error, offsets, num_frames, first_frame_offset); +} + + +static bool set_pixel_description(DcmError **error, + struct PixelDescription *desc, + const DcmDataSet *metadata) +{ + DcmElement *element; + int64_t value; + const char *string; + + element = dcm_dataset_get(error, metadata, 0x00280010); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->rows = value; + + element = dcm_dataset_get(error, metadata, 0x00280011); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->columns = value; + + 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; + + 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; + + 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; + + 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; + + return true; +} + + +DcmBOT *dcm_filehandle_build_bot(DcmError **error, + DcmFilehandle *filehandle, + DcmDataSet *metadata) +{ + uint64_t i; + + dcm_log_debug("Building Basic Offset Table."); + + uint32_t num_frames; + if (!get_num_frames(error, metadata, &num_frames)) { + return NULL; + } + + 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_seekset(error, filehandle, filehandle->pixel_data_offset)) { + return NULL; + } + + // we measure offsets from this point + int64_t position = 0; + + 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; + } + + ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); + if (offsets == NULL) { + return NULL; + } + + 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; + } + + 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; + } + + // Move filehandlepointer to the first byte of first Frame item + if (!dcm_seekcur(error, filehandle, length, &position)) { + free(offsets); + } + + // and that's the offset to the first frame + first_frame_offset = position; + + // now measure positions from the start of the first frame + position = 0; + + i = 0; + while (true) { + if (!read_iheader(error, filehandle, &tag, &length, &position)) { + free(offsets); + return NULL; + } + + if (tag == TAG_SQ_DELIM) { + break; + } + + 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; + } + + if (dcm_is_eof(filehandle)) { + break; + } + + // step back to the start of the item for this frame + offsets[i] = position - 8; + + if (!dcm_seekcur(error, filehandle, length, &position)) { + free(offsets); + return NULL; + } + + i += 1; + } + + 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; + } + + return dcm_bot_create(error, offsets, num_frames, first_frame_offset); +} + + +DcmFrame *dcm_filehandle_read_frame(DcmError **error, + DcmFilehandle *filehandle, + DcmDataSet *metadata, + DcmBOT *bot, + uint32_t number) +{ + uint32_t length; + + 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; + } + + 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; + } + + struct PixelDescription desc; + if (!set_pixel_description(error, &desc, metadata)) { + return NULL; + } + + 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; + } + + 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; + } + + char *value = DCM_MALLOC(error, length); + if (value == NULL) { + return NULL; + } + if (!dcm_require(error, filehandle, value, length, &position)) { + free(value); + return NULL; + } + + 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); +} + + + + +/* The size of the buffer we use for reading smaller element values. This is + * large enough for most VRs. + */ +#define INPUT_BUFFER_SIZE (10240) + + +typedef struct _DcmParseState { + DcmError **error; + DcmFilehandle *filehandle; + DcmParse *parse; + void *client; + + DcmDataSet *meta; + int64_t offset; + int64_t pixel_data_offset; + uint64_t *extended_offset_table; + bool byteswap; +} DcmParseState; + + +static bool parse_element_header(DcmParseState *state, + bool implicit, + uint32_t *tag, + DcmVR *vr, + uint32_t *length, + int64_t *position) +{ + if (!read_tag(error, state->filehandle, tag, position)) { + return false; + } + + 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(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(error, filehandle, length, position)) { + return false; + } + } else { + // Value Representation + char vr_str[3]; + if (!dcm_require(error, filehandle, 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(error, filehandle, &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(error, filehandle, &reserved, position) || + !read_uint32(error, filehandle, 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_length, + int64_t *position, + bool implicit) +{ + int index = 0; + int64_t seq_position = 0; + while (seq_position < seq_length) { + dcm_log_debug("Read Item #%d.", index); + uint32_t item_tag; + uint32_t item_length; + if (!read_tag(state->error, state->filehandle, &item_tag, position) || + !read_uint32(state->error, state->filehandle, &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_start(state->error, state->client)) { + return false; + } + + int64_t item_position = 0; + while (item_position < item_length) { + // peek the next tag + if (!read_tag(state->error, state->filehandle, &item_tag, &item_position)) { + 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(state->error, state->filehandle, 4, &item_position)) { + return false; + } + + break; + } + + // back to start of element + if (!dcm_seekcur(state->error, state->filehandle, -4, &item_position)) { + return false; + } + + DcmElement *element = parse_element(state, &item_position, implicit); + if (element == NULL) { + dcm_dataset_destroy(dataset); + return false; + } + + if (!dcm_dataset_insert(error, dataset, element)) { + dcm_dataset_destroy(dataset); + dcm_element_destroy(element); + return false; + } + } + + seq_position += item_position; + + if (!dcm_sequence_append(error, sequence, dataset)) { + dcm_dataset_destroy(dataset); + } + + index += 1; + } + + *position += seq_position; + + return true; +} + + +static bool parse_element_body(DcmParseState *state, + bool implicit, + uint32_t tag, + DcmVR vr, + uint32_t length, + int64_t position, + bool implicit) +{ + DcmVRClass klass = dcm_dict_vr_class(vr); + size_t size = dcm_dict_vr_size(vr); + uint32_t vm = length / size; + char *value; + + char *value_free = NULL; + char input_buffer[INPUT_BUFFER_SIZE]; + + dcm_log_debug("Read Data Element body '%08X'", tag); + + switch (klass) { + case DCM_CLASS_STRING_SINGLE: + case DCM_CLASS_STRING_MULTI: + if (klass == 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; + } + } + + if (length + 1 >= INPUT_BUFFER_SIZE) { + value = value_free = DCM_MALLOC(state->error, length + 1); + if (value == NULL) { + return false; + } + } else { + value = input_buffer; + } + + if (!dcm_require(state->error, + state->filehandle, + 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 (klass == DCM_CLASS_NUMERIC) { + if (filehandle->byteswap) { + byteswap(value, length, size); + } + } + + if (state.parse.element_create(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_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); + } + + if (state.parse.sequence_begin(error, + state.client, + tag, + vr + length)) { + return false; + } + + int64_t seq_position = 0; + if (!parse_element_sequence(state, + length, + &seq_position, + implicit)) { + return false; + } + *position += seq_position; + + if (state.parse.sequence_end(error, state.client)) { + 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; + } + + return true; +} + + +static bool parse_element(DcmParseState *state, + int64_t *position, + bool implicit) +{ + 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, implicit)) { + return false; + } + + return true; +} + +/* parse a set of elements ending in EOF or the stop function. + */ +bool +dcm_parse_dataset(DcmError **error, + DcmFilehandle *filehandle, + bool implicit, + DcmParse *parse, + void *client) +{ + DcmParseState state = { error, filehandle, parse, client }; + + if (state.parse.parse_begin(state.client, tag, vr, length, implicit) || + state.parse.dataset_begin(state.client, tag, vr, length, implicit)) { + return false; + } + + int64_t position = 0; + for (;;) { + if (dcm_is_eof(state.filehandle)) { + dcm_log_info("Stop reading Data Set. Reached end of filehandle."); + break; + } + + uint32_t tag; + DcmVR vr; + uint32_t length; + if (!parse_element_header(&state, &tag, &vr, &length, &position)) { + 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.client, tag, vr, length, implicit)) { + break; + } + + if (!parse_element_body(&state, tag, vr, length, &position, implicit)) { + return false; + } + } + + if (state.parse.dataset_end(state.client, tag, vr, length, implicit) || + state.parse.parse_end(state.client, tag, vr, length, implicit)) { + return false; + } + + return true; +} + + + diff --git a/src/pdicom.h b/src/pdicom.h index 135dea1..01044e5 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -86,3 +86,30 @@ char **dcm_parse_character_string(DcmError **error, default: break; \ } + +typedef struct _DcmParse { + bool (*parse_begin)(DcmError *, void *client); + bool (*parse_end)(DcmError *, void *client); + + bool (*dataset_begin)(DcmError *, void *client); + bool (*dataset_end)(DcmError *, void *client); + + bool (*sequence_begin)(DcmError *, void *client); + bool (*sequence_end)(DcmError *, void *client); + + bool (*element_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, + bool implicit); +} DcmParse; + + + + From 3ad25908fd3f651429fd6f8f24baec4a6ec0f551 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 3 May 2023 16:35:58 +0100 Subject: [PATCH 04/82] almost done --- include/dicom/dicom.h | 69 ++-- src/dicom-io.c | 188 ++++++----- src/dicom-parse.c | 720 +++++++++--------------------------------- src/pdicom.h | 4 +- 4 files changed, 296 insertions(+), 685 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 6332ff8..af54763 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1376,7 +1376,7 @@ void dcm_frame_destroy(DcmFrame *frame); DCM_EXTERN DcmBOT *dcm_bot_create(DcmError **error, ssize_t *offsets, - uint32_t num_frames, + uint32_t num_frames, ssize_t first_frame_offset); /** @@ -1421,59 +1421,78 @@ void dcm_bot_destroy(DcmBOT *bot); * Part 10 File */ +typedeff struct _DcmIO DcmIO; + +/** + * Something we can read from. + */ +typedef struct _DcmIOHandle { + DcmIO *io; + // more private fields follow +} DcmIOHandle; + /** - * A set of IO functions, see dcm_filehandle_create(). + * A set of IO functions, see dcm_io_handle_create(). */ 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); + /** Open an IO handle */ + DcmIOHandle *(*open)(DcmError **error, void *client); + + /** Close an IO handle */ + int (*close)(DcmError **error, DcmIOHandle *handle); + + /** Read from an IO handle, semantics as POSIX read() */ + int64_t (*read)(DcmError **error, + DcmIOHandle *handle, + char *buffer, + int64_t length); + + /** Seek an IO handle, semantics as POSIX seek() */ + int64_t (*seek)(DcmError **error, + DcmIOHandle *handle, + int64_t offset, + int whence); } DcmIO; /** - * Create a filehandle that reads using a set of DcmIO functions. + * Create a handle that reads using a set of DcmIO functions. * * :param error: Pointer to error object * :param io: Set of read functions for this DcmFilehandle * :param client: Client data for read functions * - * :return: filehandle + * :return: handle */ DCM_EXTERN -DcmFilehandle *dcm_filehandle_create(DcmError **error, - const DcmIO *io, - void *client); +DcmIOhandle *dcm_io_handle_create(DcmError **error, + const DcmIO *io, + void *client); /** - * Open a file on disk as a DcmFilehandle. + * Open a file on disk as a DcmIOhandle. * * :param error: Pointer to error object - * :param filepath: Path to the file on disk + * :param filename: Path to the file on disk * - * :return: filehandle + * :return: handle */ DCM_EXTERN -DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, - const char *filepath); +DcmIOHandle *dcm_io_handle_create_from_file(DcmError **error, + const char *filename); /** - * Open an area of memory as a DcmFilehandle. + * Open an area of memory as a DcmIOhandle. * * :param error: Pointer to error object * :param buffer: Pointer to memory area * :param length: Length of memory area in bytes * - * :return: filehandle + * :return: handle */ DCM_EXTERN -DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, - char *buffer, - int64_t length); +DcmIOHandle *dcm_io_handle_create_from_memory(DcmError **error, + char *buffer, + int64_t length); /** * Read File Meta Information from a File. diff --git a/src/dicom-io.c b/src/dicom-io.c index fd1b5d2..d8650dc 100644 --- a/src/dicom-io.c +++ b/src/dicom-io.c @@ -38,56 +38,59 @@ #define BUFFER_SIZE (4096) typedef struct _DcmIOFile { + DcmIO *io; + + // 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 int dcm_io_close_file(DcmError **error, DcmIOHandle *handle) { - DcmIOFile *io_filehandle = (DcmIOFile *) data; + DcmIOFile *file = (DcmIOFile *) handle; int close_errno = 0; - if (io_filehandle->fd != -1) { - if (close(io_filehandle->fd)) { + if (file->fd != -1) { + if (close(file->fd)) { close_errno = errno; } - io_filehandle->fd = -1; + file->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)); + file->filename, strerror(close_errno)); } } - free(io_filehandle->filehandlename); - free(io_filehandle); + free(file->filename); + free(file); return close_errno; } -static void *dcm_io_open_filehandle(DcmError **error, void *client) +static DcmIOHandle *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(error, file); return NULL; } @@ -99,7 +102,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 +111,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(error, file); return NULL; } - return io_filehandle; + return (DcmIOHandle *)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,35 +155,35 @@ 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, DcmIOHandle *handle, char *buffer, int64_t length) { - DcmIOFile *io_filehandle = (DcmIOFile *) data; + DcmIOFile *file = (DcmIOFile *) handle; 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) { @@ -191,16 +194,16 @@ static int64_t dcm_io_read_filehandle(DcmError **error, void *data, /* 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 +211,138 @@ 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, DcmIOHandle *handle, int64_t offset, int whence) { - DcmIOFile *io_filehandle = (DcmIOFile *) data; + DcmIOFile *file = (DcmIOFile *) handle; /* 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) +DcmIOHandle *dcm_io_handle_create(DcmError **error, + const DcmIO *io, + void *client) +{ + DcmIOHandle *handle = io->open(error, client); + if (handle == NULL) { + return NULL; + } + handle->io = io; + + return handle; +} + + +DcmFilehandle *dcm_io_handle_create_from_file(DcmError **error, + const char *filename) { static DcmIO io = { - dcm_io_open_filehandle, - dcm_io_close_filehandle, - dcm_io_read_filehandle, - dcm_io_seek_filehandle, + 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_handle_create(error, &io, (void *) filename); } typedef struct _DcmIOMemory { + DcmIO *io; + + // private fields char *buffer; int64_t length; int64_t read_point; } DcmIOMemory; -static int dcm_io_close_memory(DcmError **error, void *data) +static int dcm_io_close_memory(DcmError **error, DcmIOHandle *handle) { - DcmIOMemory *io_memory = (DcmIOMemory *) data; + DcmIOMemory *memory = (DcmIOMemory *) handle; USED(error); - free(io_memory); + free(memory); return 0; } -static void *dcm_io_open_memory(DcmError **error, void *client) +static DcmIOHandle *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 (DcmIOHandle *) memory; } -static int64_t dcm_io_read_memory(DcmError **error, void *data, +static int64_t dcm_io_read_memory(DcmError **error, DcmIOHandle *handle, char *buffer, int64_t length) { - DcmIOMemory *io_memory = (DcmIOMemory *) data; + DcmIOMemory *memory = (DcmIOMemory *) handle; 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, DcmIOHandle *handle int64_t offset, int whence) { - DcmIOMemory *io_memory = (DcmIOMemory *) data; + DcmIOMemory *memory = (DcmIOMemory *) handle; int64_t new_offset; @@ -333,11 +353,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,14 +367,14 @@ static int64_t dcm_io_seek_memory(DcmError **error, void *data, return -1; } - new_offset = MAX(0, MIN(new_offset, io_memory->length)); + new_offset = MAX(0, MIN(new_offset, memory->length)); return new_offset; } -DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, - char *buffer, int64_t length) +DcmIOhandle *dcm_io_handle_create_from_memory(DcmError **error, + char *buffer, int64_t length) { static DcmIO io = { dcm_io_open_memory, @@ -369,5 +389,5 @@ DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, 0 }; - return dcm_filehandle_create(error, &io, &memory); + return dcm_io_handle_create(error, &io, &memory); } diff --git a/src/dicom-parse.c b/src/dicom-parse.c index c91723d..fdb2bf1 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -46,83 +46,27 @@ struct PixelDescription { }; -struct _DcmFilehandle { - const DcmIO *io; - void *fp; +typedef struct _DcmParseState { + DcmError **error; + DcmIOhandle *handle; + DcmParse *parse; + bool byteswap; + void *client; + DcmDataSet *meta; int64_t offset; - char *transfer_syntax_uid; int64_t pixel_data_offset; uint64_t *extended_offset_table; - bool byteswap; -}; - - -/* 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; -} - - -DcmFilehandle *dcm_filehandle_create(DcmError **error, - const DcmIO *io, - void *client) -{ - DcmFilehandle *filehandle = DCM_NEW(error, DcmFilehandle); - if (filehandle == NULL) { - return NULL; - } - - 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->fp = filehandle->io->open(error, client); - if (filehandle->fp == NULL) { - dcm_filehandle_destroy(filehandle); - return NULL; - } - - return filehandle; -} - - -void dcm_filehandle_destroy(DcmFilehandle *filehandle) -{ - if (filehandle) { - if (filehandle->transfer_syntax_uid) { - free(filehandle->transfer_syntax_uid); - } - - if (filehandle->fp) { - (void)filehandle->io->close(NULL, filehandle->fp); - filehandle->fp = NULL; - } - - free(filehandle); - } -} +} DcmParseState; -static int64_t dcm_read(DcmError **error, DcmFilehandle *filehandle, +static int64_t dcm_read(DcmParseState *state, char *buffer, int64_t length, int64_t *position) { - int64_t bytes_read = filehandle->io->read(error, - filehandle->fp, buffer, length); + int64_t bytes_read = state->handle->io->read(state->error, + state->handle, + buffer, + length); if (bytes_read < 0) { return bytes_read; } @@ -133,12 +77,11 @@ static int64_t dcm_read(DcmError **error, DcmFilehandle *filehandle, } -static bool dcm_require(DcmError **error, DcmFilehandle *filehandle, +static bool dcm_require(DcmParseState *state, char *buffer, int64_t length, int64_t *position) { while (length > 0) { - int64_t bytes_read = dcm_read(error, - filehandle, buffer, length, position); + int64_t bytes_read = dcm_read(state, buffer, length, position); if (bytes_read < 0) { return false; @@ -157,18 +100,22 @@ static bool dcm_require(DcmError **error, DcmFilehandle *filehandle, } -static bool dcm_seekset(DcmError **error, - DcmFilehandle *filehandle, int64_t offset) +static bool dcm_seekset(DcmParseState *state, int64_t offset) { - return filehandle->io->seek(error, filehandle->fp, offset, SEEK_SET) >= 0; + int64_t new_offset = state->handle->io->seek(state->error, + state->handle, + offset, + SEEK_SET); + return new_offset >= 0; } -static bool dcm_seekcur(DcmError **error, DcmFilehandle *filehandle, - int64_t offset, int64_t *position) +static bool dcm_seekcur(DcmParseState *state, int64_t offset, int64_t *position) { - int64_t new_offset = filehandle->io->seek(error, - filehandle->fp, offset, SEEK_CUR); + int64_t new_offset = state->handle->io->seek(state->error, + state->handle, + offset, + SEEK_CUR); if (new_offset < 0) { return false; } @@ -179,28 +126,35 @@ static bool dcm_seekcur(DcmError **error, DcmFilehandle *filehandle, } -static bool dcm_offset(DcmError **error, - DcmFilehandle *filehandle, int64_t *offset) +static bool dcm_offset(DcmParseState *state, int64_t *offset) { - *offset = filehandle->io->seek(error, filehandle->fp, 0, SEEK_CUR); - if (*offset < 0) { + int64_t new_offset = state->handle->io->seek(state->error, + state->handle, + 0, + SEEK_CUR); + if (new_offset < 0) { return false; } + *offset = new_offset; + return true; } -static bool dcm_is_eof(DcmFilehandle *filehandle) +static bool dcm_is_eof(DcmParseState *state) { - int64_t position = 0; bool eof = true; char buffer[1]; - int64_t bytes_read = filehandle->io->read(NULL, filehandle->fp, buffer, 1); + int64_t bytes_read = state->handle->io->read(NULL, + state->handle, + buffer, + 1); if (bytes_read > 0) { eof = false; - (void) dcm_seekcur(NULL, filehandle, -1, &position); + int64_t position = 0; + (void) dcm_seekcur(state, -1, &position); } return eof; @@ -229,7 +183,7 @@ static void byteswap(char *data, size_t length, size_t size) } -static bool read_uint16(DcmError **error, DcmFilehandle *filehandle, +static bool read_uint16(DcmParseState *state, uint16_t *value, int64_t *position) { union { @@ -237,11 +191,11 @@ static bool read_uint16(DcmError **error, DcmFilehandle *filehandle, char c[2]; } buffer; - if (!dcm_require(error, filehandle, buffer.c, 2, position)) { + if (!dcm_require(state, buffer.c, 2, position)) { return false; } - if (filehandle->byteswap) { + if (state->byteswap) { byteswap(buffer.c, 2, 2); } @@ -251,7 +205,7 @@ static bool read_uint16(DcmError **error, DcmFilehandle *filehandle, } -static bool read_uint32(DcmError **error, DcmFilehandle *filehandle, +static bool read_uint32(DcmParseState *state, uint32_t *value, int64_t *position) { union { @@ -259,11 +213,11 @@ static bool read_uint32(DcmError **error, DcmFilehandle *filehandle, char c[4]; } buffer; - if (!dcm_require(error, filehandle, buffer.c, 4, position)) { + if (!dcm_require(state, buffer.c, 4, position)) { return false; } - if (filehandle->byteswap) { + if (state->byteswap) { byteswap(buffer.c, 4, 4); } @@ -273,13 +227,12 @@ static bool read_uint32(DcmError **error, DcmFilehandle *filehandle, } -static bool read_tag(DcmError **error, DcmFilehandle *filehandle, - uint32_t *value, int64_t *position) +static bool read_tag(DcmParseState *state, uint32_t *value, int64_t *position) { uint16_t group, elem; - if (!read_uint16(error, filehandle, &group, position) || - !read_uint16(error, filehandle, &elem, position)) { + if (!read_uint16(state, &group, position) || + !read_uint16(state, &elem, position)) { return false; } @@ -289,402 +242,6 @@ static bool read_tag(DcmError **error, DcmFilehandle *filehandle, } -static bool read_iheader(DcmError **error, DcmFilehandle *filehandle, - uint32_t *item_tag, uint32_t *item_length, int64_t *position) -{ - if (!read_tag(error, filehandle, item_tag, position) || - !read_uint32(error, filehandle, item_length, position)) { - return false; - } - - return true; -} - - -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; -} - - -static DcmElement *read_element_header(DcmError **error, - DcmFilehandle *filehandle, - uint32_t *length, - int64_t *position, - bool implicit) -{ - uint32_t tag; - if (!read_tag(error, filehandle, &tag, position)) { - 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); - } - - if (!read_uint32(error, filehandle, length, position)) { - return NULL; - } - } else { - // Value Representation - char vr_str[3]; - if (!dcm_require(error, filehandle, vr_str, 2, position)) { - 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); - 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 (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; - } - } - } - - return dcm_element_create(error, tag, vr); -} - - -// fwd ref this -static DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit); - -static bool read_element_sequence(DcmError **error, - DcmFilehandle *filehandle, - DcmSequence *sequence, - uint32_t length, - int64_t *position, - bool implicit) -{ - 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; - } - - 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(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 (length == 0xFFFFFFFF) { - dcm_log_debug("Item #%d has undefined length.", index); - } else { - dcm_log_debug("Item #%d has defined length %d.", - index, item_length); - } - - DcmDataSet *dataset = dcm_dataset_create(error); - if (dataset == NULL) { - 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; - } - - // back to start of element - if (!dcm_seekcur(error, filehandle, -4, &item_position)) { - dcm_dataset_destroy(dataset); - return false; - } - - DcmElement *element = read_element(error, filehandle, - &item_position, implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return false; - } - - if (!dcm_dataset_insert(error, dataset, element)) { - dcm_dataset_destroy(dataset); - dcm_element_destroy(element); - return false; - } - } - - seq_position += item_position; - - if (!dcm_sequence_append(error, sequence, dataset)) { - dcm_dataset_destroy(dataset); - } - - index += 1; - } - - *position += seq_position; - - return true; -} - - -static bool read_element_body(DcmError **error, - DcmElement *element, - DcmFilehandle *filehandle, - uint32_t length, - int64_t *position, - bool implicit) -{ - 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; - - dcm_log_debug("Read Data Element body '%08X'", tag); - - 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; - - 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; - - char *values = DCM_MALLOC(error, length); - if (values == NULL) { - return false; - } - - if (!dcm_require(error, filehandle, values, length, position)) { - free(values); - return false; - } - - if (filehandle->byteswap) { - byteswap(values, length, size); - } - - if( !dcm_element_set_value_numeric_multi(error, - element, - (int *) values, - vm, - true)) { - free(values); - return false; - } - - break; - - case DCM_CLASS_BINARY: - value = DCM_MALLOC(error, length); - if (value == NULL) { - return false; - } - - if (!dcm_require(error, filehandle, value, length, position)) { - free(value); - return false; - } - - if( !dcm_element_set_value_binary(error, element, - value, length, true)) { - free(value); - return false; - } - - break; - - case DCM_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); - } - - DcmSequence *seq = dcm_sequence_create(error); - if (seq == NULL) { - return NULL; - } - - 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; - } - - return true; -} - - -DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit) -{ - DcmElement *element; - - uint32_t length; - element = read_element_header(error, - filehandle, &length, position, implicit); - if (element == NULL) { - return NULL; - } - - if (!read_element_body(error, element, filehandle, - length, position, implicit)) { - dcm_element_destroy(element); - return NULL; - } - - return element; -} - - DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, DcmFilehandle *filehandle) { @@ -1382,22 +939,9 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, /* The size of the buffer we use for reading smaller element values. This is * large enough for most VRs. */ -#define INPUT_BUFFER_SIZE (10240) +#define INPUT_BUFFER_SIZE (256) -typedef struct _DcmParseState { - DcmError **error; - DcmFilehandle *filehandle; - DcmParse *parse; - void *client; - - DcmDataSet *meta; - int64_t offset; - int64_t pixel_data_offset; - uint64_t *extended_offset_table; - bool byteswap; -} DcmParseState; - static bool parse_element_header(DcmParseState *state, bool implicit, @@ -1406,7 +950,7 @@ static bool parse_element_header(DcmParseState *state, uint32_t *length, int64_t *position) { - if (!read_tag(error, state->filehandle, tag, position)) { + if (!read_tag(error, state->handle, tag, position)) { return false; } @@ -1471,18 +1015,32 @@ static bool parse_element_header(DcmParseState *state, static bool parse_element_sequence(DcmParseState *state, + bool implicit, + uint32_t tag, + DcmVR vr, uint32_t seq_length, - int64_t *position, - bool implicit) + int64_t *position) { + if (state->parse->sequence_begin(error, state->client, + tag, + vr, + seq_length)) { + return false; + } + int index = 0; - int64_t seq_position = 0; - while (seq_position < seq_length) { + while (*position < seq_length) { dcm_log_debug("Read Item #%d.", index); uint32_t item_tag; uint32_t item_length; - if (!read_tag(state->error, state->filehandle, &item_tag, position) || - !read_uint32(state->error, state->filehandle, &item_length, position)) { + if (!read_tag(state->error, + state->filehandle, + &item_tag, + position) || + !read_uint32(state->error, + state->filehandle, + &item_length, + position)) { return false; } @@ -1510,23 +1068,29 @@ static bool parse_element_sequence(DcmParseState *state, index, item_length); } - if (state.parse.dataset_start(state->error, state->client)) { + if (state->parse->dataset_start(state->error, state->client)) { return false; } int64_t item_position = 0; while (item_position < item_length) { // peek the next tag - if (!read_tag(state->error, state->filehandle, &item_tag, &item_position)) { + if (!read_tag(state->error, + state->filehandle, + &item_tag, + &item_position)) { 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(state->error, state->filehandle, 4, &item_position)) { + // step over the tag length + if (!dcm_seekcur(state->error, + state->filehandle, + 4, + &item_position)) { return false; } @@ -1534,27 +1098,22 @@ static bool parse_element_sequence(DcmParseState *state, } // back to start of element - if (!dcm_seekcur(state->error, state->filehandle, -4, &item_position)) { + if (!dcm_seekcur(state->error, + state->filehandle, + -4, + &item_position)) { return false; } - DcmElement *element = parse_element(state, &item_position, implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return false; - } - - if (!dcm_dataset_insert(error, dataset, element)) { - dcm_dataset_destroy(dataset); - dcm_element_destroy(element); + if (!parse_element(state, implicit, &item_position)) { return false; } } seq_position += item_position; - if (!dcm_sequence_append(error, sequence, dataset)) { - dcm_dataset_destroy(dataset); + if (state->parse->dataset_end(state->error, state->client)) { + return false; } index += 1; @@ -1562,6 +1121,10 @@ static bool parse_element_sequence(DcmParseState *state, *position += seq_position; + if (state->parse->sequence_end(state->error, state->client)) { + return false; + } + return true; } @@ -1571,8 +1134,7 @@ static bool parse_element_body(DcmParseState *state, uint32_t tag, DcmVR vr, uint32_t length, - int64_t position, - bool implicit) + int64_t position) { DcmVRClass klass = dcm_dict_vr_class(vr); size_t size = dcm_dict_vr_size(vr); @@ -1587,7 +1149,11 @@ static bool parse_element_body(DcmParseState *state, switch (klass) { case DCM_CLASS_STRING_SINGLE: case DCM_CLASS_STRING_MULTI: + case DCM_CLASS_NUMERIC: + case DCM_CLASS_BINARY: if (klass == DCM_CLASS_NUMERIC) { + // all numeric classes have a size + g_assert (size != 0); if (length % size != 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading of Data Element failed", @@ -1597,6 +1163,7 @@ static bool parse_element_body(DcmParseState *state, } } + // read to a static char buffer, if possible if (length + 1 >= INPUT_BUFFER_SIZE) { value = value_free = DCM_MALLOC(state->error, length + 1); if (value == NULL) { @@ -1632,12 +1199,12 @@ static bool parse_element_body(DcmParseState *state, } } - if (state.parse.element_create(error, - state.client, - tag, - vr - value, - length)) { + if (state->parse->element_create(error, + state->client, + tag, + vr, + value, + length)) { if (value_free != NULL) { free(value_free); } @@ -1661,27 +1228,17 @@ static bool parse_element_body(DcmParseState *state, tag, length); } - if (state.parse.sequence_begin(error, - state.client, - tag, - vr - length)) { - return false; - } - int64_t seq_position = 0; if (!parse_element_sequence(state, + implicit, + tag, + vr, length, - &seq_position, - implicit)) { + &seq_position)) { return false; } *position += seq_position; - if (state.parse.sequence_end(error, state.client)) { - return false; - } - break; default: @@ -1697,39 +1254,34 @@ static bool parse_element_body(DcmParseState *state, static bool parse_element(DcmParseState *state, - int64_t *position, - bool implicit) + bool implicit, + 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, implicit)) { + if (!parse_element_header(state, implicit, &tag, &vr, &length, position) || + !parse_element_body(state, implicit, tag, vr, length, position)) { return false; } return true; } -/* parse a set of elements ending in EOF or the stop function. +/* Top-level datasets don't have an enclosing length, and can broken by a + * stop function. */ -bool -dcm_parse_dataset(DcmError **error, - DcmFilehandle *filehandle, - bool implicit, - DcmParse *parse, - void *client) +static bool +parse_toplevel_dataset(DcmParseState *state, + bool implicit, + int64_t *position) { - DcmParseState state = { error, filehandle, parse, client }; - - if (state.parse.parse_begin(state.client, tag, vr, length, implicit) || - state.parse.dataset_begin(state.client, tag, vr, length, implicit)) { + if (!state->parse->dataset_begin(state->error, state->client)) { return false; } - int64_t position = 0; for (;;) { - if (dcm_is_eof(state.filehandle)) { + if (dcm_is_eof(state->filehandle)) { dcm_log_info("Stop reading Data Set. Reached end of filehandle."); break; } @@ -1737,7 +1289,8 @@ dcm_parse_dataset(DcmError **error, uint32_t tag; DcmVR vr; uint32_t length; - if (!parse_element_header(&state, &tag, &vr, &length, &position)) { + if (!parse_element_header(state, implicit, + &tag, &vr, &length, position)) { return false; } @@ -1747,17 +1300,16 @@ dcm_parse_dataset(DcmError **error, break; } - if (state.parse.stop(state.client, tag, vr, length, implicit)) { + if (state->parse->stop(state->client, implicit, tag, vr, length)) { break; } - if (!parse_element_body(&state, tag, vr, length, &position, implicit)) { + if (!parse_element_body(state, implicit, tag, vr, length, position)) { return false; } } - if (state.parse.dataset_end(state.client, tag, vr, length, implicit) || - state.parse.parse_end(state.client, tag, vr, length, implicit)) { + if (!state->parse->dataset_end(state->error, state->client)) { return false; } @@ -1765,4 +1317,24 @@ dcm_parse_dataset(DcmError **error, } +/* Parse a dataset from a filehandle. + */ +bool +dcm_parse_dataset(DcmError **error, + DcmIOHandle *handle, + bool implicit, + DcmParse *parse, + bool byteswap, + void *client) +{ + DcmParseState state = { error, handle, parse, byteswap, client }; + int64_t position = 0; + if (!state.parse->parse_begin(state.error, state.client) || + !parse_toplevel_dataset(&state, implicit, &position) || + !state.parse->parse_end(state.error, state.client)) { + return false; + } + + return true; +} diff --git a/src/pdicom.h b/src/pdicom.h index 01044e5..c67e025 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -104,10 +104,10 @@ typedef struct _DcmParse { uint32_t length); bool (*stop)(void *client, + bool implicit, uint32_t tag, DcmVR vr, - uint32_t length, - bool implicit); + uint32_t length); } DcmParse; From 4332e4585eb4ea41934dd44b75a458578d02f880 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 4 May 2023 08:47:00 +0100 Subject: [PATCH 05/82] almost there --- src/dicom-parse.c | 738 +--------------------------------------------- src/pdicom.h | 45 +-- 2 files changed, 37 insertions(+), 746 deletions(-) diff --git a/src/dicom-parse.c b/src/dicom-parse.c index fdb2bf1..bb246b9 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -33,17 +33,10 @@ #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; -}; +/* 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 { @@ -242,707 +235,6 @@ static bool read_tag(DcmParseState *state, uint32_t *value, int64_t *position) } -DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, - DcmFilehandle *filehandle) -{ - const bool implicit = false; - - int64_t position; - uint16_t group_number; - DcmElement *element; - - DcmDataSet *file_meta = dcm_dataset_create(error); - if (file_meta == NULL) { - return NULL; - } - - position = 0; - - // 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'; - - // 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; - } - - position = 0; - - // File Meta Information Group Length - element = read_element(error, filehandle, &position, implicit); - if (element == NULL) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - 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; - } - - 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; - } - - group_number = dcm_element_get_group_number(element); - if (group_number != 0x0002) { - dcm_element_destroy(element); - break; - } - - if (!read_element_body(error, element, filehandle, - length, &position, implicit)) { - dcm_element_destroy(element); - dcm_dataset_destroy(file_meta); - return NULL; - } - - // many DICOMs have a FileMetaInformationVersion element, but - // not all ... ignore the version number if present - if (dcm_element_get_tag(element) == 0x00020001) { - dcm_element_destroy(element); - continue; - } - - if (!dcm_dataset_insert(error, file_meta, element)) { - dcm_element_destroy(element); - dcm_dataset_destroy(file_meta); - return NULL; - } - } - - if (!dcm_offset(error, filehandle, &filehandle->offset)) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - 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; - } - - 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; - } - - dcm_dataset_lock(file_meta); - - return file_meta; -} - - -DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, - DcmFilehandle *filehandle) -{ - bool implicit; - - if (filehandle->offset == 0) { - DcmDataSet *meta = dcm_filehandle_read_file_meta(error, filehandle); - if (meta== NULL) { - return NULL; - } - 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; - } - } - - DcmDataSet *dataset = dcm_dataset_create(error); - if (dataset == NULL) { - return NULL; - } - - int64_t position = 0; - for (;;) { - if (dcm_is_eof(filehandle)) { - dcm_log_info("Stop reading Data Set. Reached end of filehandle."); - break; - } - - uint32_t length; - DcmElement *element = read_element_header(error, - filehandle, - &length, - &position, - implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return NULL; - } - 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 (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; - } - - } 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; - } - } - - if (!dcm_offset(error, - filehandle, &filehandle->pixel_data_offset)) { - dcm_dataset_destroy(dataset); - return NULL; - } - - 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; - } - - 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; - } - } - - 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) -{ - uint64_t value; - - dcm_log_debug("Reading Basic Offset Table."); - - 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); - return NULL; - } - - uint32_t num_frames; - if (!get_num_frames(error, metadata, &num_frames)) { - return NULL; - } - - 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_seekset(error, filehandle, filehandle->pixel_data_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"); - return NULL; - } - - // The header of the BOT Item - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - return NULL; - } - 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 NULL; - } - - ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); - if (offsets == NULL) { - 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) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "No Basic Offset Table, " - "and no Extended Offset Table"); - free(offsets); - return NULL; - } - - dcm_log_info("Found Extended Offset Table."); - - 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; - } - - // FIXME is this correct? - first_frame_offset = position; - } - - return dcm_bot_create(error, offsets, num_frames, first_frame_offset); -} - - -static bool set_pixel_description(DcmError **error, - struct PixelDescription *desc, - const DcmDataSet *metadata) -{ - DcmElement *element; - int64_t value; - const char *string; - - element = dcm_dataset_get(error, metadata, 0x00280010); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->rows = value; - - element = dcm_dataset_get(error, metadata, 0x00280011); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->columns = value; - - 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; - - 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; - - 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; - - 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; - - return true; -} - - -DcmBOT *dcm_filehandle_build_bot(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata) -{ - uint64_t i; - - dcm_log_debug("Building Basic Offset Table."); - - uint32_t num_frames; - if (!get_num_frames(error, metadata, &num_frames)) { - return NULL; - } - - 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_seekset(error, filehandle, filehandle->pixel_data_offset)) { - return NULL; - } - - // we measure offsets from this point - int64_t position = 0; - - 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; - } - - ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); - if (offsets == NULL) { - return NULL; - } - - 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; - } - - 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; - } - - // Move filehandlepointer to the first byte of first Frame item - if (!dcm_seekcur(error, filehandle, length, &position)) { - free(offsets); - } - - // and that's the offset to the first frame - first_frame_offset = position; - - // now measure positions from the start of the first frame - position = 0; - - i = 0; - while (true) { - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - free(offsets); - return NULL; - } - - if (tag == TAG_SQ_DELIM) { - break; - } - - 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; - } - - if (dcm_is_eof(filehandle)) { - break; - } - - // step back to the start of the item for this frame - offsets[i] = position - 8; - - if (!dcm_seekcur(error, filehandle, length, &position)) { - free(offsets); - return NULL; - } - - i += 1; - } - - 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; - } - - return dcm_bot_create(error, offsets, num_frames, first_frame_offset); -} - - -DcmFrame *dcm_filehandle_read_frame(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata, - DcmBOT *bot, - uint32_t number) -{ - uint32_t length; - - 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; - } - - 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; - } - - struct PixelDescription desc; - if (!set_pixel_description(error, &desc, metadata)) { - return NULL; - } - - 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; - } - - 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; - } - - char *value = DCM_MALLOC(error, length); - if (value == NULL) { - return NULL; - } - if (!dcm_require(error, filehandle, value, length, &position)) { - free(value); - return NULL; - } - - 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); -} - - - - -/* The size of the buffer we use for reading smaller element values. This is - * large enough for most VRs. - */ -#define INPUT_BUFFER_SIZE (256) - - - static bool parse_element_header(DcmParseState *state, bool implicit, uint32_t *tag, @@ -956,7 +248,7 @@ static bool parse_element_header(DcmParseState *state, if (implicit) { // this can be an ambiguious VR, eg. pixeldata is allowed in implicit - // mode and has to be disambiguated later from other tags + // 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, @@ -1271,10 +563,9 @@ static bool parse_element(DcmParseState *state, /* Top-level datasets don't have an enclosing length, and can broken by a * stop function. */ -static bool -parse_toplevel_dataset(DcmParseState *state, - bool implicit, - int64_t *position) +static bool parse_toplevel_dataset(DcmParseState *state, + bool implicit, + int64_t *position) { if (!state->parse->dataset_begin(state->error, state->client)) { return false; @@ -1319,13 +610,12 @@ parse_toplevel_dataset(DcmParseState *state, /* Parse a dataset from a filehandle. */ -bool -dcm_parse_dataset(DcmError **error, - DcmIOHandle *handle, - bool implicit, - DcmParse *parse, - bool byteswap, - void *client) +bool dcm_parse_dataset(DcmError **error, + DcmIOHandle *handle, + bool implicit, + DcmParse *parse, + bool byteswap, + void *client) { DcmParseState state = { error, handle, parse, byteswap, client }; diff --git a/src/pdicom.h b/src/pdicom.h index c67e025..89ffb8f 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -20,7 +20,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 +29,7 @@ if (!(CONDITION)) { \ dcm_error_set((ERROR), DCM_ERROR_CODE_INVALID, \ "Test fail", \ - "%s:%s (%d)" \ + "%s:%s (%d)" \ __FILE__, __FUNCTION__, __LINE__); \ return; \ } @@ -76,21 +76,17 @@ char **dcm_parse_character_string(DcmError **error, #define DCM_SWITCH_NUMERIC(VR, OPERATION) \ switch (VR) { \ 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; \ } - typedef struct _DcmParse { - bool (*parse_begin)(DcmError *, void *client); - bool (*parse_end)(DcmError *, void *client); - bool (*dataset_begin)(DcmError *, void *client); bool (*dataset_end)(DcmError *, void *client); @@ -99,17 +95,22 @@ typedef struct _DcmParse { bool (*element_create)(DcmError *, void *client, uint32_t tag, - DcmVR vr, - char *value, + DcmVR vr, + char *value, uint32_t length); bool (*stop)(void *client, - bool implicit, - uint32_t tag, - DcmVR vr, - uint32_t length); + bool implicit, + uint32_t tag, + DcmVR vr, + uint32_t length); } DcmParse; - - +DCM_EXTERN +bool dcm_parse_dataset(DcmError **error, + DcmIOHandle *handle, + bool implicit, + DcmParse *parse, + bool byteswap, + void *client); From 00060017cb5271556119da0bb692784deaa71cac Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 4 May 2023 13:52:04 +0100 Subject: [PATCH 06/82] tests pass again --- TODO | 2 +- include/dicom/dicom.h | 174 ++++++++++++++++++++++++++++++++---------- src/dicom-file.c | 68 ++++++++++------- src/dicom-io.c | 100 +++++++++++++++--------- src/dicom-parse.c | 84 ++++++-------------- src/pdicom.h | 4 +- tools/dcm-dump.c | 16 ++-- 7 files changed, 269 insertions(+), 179 deletions(-) diff --git a/TODO b/TODO index ebe089d..7b70465 100644 --- a/TODO +++ b/TODO @@ -4,7 +4,7 @@ - split parser from AST builder -- add get_vr_max_size() and use a static buffer to read value when possible +- move implicit into parser state? # bot support diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index af54763..b3d1095 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1421,78 +1421,178 @@ void dcm_bot_destroy(DcmBOT *bot); * Part 10 File */ -typedeff struct _DcmIO DcmIO; +typedef struct _DcmIOMethods DcmIOMethods; /** * Something we can read from. */ -typedef struct _DcmIOHandle { - DcmIO *io; +typedef struct _DcmIO { + const DcmIOMethods *methods; // more private fields follow -} DcmIOHandle; +} DcmIO; /** - * A set of IO functions, see dcm_io_handle_create(). + * A set of IO methods, see dcm_io_create(). */ -typedef struct _DcmIO { - /** Open an IO handle */ - DcmIOHandle *(*open)(DcmError **error, void *client); +typedef struct _DcmIOMethods { + /** Open an IO object */ + DcmIO *(*open)(DcmError **error, void *client); - /** Close an IO handle */ - int (*close)(DcmError **error, DcmIOHandle *handle); + /** Close an IO object */ + bool (*close)(DcmError **error, DcmIO *io); - /** Read from an IO handle, semantics as POSIX read() */ + /** Read from an IO object, semantics as POSIX read() */ int64_t (*read)(DcmError **error, - DcmIOHandle *handle, + DcmIO *io, char *buffer, int64_t length); - /** Seek an IO handle, semantics as POSIX seek() */ + /** Seek an IO object, semantics as POSIX seek() */ int64_t (*seek)(DcmError **error, - DcmIOHandle *handle, + DcmIO *io, int64_t offset, int whence); -} DcmIO; +} DcmIOMethods; /** - * Create a handle that reads using a set of DcmIO functions. + * Create an IO object using a set of IO methods. * - * :param error: Pointer to error object - * :param io: Set of read functions for this DcmFilehandle - * :param client: Client data for read functions + * :param error: Error structure pointer + * :param io: Set of read methods + * :param client: Client data for read methods * - * :return: handle + * :return: IO object */ DCM_EXTERN -DcmIOhandle *dcm_io_handle_create(DcmError **error, - const DcmIO *io, - void *client); +DcmIO *dcm_io_create(DcmError **error, + const DcmIOMethods *methods, + void *client); /** - * Open a file on disk as a DcmIOhandle. + * Open a file on disk for IO. * - * :param error: Pointer to error object + * :param error: Error structure pointer * :param filename: Path to the file on disk * - * :return: handle + * :return: IO object + */ +DCM_EXTERN +DcmIO *dcm_io_create_from_file(DcmError **error, + const char *filename); + +/** + * 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 + * + * :return: IO object + */ +DCM_EXTERN +DcmIO *dcm_io_create_from_memory(DcmError **error, + char *buffer, + int64_t length); + +/** + * Close an IO object. + * + * :param error: Error structure pointer + * :param io: Pointer to IO object + * + * :return: true on success */ DCM_EXTERN -DcmIOHandle *dcm_io_handle_create_from_file(DcmError **error, - const char *filename); +bool dcm_io_close(DcmError **error, DcmIO *io); /** - * Open an area of memory as a DcmIOhandle. + * 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); + +/** + * 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 + */ +DCM_EXTERN +int64_t dcm_io_seek(DcmError **error, + DcmIO *io, + int64_t offset, + int whence); + +/** + * Create a representatiopn of a DICOM File using an IO object. + * + * 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, DcmIO *io); + +/** + * Open a file on disk as a DcmFilehandle. + * + * :param error: Error structure pointer + * :param filepath: Path to the file on disk + * + * :return: filehandle + */ +DCM_EXTERN +DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, + const char *filepath); + + +/** + * Open an area of memory as a DcmFilehandle. + * + * :param error: Error structure pointer * :param buffer: Pointer to memory area * :param length: Length of memory area in bytes * - * :return: handle + * :return: filehandle + */ +DCM_EXTERN +DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, + char *buffer, int64_t length); + +/** + * Destroy a Filehandle. + * + * :param filehandle: File */ DCM_EXTERN -DcmIOHandle *dcm_io_handle_create_from_memory(DcmError **error, - char *buffer, - int64_t length); +void dcm_filehandle_destroy(DcmFilehandle *filehandle); /** * Read File Meta Information from a File. @@ -1574,12 +1674,4 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, DcmBOT *bot, uint32_t index); -/** - * Destroy a File. - * - * :param filehandle: File - */ -DCM_EXTERN -void dcm_filehandle_destroy(DcmFilehandle *filehandle); - #endif diff --git a/src/dicom-file.c b/src/dicom-file.c index 3ee7ee6..abeb65a 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -47,8 +47,7 @@ struct PixelDescription { struct _DcmFilehandle { - const DcmIO *io; - void *fp; + DcmIO *io; DcmDataSet *meta; int64_t offset; char *transfer_syntax_uid; @@ -73,9 +72,7 @@ static bool is_big_endian(void) } -DcmFilehandle *dcm_filehandle_create(DcmError **error, - const DcmIO *io, - void *client) +DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) { DcmFilehandle *filehandle = DCM_NEW(error, DcmFilehandle); if (filehandle == NULL) { @@ -83,7 +80,6 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, } filehandle->io = io; - filehandle->fp = NULL; filehandle->meta = NULL; filehandle->offset = 0; filehandle->transfer_syntax_uid = NULL; @@ -91,13 +87,31 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, filehandle->extended_offset_table = NULL; filehandle->byteswap = is_big_endian(); - filehandle->fp = filehandle->io->open(error, client); - if (filehandle->fp == NULL) { - dcm_filehandle_destroy(filehandle); + return 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, + 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); } @@ -108,11 +122,7 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) free(filehandle->transfer_syntax_uid); } - if (filehandle->fp) { - (void)filehandle->io->close(NULL, filehandle->fp); - filehandle->fp = NULL; - } - + (void) dcm_io_close(NULL, filehandle->io); free(filehandle); } } @@ -121,8 +131,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,15 +169,14 @@ 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) { - int64_t new_offset = filehandle->io->seek(error, - filehandle->fp, offset, SEEK_CUR); + int64_t new_offset = dcm_io_seek(error, filehandle->io, offset, SEEK_CUR); if (new_offset < 0) { return false; } @@ -182,11 +190,13 @@ static bool dcm_seekcur(DcmError **error, DcmFilehandle *filehandle, static bool dcm_offset(DcmError **error, DcmFilehandle *filehandle, int64_t *offset) { - *offset = filehandle->io->seek(error, filehandle->fp, 0, SEEK_CUR); - if (*offset < 0) { + int64_t new_offset = dcm_io_seek(error, filehandle->io, 0, SEEK_CUR); + if (new_offset < 0) { return false; } + *offset = new_offset; + return true; } @@ -197,7 +207,7 @@ static bool dcm_is_eof(DcmFilehandle *filehandle) bool eof = true; char buffer[1]; - int64_t bytes_read = filehandle->io->read(NULL, filehandle->fp, buffer, 1); + int64_t bytes_read = dcm_io_read(NULL, filehandle->io, buffer, 1); if (bytes_read > 0) { eof = false; (void) dcm_seekcur(NULL, filehandle, -1, &position); @@ -412,10 +422,10 @@ static DcmElement *read_element_header(DcmError **error, // fwd ref this -static DcmElement *parse_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit); +static DcmElement *read_element(DcmError **error, + DcmFilehandle *filehandle, + int64_t *position, + bool implicit); static bool read_element_sequence(DcmError **error, DcmFilehandle *filehandle, @@ -430,8 +440,8 @@ static bool read_element_sequence(DcmError **error, dcm_log_debug("Read Item #%d.", index); uint32_t item_tag; uint32_t item_length; - if (!read_tag(state->error, state->filehandle, &item_tag, position) || - !read_uint32(state->error, state->filehandle, &item_length, position)) { + if (!read_iheader(error, filehandle, + &item_tag, &item_length, &seq_position)) { return false; } diff --git a/src/dicom-io.c b/src/dicom-io.c index d8650dc..ce67d9b 100644 --- a/src/dicom-io.c +++ b/src/dicom-io.c @@ -38,7 +38,7 @@ #define BUFFER_SIZE (4096) typedef struct _DcmIOFile { - DcmIO *io; + DcmIOMethods *methods; // private fields int fd; @@ -49,10 +49,9 @@ typedef struct _DcmIOFile { } DcmIOFile; -static int dcm_io_close_file(DcmError **error, DcmIOHandle *handle) +static bool dcm_io_close_file(DcmError **error, DcmIO *io) { - DcmIOFile *file = (DcmIOFile *) handle; - + DcmIOFile *file = (DcmIOFile *) io; int close_errno = 0; if (file->fd != -1) { @@ -73,11 +72,11 @@ static int dcm_io_close_file(DcmError **error, DcmIOHandle *handle) free(file->filename); free(file); - return close_errno; + return close_errno == 0; } -static DcmIOHandle *dcm_io_open_file(DcmError **error, void *client) +static DcmIO *dcm_io_open_file(DcmError **error, void *client) { DcmIOFile *file = DCM_NEW(error, DcmIOFile); if (file == NULL) { @@ -90,7 +89,7 @@ static DcmIOHandle *dcm_io_open_file(DcmError **error, void *client) const char *filename = (const char *) client; file->filename = dcm_strdup(error, filename); if (file->filename == NULL) { - dcm_io_close_file(error, file); + dcm_io_close_file(error, (DcmIO *)file); return NULL; } @@ -121,11 +120,11 @@ static DcmIOHandle *dcm_io_open_file(DcmError **error, void *client) dcm_error_set(error, DCM_ERROR_CODE_IO, "Unable to open filehandle", "Unable to open %s - %s", file->filename, strerror(open_errno)); - dcm_io_close_file(error, file); + dcm_io_close_file(error, (DcmIO *)file); return NULL; } - return (DcmIOHandle *)file; + return (DcmIO *)file; } @@ -173,10 +172,10 @@ static int64_t refill(DcmError **error, DcmIOFile *file) } -static int64_t dcm_io_read_file(DcmError **error, DcmIOHandle *handle, +static int64_t dcm_io_read_file(DcmError **error, DcmIO *io, char *buffer, int64_t length) { - DcmIOFile *file = (DcmIOFile *) handle; + DcmIOFile *file = (DcmIOFile *) io; int64_t bytes_read = 0; while (length > 0) { @@ -211,10 +210,10 @@ static int64_t dcm_io_read_file(DcmError **error, DcmIOHandle *handle, } -static int64_t dcm_io_seek_file(DcmError **error, DcmIOHandle *handle, +static int64_t dcm_io_seek_file(DcmError **error, DcmIO *io, int64_t offset, int whence) { - DcmIOFile *file = (DcmIOFile *) handle; + 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. @@ -257,36 +256,35 @@ static int64_t dcm_io_seek_file(DcmError **error, DcmIOHandle *handle, } -DcmIOHandle *dcm_io_handle_create(DcmError **error, - const DcmIO *io, - void *client) +DcmIO *dcm_io_create(DcmError **error, + const DcmIOMethods *methods, + void *client) { - DcmIOHandle *handle = io->open(error, client); - if (handle == NULL) { + DcmIO *io = methods->open(error, client); + if (io == NULL) { return NULL; } - handle->io = io; + io->methods = methods; - return handle; + return io; } -DcmFilehandle *dcm_io_handle_create_from_file(DcmError **error, - const char *filename) +DcmIO *dcm_io_create_from_file(DcmError **error, const char *filename) { - static DcmIO io = { + static DcmIOMethods methods = { dcm_io_open_file, dcm_io_close_file, dcm_io_read_file, dcm_io_seek_file, }; - return dcm_io_handle_create(error, &io, (void *) filename); + return dcm_io_create(error, &methods, (void *) filename); } typedef struct _DcmIOMemory { - DcmIO *io; + DcmIOMethods *methods; // private fields char *buffer; @@ -295,18 +293,18 @@ typedef struct _DcmIOMemory { } DcmIOMemory; -static int dcm_io_close_memory(DcmError **error, DcmIOHandle *handle) +static bool dcm_io_close_memory(DcmError **error, DcmIO *io) { - DcmIOMemory *memory = (DcmIOMemory *) handle; + DcmIOMemory *memory = (DcmIOMemory *) io; USED(error); free(memory); - return 0; + return true; } -static DcmIOHandle *dcm_io_open_memory(DcmError **error, void *client) +static DcmIO *dcm_io_open_memory(DcmError **error, void *client) { DcmIOMemory *params = (DcmIOMemory *)client; @@ -317,14 +315,14 @@ static DcmIOHandle *dcm_io_open_memory(DcmError **error, void *client) memory->buffer = params->buffer; memory->length = params->length; - return (DcmIOHandle *) memory; + return (DcmIO *) memory; } -static int64_t dcm_io_read_memory(DcmError **error, DcmIOHandle *handle, +static int64_t dcm_io_read_memory(DcmError **error, DcmIO *io, char *buffer, int64_t length) { - DcmIOMemory *memory = (DcmIOMemory *) handle; + DcmIOMemory *memory = (DcmIOMemory *) io; USED(error); @@ -339,10 +337,10 @@ static int64_t dcm_io_read_memory(DcmError **error, DcmIOHandle *handle, } -static int64_t dcm_io_seek_memory(DcmError **error, DcmIOHandle *handle +static int64_t dcm_io_seek_memory(DcmError **error, DcmIO *io, int64_t offset, int whence) { - DcmIOMemory *memory = (DcmIOMemory *) handle; + DcmIOMemory *memory = (DcmIOMemory *) io; int64_t new_offset; @@ -373,10 +371,10 @@ static int64_t dcm_io_seek_memory(DcmError **error, DcmIOHandle *handle } -DcmIOhandle *dcm_io_handle_create_from_memory(DcmError **error, - char *buffer, int64_t length) +DcmIO *dcm_io_create_from_memory(DcmError **error, + char *buffer, int64_t length) { - static DcmIO io = { + static DcmIOMethods methods = { dcm_io_open_memory, dcm_io_close_memory, dcm_io_read_memory, @@ -384,10 +382,36 @@ DcmIOhandle *dcm_io_handle_create_from_memory(DcmError **error, }; DcmIOMemory memory = { + &methods, buffer, length, 0 }; - return dcm_io_handle_create(error, &io, &memory); + return dcm_io_create(error, &methods, &memory); +} + + +bool dcm_io_close(DcmError **error, DcmIO *io) +{ + return io->methods->close(error, 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 index bb246b9..f727d63 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -41,7 +41,7 @@ typedef struct _DcmParseState { DcmError **error; - DcmIOhandle *handle; + DcmIO *io; DcmParse *parse; bool byteswap; void *client; @@ -56,10 +56,7 @@ typedef struct _DcmParseState { static int64_t dcm_read(DcmParseState *state, char *buffer, int64_t length, int64_t *position) { - int64_t bytes_read = state->handle->io->read(state->error, - state->handle, - buffer, - length); + int64_t bytes_read = dcm_io_read(state->error, state->io, buffer, length); if (bytes_read < 0) { return bytes_read; } @@ -95,20 +92,14 @@ static bool dcm_require(DcmParseState *state, static bool dcm_seekset(DcmParseState *state, int64_t offset) { - int64_t new_offset = state->handle->io->seek(state->error, - state->handle, - offset, - SEEK_SET); + int64_t new_offset = dcm_io_seek(state->error, state->io, offset, SEEK_SET); return new_offset >= 0; } static bool dcm_seekcur(DcmParseState *state, int64_t offset, int64_t *position) { - int64_t new_offset = state->handle->io->seek(state->error, - state->handle, - offset, - SEEK_CUR); + int64_t new_offset = dcm_io_seek(state->error, state->io, offset, SEEK_CUR); if (new_offset < 0) { return false; } @@ -121,10 +112,7 @@ static bool dcm_seekcur(DcmParseState *state, int64_t offset, int64_t *position) static bool dcm_offset(DcmParseState *state, int64_t *offset) { - int64_t new_offset = state->handle->io->seek(state->error, - state->handle, - 0, - SEEK_CUR); + int64_t new_offset = dcm_io_seek(state->error, state->io, 0, SEEK_CUR); if (new_offset < 0) { return false; } @@ -140,10 +128,7 @@ static bool dcm_is_eof(DcmParseState *state) bool eof = true; char buffer[1]; - int64_t bytes_read = state->handle->io->read(NULL, - state->handle, - buffer, - 1); + int64_t bytes_read = dcm_io_read(NULL, state->io, buffer, 1); if (bytes_read > 0) { eof = false; int64_t position = 0; @@ -220,7 +205,7 @@ static bool read_uint32(DcmParseState *state, } -static bool read_tag(DcmParseState *state, uint32_t *value, int64_t *position) +static bool read_tag(DcmParseState *state, uint32_t *tag, int64_t *position) { uint16_t group, elem; @@ -229,7 +214,7 @@ static bool read_tag(DcmParseState *state, uint32_t *value, int64_t *position) return false; } - *value = ((uint32_t)group << 16) | elem; + *tag = ((uint32_t)group << 16) | elem; return true; } @@ -242,7 +227,7 @@ static bool parse_element_header(DcmParseState *state, uint32_t *length, int64_t *position) { - if (!read_tag(error, state->handle, tag, position)) { + if (!read_tag(state, tag, position)) { return false; } @@ -257,13 +242,13 @@ static bool parse_element_header(DcmParseState *state, return false; } - if (!read_uint32(error, filehandle, length, position)) { + if (!read_uint32(state, length, position)) { return false; } } else { // Value Representation char vr_str[3]; - if (!dcm_require(error, filehandle, vr_str, 2, position)) { + if (!dcm_require(state, vr_str, 2, position)) { return false; } vr_str[2] = '\0'; @@ -279,15 +264,15 @@ static bool parse_element_header(DcmParseState *state, 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)) { + 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(error, filehandle, &reserved, position) || - !read_uint32(error, filehandle, length, position)) { + if (!read_uint16(state, &reserved, position) || + !read_uint32(state, length, position)) { return false; } @@ -325,14 +310,8 @@ static bool parse_element_sequence(DcmParseState *state, dcm_log_debug("Read Item #%d.", index); uint32_t item_tag; uint32_t item_length; - if (!read_tag(state->error, - state->filehandle, - &item_tag, - position) || - !read_uint32(state->error, - state->filehandle, - &item_length, - position)) { + if (!read_tag(state, &item_tag, position) || + !read_uint32(state, &item_length, position)) { return false; } @@ -367,10 +346,7 @@ static bool parse_element_sequence(DcmParseState *state, int64_t item_position = 0; while (item_position < item_length) { // peek the next tag - if (!read_tag(state->error, - state->filehandle, - &item_tag, - &item_position)) { + if (!read_tag(state, &item_tag, &item_position)) { return false; } @@ -379,10 +355,7 @@ static bool parse_element_sequence(DcmParseState *state, "Encountered Item Delimination Tag.", index); // step over the tag length - if (!dcm_seekcur(state->error, - state->filehandle, - 4, - &item_position)) { + if (!dcm_seekcur(state, 4, &item_position)) { return false; } @@ -390,10 +363,7 @@ static bool parse_element_sequence(DcmParseState *state, } // back to start of element - if (!dcm_seekcur(state->error, - state->filehandle, - -4, - &item_position)) { + if (!dcm_seekcur(state, -4, &item_position)) { return false; } @@ -465,11 +435,7 @@ static bool parse_element_body(DcmParseState *state, value = input_buffer; } - if (!dcm_require(state->error, - state->filehandle, - value, - length, - position)) { + if (!dcm_require(state, value, length, position)) { if (value_free != NULL) { free(value_free); } @@ -611,18 +577,16 @@ static bool parse_toplevel_dataset(DcmParseState *state, /* Parse a dataset from a filehandle. */ bool dcm_parse_dataset(DcmError **error, - DcmIOHandle *handle, + DcmIO *io, bool implicit, - DcmParse *parse, + const DcmParse *parse, bool byteswap, void *client) { - DcmParseState state = { error, handle, parse, byteswap, client }; + DcmParseState state = { error, io, parse, byteswap, client }; int64_t position = 0; - if (!state.parse->parse_begin(state.error, state.client) || - !parse_toplevel_dataset(&state, implicit, &position) || - !state.parse->parse_end(state.error, state.client)) { + if (!parse_toplevel_dataset(&state, implicit, &position)) { return false; } diff --git a/src/pdicom.h b/src/pdicom.h index 89ffb8f..9f18c5d 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -108,9 +108,9 @@ typedef struct _DcmParse { DCM_EXTERN bool dcm_parse_dataset(DcmError **error, - DcmIOHandle *handle, + DcmIO *io, bool implicit, - DcmParse *parse, + const DcmParse *parse, bool byteswap, void *client); diff --git a/tools/dcm-dump.c b/tools/dcm-dump.c index 4a48af6..1de1768 100644 --- a/tools/dcm-dump.c +++ b/tools/dcm-dump.c @@ -17,7 +17,7 @@ int main(int argc, char *argv[]) DcmError *error = NULL; DcmDataSet *metadata = NULL; DcmDataSet *meta = NULL; - DcmFilehandle *filehandle = NULL; + DcmFilehandle *handle = NULL; dcm_log_level = DCM_LOG_ERROR; @@ -45,18 +45,18 @@ int main(int argc, char *argv[]) file_path = argv[i]; dcm_log_info("Read file '%s'", file_path); - filehandle = dcm_filehandle_create_from_file(&error, file_path); - if (filehandle == NULL) { + handle = dcm_filehandle_create_from_file(&error, file_path); + if (handle == 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); + meta = dcm_filehandle_read_file_meta(&error, handle); if (meta == NULL) { dcm_error_log(error); dcm_error_clear(&error); - dcm_filehandle_destroy(filehandle); + dcm_filehandle_destroy(handle); return EXIT_FAILURE; } @@ -64,21 +64,21 @@ int main(int argc, char *argv[]) dcm_dataset_print(meta, 0); dcm_log_info("Read metadata"); - metadata = dcm_filehandle_read_metadata(&error, filehandle); + metadata = dcm_filehandle_read_metadata(&error, handle); if (metadata == NULL) { dcm_error_log(error); dcm_error_clear(&error); - dcm_filehandle_destroy(filehandle); dcm_dataset_destroy(meta); + dcm_filehandle_destroy(handle); return EXIT_FAILURE; } printf("===Dataset===\n"); dcm_dataset_print(metadata, 0); - dcm_filehandle_destroy(filehandle); dcm_dataset_destroy(meta); dcm_dataset_destroy(metadata); + dcm_filehandle_destroy(handle); return EXIT_SUCCESS; } From bbcb8b37f2d6fd7eb928e62e32ccb20e4d2610b5 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 5 May 2023 18:49:21 +0100 Subject: [PATCH 07/82] file_meta reads correctly woo --- include/dicom/dicom.h | 17 + meson.build | 1 + src/dicom-data.c | 60 +- src/dicom-file.c | 260 ++++---- src/dicom-file.old.c | 1387 +++++++++++++++++++++++++++++++++++++++++ src/dicom-io.c | 4 +- src/dicom-parse.c | 169 ++++- src/pdicom.h | 17 +- 8 files changed, 1744 insertions(+), 171 deletions(-) create mode 100644 src/dicom-file.old.c diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index b3d1095..490bd3c 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -841,6 +841,23 @@ bool dcm_element_set_value_binary(DcmError **error, 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. * diff --git a/meson.build b/meson.build index a38f166..e60bc4d 100644 --- a/meson.build +++ b/meson.build @@ -153,6 +153,7 @@ library_sources = [ 'src/dicom-data.c', 'src/dicom-dict.c', 'src/dicom-file.c', + 'src/dicom-parse.c', ] libdicom = library( 'dicom', diff --git a/src/dicom-data.c b/src/dicom-data.c index 422138e..ac448c3 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -883,6 +883,64 @@ 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) +{ + switch (dcm_dict_vr_class(element->vr)) + { + case DCM_CLASS_STRING_SINGLE: + case DCM_CLASS_STRING_MULTI: + if (!dcm_element_set_value_string(error, element, value, steal)) { + return false; + } + break; + + case DCM_CLASS_NUMERIC: + size_t 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_CLASS_BINARY: + if (!dcm_element_set_value_binary(error, + element, + value, + length, + steal)) { + return false; + } + + break; + + case DCM_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, @@ -1142,7 +1200,7 @@ 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 + // in any case, we can't display the keyword printf("%*.*s (%04X,%04X) | %s", num_indent, num_indent, diff --git a/src/dicom-file.c b/src/dicom-file.c index abeb65a..1b011ac 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -48,12 +48,17 @@ struct PixelDescription { struct _DcmFilehandle { DcmIO *io; + DcmDataSet *file_meta; DcmDataSet *meta; int64_t offset; char *transfer_syntax_uid; int64_t pixel_data_offset; uint64_t *extended_offset_table; bool byteswap; + + // push and pop these while we parse + UT_array *dataset_stack; + UT_array *sequence_stack; }; @@ -80,6 +85,7 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) } filehandle->io = io; + filehandle->file_meta = NULL; filehandle->meta = NULL; filehandle->offset = 0; filehandle->transfer_syntax_uid = NULL; @@ -87,6 +93,9 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) filehandle->extended_offset_table = NULL; filehandle->byteswap = is_big_endian(); + utarray_new(filehandle->dataset_stack, &ut_ptr_icd); + utarray_new(filehandle->sequence_stack, &ut_ptr_icd); + return filehandle; } @@ -122,7 +131,19 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) free(filehandle->transfer_syntax_uid); } + if (filehandle->file_meta) { + dcm_dataset_destroy(filehandle->file_meta); + } + + if (filehandle->meta) { + dcm_dataset_destroy(filehandle->meta); + } + (void) dcm_io_close(NULL, filehandle->io); + + utarray_free(filehandle->dataset_stack); + utarray_free(filehandle->sequence_stack); + free(filehandle); } } @@ -695,135 +716,6 @@ DcmElement *read_element(DcmError **error, } -DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, - DcmFilehandle *filehandle) -{ - const bool implicit = false; - - int64_t position; - uint16_t group_number; - DcmElement *element; - - DcmDataSet *file_meta = dcm_dataset_create(error); - if (file_meta == NULL) { - return NULL; - } - - position = 0; - - // 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'; - - // 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; - } - - position = 0; - - // File Meta Information Group Length - element = read_element(error, filehandle, &position, implicit); - if (element == NULL) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - 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; - } - - 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; - } - - group_number = dcm_element_get_group_number(element); - if (group_number != 0x0002) { - dcm_element_destroy(element); - break; - } - - if (!read_element_body(error, element, filehandle, - length, &position, implicit)) { - dcm_element_destroy(element); - dcm_dataset_destroy(file_meta); - return NULL; - } - - // many DICOMs have a FileMetaInformationVersion element, but - // not all ... ignore the version number if present - if (dcm_element_get_tag(element) == 0x00020001) { - dcm_element_destroy(element); - continue; - } - - if (!dcm_dataset_insert(error, file_meta, element)) { - dcm_element_destroy(element); - dcm_dataset_destroy(file_meta); - return NULL; - } - } - - if (!dcm_offset(error, filehandle, &filehandle->offset)) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - 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; - } - - 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; - } - - dcm_dataset_lock(file_meta); - - return file_meta; -} - - DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, DcmFilehandle *filehandle) { @@ -1385,3 +1277,113 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, desc.photometric_interpretation, filehandle->transfer_syntax_uid); } + + +static bool parse_file_meta_element_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + DcmElement *element = dcm_element_create(error, tag, vr); + if (element == NULL) { + return false; + } + + if (!dcm_element_set_value(error, element, value, length, false) || + !dcm_dataset_insert(error, filehandle->file_meta, element)) { + dcm_element_destroy(element); + return false; + } + + return true; +} + + +static DcmParse parse_file_meta = { + .element_create = parse_file_meta_element_create +}; + + +DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, + DcmFilehandle *filehandle) +{ + // already read the file metadata + if (filehandle->file_meta != NULL) { + return filehandle->file_meta; + } + + const bool implicit = false; + + int64_t position = 0; + + // File Preamble + char preamble[129]; + if (!dcm_require(error, + filehandle, preamble, sizeof(preamble) - 1, &position)) { + return NULL; + } + preamble[128] = '\0'; + + // DICOM Prefix + char prefix[5]; + if (!dcm_require(error, + filehandle, prefix, sizeof(prefix) - 1, &position)) { + 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."); + return NULL; + } + + filehandle->file_meta = dcm_dataset_create(error); + if (filehandle->file_meta == NULL) { + return NULL; + } + + // parse the group elements into ->file_meta + if (!dcm_parse_group(error, + filehandle->io, + implicit, + &parse_file_meta, + false, + filehandle)) { + return false; + } + + // record the start point for the image metadata + if (!dcm_offset(error, filehandle, &filehandle->offset)) { + return NULL; + } + + DcmElement *element = dcm_dataset_get(error, + filehandle->file_meta, + 0x00020010); + if (element == NULL) { + return NULL; + } + + const char *transfer_syntax_uid; + if (!dcm_element_get_value_string(error, + element, + 0, + &transfer_syntax_uid)) { + return NULL; + } + + filehandle->transfer_syntax_uid = dcm_strdup(error, transfer_syntax_uid); + if (filehandle->transfer_syntax_uid == NULL) { + return NULL; + } + + dcm_dataset_lock(filehandle->file_meta); + + return filehandle->file_meta; +} diff --git a/src/dicom-file.old.c b/src/dicom-file.old.c new file mode 100644 index 0000000..abeb65a --- /dev/null +++ b/src/dicom-file.old.c @@ -0,0 +1,1387 @@ +/* + * 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" + + +#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; +}; + + +struct _DcmFilehandle { + DcmIO *io; + DcmDataSet *meta; + int64_t offset; + char *transfer_syntax_uid; + int64_t pixel_data_offset; + uint64_t *extended_offset_table; + bool byteswap; +}; + + +/* 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; +} + + +DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) +{ + DcmFilehandle *filehandle = DCM_NEW(error, DcmFilehandle); + if (filehandle == NULL) { + return NULL; + } + + filehandle->io = io; + 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(); + + return 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 dcm_filehandle_create(error, io); +} + + +DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, + 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); +} + + +void dcm_filehandle_destroy(DcmFilehandle *filehandle) +{ + if (filehandle) { + if (filehandle->transfer_syntax_uid) { + free(filehandle->transfer_syntax_uid); + } + + (void) dcm_io_close(NULL, filehandle->io); + free(filehandle); + } +} + + +static int64_t dcm_read(DcmError **error, DcmFilehandle *filehandle, + char *buffer, int64_t length, int64_t *position) +{ + int64_t bytes_read = dcm_io_read(error, filehandle->io, buffer, length); + if (bytes_read < 0) { + return bytes_read; + } + + *position += bytes_read; + + return bytes_read; +} + + +static bool dcm_require(DcmError **error, DcmFilehandle *filehandle, + char *buffer, int64_t length, int64_t *position) +{ + while (length > 0) { + int64_t bytes_read = dcm_read(error, + filehandle, buffer, length, position); + + if (bytes_read < 0) { + return false; + } else if (bytes_read == 0) { + dcm_error_set(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_seekset(DcmError **error, + DcmFilehandle *filehandle, int64_t offset) +{ + 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) +{ + int64_t new_offset = dcm_io_seek(error, filehandle->io, offset, SEEK_CUR); + if (new_offset < 0) { + return false; + } + + *position += offset; + + return true; +} + + +static bool dcm_offset(DcmError **error, + DcmFilehandle *filehandle, int64_t *offset) +{ + int64_t new_offset = dcm_io_seek(error, filehandle->io, 0, SEEK_CUR); + if (new_offset < 0) { + return false; + } + + *offset = new_offset; + + return true; +} + + +static bool dcm_is_eof(DcmFilehandle *filehandle) +{ + int64_t position = 0; + bool eof = true; + + char buffer[1]; + int64_t bytes_read = dcm_io_read(NULL, filehandle->io, buffer, 1); + if (bytes_read > 0) { + eof = false; + (void) dcm_seekcur(NULL, filehandle, -1, &position); + } + + return eof; +} + + +static void byteswap(char *data, size_t length, size_t size) +{ + assert(length >= size); + + if (size > 1) { + assert(length % size == 0); + assert(size % 2 == 0); + + size_t half_size = size / 2; + + 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; + } + } + } +} + + +static bool read_uint16(DcmError **error, DcmFilehandle *filehandle, + uint16_t *value, int64_t *position) +{ + union { + uint16_t i; + char c[2]; + } buffer; + + if (!dcm_require(error, filehandle, buffer.c, 2, position)) { + return false; + } + + if (filehandle->byteswap) { + byteswap(buffer.c, 2, 2); + } + + *value = buffer.i; + + return true; +} + + +static bool read_uint32(DcmError **error, DcmFilehandle *filehandle, + uint32_t *value, int64_t *position) +{ + union { + uint32_t i; + char c[4]; + } buffer; + + if (!dcm_require(error, filehandle, buffer.c, 4, position)) { + return false; + } + + if (filehandle->byteswap) { + byteswap(buffer.c, 4, 4); + } + + *value = buffer.i; + + return true; +} + + +static bool read_tag(DcmError **error, DcmFilehandle *filehandle, + uint32_t *value, int64_t *position) +{ + uint16_t group, elem; + + if (!read_uint16(error, filehandle, &group, position) || + !read_uint16(error, filehandle, &elem, position)) { + return false; + } + + *value = ((uint32_t)group << 16) | elem; + + return true; +} + + +static bool read_iheader(DcmError **error, DcmFilehandle *filehandle, + uint32_t *item_tag, uint32_t *item_length, int64_t *position) +{ + if (!read_tag(error, filehandle, item_tag, position) || + !read_uint32(error, filehandle, item_length, position)) { + return false; + } + + return true; +} + + +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; +} + + +static DcmElement *read_element_header(DcmError **error, + DcmFilehandle *filehandle, + uint32_t *length, + int64_t *position, + bool implicit) +{ + uint32_t tag; + if (!read_tag(error, filehandle, &tag, position)) { + 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); + } + + if (!read_uint32(error, filehandle, length, position)) { + return NULL; + } + } else { + // Value Representation + char vr_str[3]; + if (!dcm_require(error, filehandle, vr_str, 2, position)) { + 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); + 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 (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; + } + } + } + + return dcm_element_create(error, tag, vr); +} + + +// fwd ref this +static DcmElement *read_element(DcmError **error, + DcmFilehandle *filehandle, + int64_t *position, + bool implicit); + +static bool read_element_sequence(DcmError **error, + DcmFilehandle *filehandle, + DcmSequence *sequence, + uint32_t length, + int64_t *position, + bool implicit) +{ + 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; + } + + 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(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 (length == 0xFFFFFFFF) { + dcm_log_debug("Item #%d has undefined length.", index); + } else { + dcm_log_debug("Item #%d has defined length %d.", + index, item_length); + } + + DcmDataSet *dataset = dcm_dataset_create(error); + if (dataset == NULL) { + 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; + } + + // back to start of element + if (!dcm_seekcur(error, filehandle, -4, &item_position)) { + dcm_dataset_destroy(dataset); + return false; + } + + DcmElement *element = read_element(error, filehandle, + &item_position, implicit); + if (element == NULL) { + dcm_dataset_destroy(dataset); + return false; + } + + if (!dcm_dataset_insert(error, dataset, element)) { + dcm_dataset_destroy(dataset); + dcm_element_destroy(element); + return false; + } + } + + seq_position += item_position; + + if (!dcm_sequence_append(error, sequence, dataset)) { + dcm_dataset_destroy(dataset); + } + + index += 1; + } + + *position += seq_position; + + return true; +} + + +static bool read_element_body(DcmError **error, + DcmElement *element, + DcmFilehandle *filehandle, + uint32_t length, + int64_t *position, + bool implicit) +{ + 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; + + dcm_log_debug("Read Data Element body '%08X'", tag); + + 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; + + 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; + + char *values = DCM_MALLOC(error, length); + if (values == NULL) { + return false; + } + + if (!dcm_require(error, filehandle, values, length, position)) { + free(values); + return false; + } + + if (filehandle->byteswap) { + byteswap(values, length, size); + } + + if( !dcm_element_set_value_numeric_multi(error, + element, + (int *) values, + vm, + true)) { + free(values); + return false; + } + + break; + + case DCM_CLASS_BINARY: + value = DCM_MALLOC(error, length); + if (value == NULL) { + return false; + } + + if (!dcm_require(error, filehandle, value, length, position)) { + free(value); + return false; + } + + if( !dcm_element_set_value_binary(error, element, + value, length, true)) { + free(value); + return false; + } + + break; + + case DCM_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); + } + + DcmSequence *seq = dcm_sequence_create(error); + if (seq == NULL) { + return NULL; + } + + 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; + } + + return true; +} + + +DcmElement *read_element(DcmError **error, + DcmFilehandle *filehandle, + int64_t *position, + bool implicit) +{ + DcmElement *element; + + uint32_t length; + element = read_element_header(error, + filehandle, &length, position, implicit); + if (element == NULL) { + return NULL; + } + + if (!read_element_body(error, element, filehandle, + length, position, implicit)) { + dcm_element_destroy(element); + return NULL; + } + + return element; +} + + +DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, + DcmFilehandle *filehandle) +{ + const bool implicit = false; + + int64_t position; + uint16_t group_number; + DcmElement *element; + + DcmDataSet *file_meta = dcm_dataset_create(error); + if (file_meta == NULL) { + return NULL; + } + + position = 0; + + // 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'; + + // 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; + } + + position = 0; + + // File Meta Information Group Length + element = read_element(error, filehandle, &position, implicit); + if (element == NULL) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + 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; + } + + 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; + } + + group_number = dcm_element_get_group_number(element); + if (group_number != 0x0002) { + dcm_element_destroy(element); + break; + } + + if (!read_element_body(error, element, filehandle, + length, &position, implicit)) { + dcm_element_destroy(element); + dcm_dataset_destroy(file_meta); + return NULL; + } + + // many DICOMs have a FileMetaInformationVersion element, but + // not all ... ignore the version number if present + if (dcm_element_get_tag(element) == 0x00020001) { + dcm_element_destroy(element); + continue; + } + + if (!dcm_dataset_insert(error, file_meta, element)) { + dcm_element_destroy(element); + dcm_dataset_destroy(file_meta); + return NULL; + } + } + + if (!dcm_offset(error, filehandle, &filehandle->offset)) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + 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; + } + + 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; + } + + dcm_dataset_lock(file_meta); + + return file_meta; +} + + +DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, + DcmFilehandle *filehandle) +{ + bool implicit; + + if (filehandle->offset == 0) { + DcmDataSet *meta = dcm_filehandle_read_file_meta(error, filehandle); + if (meta== NULL) { + return NULL; + } + 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; + } + } + + DcmDataSet *dataset = dcm_dataset_create(error); + if (dataset == NULL) { + return NULL; + } + + int64_t position = 0; + for (;;) { + if (dcm_is_eof(filehandle)) { + dcm_log_info("Stop reading Data Set. Reached end of filehandle."); + break; + } + + uint32_t length; + DcmElement *element = read_element_header(error, + filehandle, + &length, + &position, + implicit); + if (element == NULL) { + dcm_dataset_destroy(dataset); + return NULL; + } + 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 (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; + } + + } 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; + } + } + + if (!dcm_offset(error, + filehandle, &filehandle->pixel_data_offset)) { + dcm_dataset_destroy(dataset); + return NULL; + } + + 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; + } + + 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; + } + } + + 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) +{ + uint64_t value; + + dcm_log_debug("Reading Basic Offset Table."); + + 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); + return NULL; + } + + uint32_t num_frames; + if (!get_num_frames(error, metadata, &num_frames)) { + return NULL; + } + + 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_seekset(error, filehandle, filehandle->pixel_data_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"); + return NULL; + } + + // The header of the BOT Item + if (!read_iheader(error, filehandle, &tag, &length, &position)) { + return NULL; + } + 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 NULL; + } + + ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); + if (offsets == NULL) { + 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) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "No Basic Offset Table, " + "and no Extended Offset Table"); + free(offsets); + return NULL; + } + + dcm_log_info("Found Extended Offset Table."); + + 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; + } + + // FIXME is this correct? + first_frame_offset = position; + } + + return dcm_bot_create(error, offsets, num_frames, first_frame_offset); +} + + +static bool set_pixel_description(DcmError **error, + struct PixelDescription *desc, + const DcmDataSet *metadata) +{ + DcmElement *element; + int64_t value; + const char *string; + + element = dcm_dataset_get(error, metadata, 0x00280010); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->rows = value; + + element = dcm_dataset_get(error, metadata, 0x00280011); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->columns = value; + + 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; + + 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; + + 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; + + 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; + + return true; +} + + +DcmBOT *dcm_filehandle_build_bot(DcmError **error, + DcmFilehandle *filehandle, + DcmDataSet *metadata) +{ + uint64_t i; + + dcm_log_debug("Building Basic Offset Table."); + + uint32_t num_frames; + if (!get_num_frames(error, metadata, &num_frames)) { + return NULL; + } + + 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_seekset(error, filehandle, filehandle->pixel_data_offset)) { + return NULL; + } + + // we measure offsets from this point + int64_t position = 0; + + 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; + } + + ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); + if (offsets == NULL) { + return NULL; + } + + 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; + } + + 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; + } + + // Move filehandlepointer to the first byte of first Frame item + if (!dcm_seekcur(error, filehandle, length, &position)) { + free(offsets); + } + + // and that's the offset to the first frame + first_frame_offset = position; + + // now measure positions from the start of the first frame + position = 0; + + i = 0; + while (true) { + if (!read_iheader(error, filehandle, &tag, &length, &position)) { + free(offsets); + return NULL; + } + + if (tag == TAG_SQ_DELIM) { + break; + } + + 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; + } + + if (dcm_is_eof(filehandle)) { + break; + } + + // step back to the start of the item for this frame + offsets[i] = position - 8; + + if (!dcm_seekcur(error, filehandle, length, &position)) { + free(offsets); + return NULL; + } + + i += 1; + } + + 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; + } + + return dcm_bot_create(error, offsets, num_frames, first_frame_offset); +} + + +DcmFrame *dcm_filehandle_read_frame(DcmError **error, + DcmFilehandle *filehandle, + DcmDataSet *metadata, + DcmBOT *bot, + uint32_t number) +{ + uint32_t length; + + 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; + } + + 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; + } + + struct PixelDescription desc; + if (!set_pixel_description(error, &desc, metadata)) { + return NULL; + } + + 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; + } + + 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; + } + + char *value = DCM_MALLOC(error, length); + if (value == NULL) { + return NULL; + } + if (!dcm_require(error, filehandle, value, length, &position)) { + free(value); + return NULL; + } + + 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); +} diff --git a/src/dicom-io.c b/src/dicom-io.c index ce67d9b..b315124 100644 --- a/src/dicom-io.c +++ b/src/dicom-io.c @@ -89,7 +89,7 @@ static DcmIO *dcm_io_open_file(DcmError **error, void *client) const char *filename = (const char *) client; file->filename = dcm_strdup(error, filename); if (file->filename == NULL) { - dcm_io_close_file(error, (DcmIO *)file); + (void) dcm_io_close_file(error, (DcmIO *)file); return NULL; } @@ -120,7 +120,7 @@ static DcmIO *dcm_io_open_file(DcmError **error, void *client) dcm_error_set(error, DCM_ERROR_CODE_IO, "Unable to open filehandle", "Unable to open %s - %s", file->filename, strerror(open_errno)); - dcm_io_close_file(error, (DcmIO *)file); + (void) dcm_io_close_file(error, (DcmIO *)file); return NULL; } diff --git a/src/dicom-parse.c b/src/dicom-parse.c index f727d63..9c76ad4 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -42,7 +42,7 @@ typedef struct _DcmParseState { DcmError **error; DcmIO *io; - DcmParse *parse; + const DcmParse *parse; bool byteswap; void *client; @@ -76,7 +76,7 @@ static bool dcm_require(DcmParseState *state, if (bytes_read < 0) { return false; } else if (bytes_read == 0) { - dcm_error_set(error, DCM_ERROR_CODE_IO, + dcm_error_set(state->error, DCM_ERROR_CODE_IO, "End of filehandle", "Needed %zd bytes beyond end of filehandle", length); return false; @@ -220,6 +220,13 @@ static bool read_tag(DcmParseState *state, uint32_t *tag, int64_t *position) } +/* This is used recursively. + */ +static bool parse_element(DcmParseState *state, + bool implicit, + int64_t *position); + + static bool parse_element_header(DcmParseState *state, bool implicit, uint32_t *tag, @@ -261,7 +268,7 @@ static bool parse_element_header(DcmParseState *state, return false; } - if (dcm_dict_vr_header_length(vr) == 2) { + 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)) { @@ -293,15 +300,11 @@ static bool parse_element_header(DcmParseState *state, static bool parse_element_sequence(DcmParseState *state, bool implicit, - uint32_t tag, - DcmVR vr, uint32_t seq_length, int64_t *position) { - if (state->parse->sequence_begin(error, state->client, - tag, - vr, - seq_length)) { + if (state->parse->sequence_begin && + !state->parse->sequence_begin(state->error, state->client)) { return false; } @@ -339,7 +342,8 @@ static bool parse_element_sequence(DcmParseState *state, index, item_length); } - if (state->parse->dataset_start(state->error, state->client)) { + if (state->parse->dataset_begin && + !state->parse->dataset_begin(state->error, state->client)) { return false; } @@ -372,18 +376,18 @@ static bool parse_element_sequence(DcmParseState *state, } } - seq_position += item_position; + *position += item_position; - if (state->parse->dataset_end(state->error, state->client)) { + if (state->parse->dataset_end && + !state->parse->dataset_end(state->error, state->client)) { return false; } index += 1; } - *position += seq_position; - - if (state->parse->sequence_end(state->error, state->client)) { + if (state->parse->sequence_end && + !state->parse->sequence_end(state->error, state->client)) { return false; } @@ -396,11 +400,10 @@ static bool parse_element_body(DcmParseState *state, uint32_t tag, DcmVR vr, uint32_t length, - int64_t position) + int64_t *position) { DcmVRClass klass = dcm_dict_vr_class(vr); size_t size = dcm_dict_vr_size(vr); - uint32_t vm = length / size; char *value; char *value_free = NULL; @@ -415,9 +418,8 @@ static bool parse_element_body(DcmParseState *state, case DCM_CLASS_BINARY: if (klass == DCM_CLASS_NUMERIC) { // all numeric classes have a size - g_assert (size != 0); if (length % size != 0) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, "Reading of Data Element failed", "Bad length for tag '%08X'", tag); @@ -452,17 +454,18 @@ static bool parse_element_body(DcmParseState *state, } if (klass == DCM_CLASS_NUMERIC) { - if (filehandle->byteswap) { + if (state->byteswap) { byteswap(value, length, size); } } - if (state->parse->element_create(error, - state->client, - tag, - vr, - value, - length)) { + if (state->parse->element_create && + !state->parse->element_create(state->error, + state->client, + tag, + vr, + value, + length)) { if (value_free != NULL) { free(value_free); } @@ -489,8 +492,6 @@ static bool parse_element_body(DcmParseState *state, int64_t seq_position = 0; if (!parse_element_sequence(state, implicit, - tag, - vr, length, &seq_position)) { return false; @@ -500,7 +501,7 @@ static bool parse_element_body(DcmParseState *state, break; default: - dcm_error_set(error, DCM_ERROR_CODE_PARSE, + dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, "Reading of Data Element failed", "Data Element '%08X' has unexpected " "Value Representation", tag); @@ -533,12 +534,13 @@ static bool parse_toplevel_dataset(DcmParseState *state, bool implicit, int64_t *position) { - if (!state->parse->dataset_begin(state->error, state->client)) { + if (state->parse->dataset_begin && + !state->parse->dataset_begin(state->error, state->client)) { return false; } for (;;) { - if (dcm_is_eof(state->filehandle)) { + if (dcm_is_eof(state)) { dcm_log_info("Stop reading Data Set. Reached end of filehandle."); break; } @@ -557,7 +559,8 @@ static bool parse_toplevel_dataset(DcmParseState *state, break; } - if (state->parse->stop(state->client, implicit, tag, vr, length)) { + if (state->parse->stop && + state->parse->stop(state->client, implicit, tag, vr, length)) { break; } @@ -566,7 +569,8 @@ static bool parse_toplevel_dataset(DcmParseState *state, } } - if (!state->parse->dataset_end(state->error, state->client)) { + if (state->parse->dataset_end && + !state->parse->dataset_end(state->error, state->client)) { return false; } @@ -583,7 +587,13 @@ bool dcm_parse_dataset(DcmError **error, bool byteswap, void *client) { - DcmParseState state = { error, io, parse, byteswap, client }; + DcmParseState state = { + .error = error, + .io = io, + .parse = parse, + .byteswap = byteswap, + .client = client + }; int64_t position = 0; if (!parse_toplevel_dataset(&state, implicit, &position)) { @@ -592,3 +602,94 @@ bool dcm_parse_dataset(DcmError **error, 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, + bool byteswap, + void *client) +{ + DcmParseState state = { + .error = error, + .io = io, + .parse = parse, + .byteswap = byteswap, + .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, + implicit, + &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, + implicit, + &tag, + &vr, + &length, + &element_start)) { + return false; + } + + // stop if we run out of the group, or the stop function triggers + if ((tag >> 16) != group_number || + (state.parse->stop && + state.parse->stop(state.client, implicit, 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, implicit, tag, vr, length, &position)) { + return false; + } + } + + if (state.parse->dataset_end && + !state.parse->dataset_end(state.error, state.client)) { + return false; + } + + return true; +} + diff --git a/src/pdicom.h b/src/pdicom.h index 9f18c5d..eac6284 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -87,13 +87,13 @@ char **dcm_parse_character_string(DcmError **error, } typedef struct _DcmParse { - bool (*dataset_begin)(DcmError *, void *client); - bool (*dataset_end)(DcmError *, void *client); + bool (*dataset_begin)(DcmError **, void *client); + bool (*dataset_end)(DcmError **, void *client); - bool (*sequence_begin)(DcmError *, void *client); - bool (*sequence_end)(DcmError *, void *client); + bool (*sequence_begin)(DcmError **, void *client); + bool (*sequence_end)(DcmError **, void *client); - bool (*element_create)(DcmError *, void *client, + bool (*element_create)(DcmError **, void *client, uint32_t tag, DcmVR vr, char *value, @@ -114,3 +114,10 @@ bool dcm_parse_dataset(DcmError **error, bool byteswap, void *client); +DCM_EXTERN +bool dcm_parse_group(DcmError **error, + DcmIO *io, + bool implicit, + const DcmParse *parse, + bool byteswap, + void *client); From 97bc075db2e7738a252e6291d2efd7b672188e22 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 13 May 2023 16:54:11 +0100 Subject: [PATCH 08/82] parses leica dicoms correctly test suite still crashes though, needs some cleaning up --- src/dicom-file.c | 420 ++++++++++++++++++++++++++++++---------------- src/dicom-parse.c | 25 ++- src/pdicom.h | 11 +- tools/dcm-dump.c | 1 + 4 files changed, 305 insertions(+), 152 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index 1b011ac..9b50db6 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -48,8 +48,6 @@ struct PixelDescription { struct _DcmFilehandle { DcmIO *io; - DcmDataSet *file_meta; - DcmDataSet *meta; int64_t offset; char *transfer_syntax_uid; int64_t pixel_data_offset; @@ -76,7 +74,6 @@ static bool is_big_endian(void) return bint.c[0] == 1; } - DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) { DcmFilehandle *filehandle = DCM_NEW(error, DcmFilehandle); @@ -85,14 +82,11 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) } filehandle->io = io; - filehandle->file_meta = 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(); - utarray_new(filehandle->dataset_stack, &ut_ptr_icd); utarray_new(filehandle->sequence_stack, &ut_ptr_icd); @@ -124,21 +118,39 @@ DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, } +static void dcm_filehandle_clear(DcmFilehandle *filehandle) +{ + unsigned int i; + + 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->dataset_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->file_meta) { - dcm_dataset_destroy(filehandle->file_meta); - } - - if (filehandle->meta) { - dcm_dataset_destroy(filehandle->meta); - } - (void) dcm_io_close(NULL, filehandle->io); utarray_free(filehandle->dataset_stack); @@ -716,118 +728,6 @@ DcmElement *read_element(DcmError **error, } -DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, - DcmFilehandle *filehandle) -{ - bool implicit; - - if (filehandle->offset == 0) { - DcmDataSet *meta = dcm_filehandle_read_file_meta(error, filehandle); - if (meta== NULL) { - return NULL; - } - 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; - } - } - - DcmDataSet *dataset = dcm_dataset_create(error); - if (dataset == NULL) { - return NULL; - } - - int64_t position = 0; - for (;;) { - if (dcm_is_eof(filehandle)) { - dcm_log_info("Stop reading Data Set. Reached end of filehandle."); - break; - } - - uint32_t length; - DcmElement *element = read_element_header(error, - filehandle, - &length, - &position, - implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return NULL; - } - 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 (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; - } - - } 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; - } - } - - if (!dcm_offset(error, - filehandle, &filehandle->pixel_data_offset)) { - dcm_dataset_destroy(dataset); - return NULL; - } - - 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; - } - - 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; - } - } - - dcm_dataset_lock(dataset); - - return dataset; -} - - static bool get_num_frames(DcmError **error, const DcmDataSet *metadata, uint32_t *number_of_frames) @@ -1286,7 +1186,7 @@ static bool parse_file_meta_element_create(DcmError **error, char *value, uint32_t length) { - DcmFilehandle *filehandle = (DcmFilehandle *) client; + DcmDataSet *file_meta = (DcmDataSet *) client; DcmElement *element = dcm_element_create(error, tag, vr); if (element == NULL) { @@ -1294,7 +1194,7 @@ static bool parse_file_meta_element_create(DcmError **error, } if (!dcm_element_set_value(error, element, value, length, false) || - !dcm_dataset_insert(error, filehandle->file_meta, element)) { + !dcm_dataset_insert(error, file_meta, element)) { dcm_element_destroy(element); return false; } @@ -1311,11 +1211,6 @@ static DcmParse parse_file_meta = { DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, DcmFilehandle *filehandle) { - // already read the file metadata - if (filehandle->file_meta != NULL) { - return filehandle->file_meta; - } - const bool implicit = false; int64_t position = 0; @@ -1343,8 +1238,8 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, return NULL; } - filehandle->file_meta = dcm_dataset_create(error); - if (filehandle->file_meta == NULL) { + DcmDataSet *file_meta = dcm_dataset_create(error); + if (file_meta == NULL) { return NULL; } @@ -1354,19 +1249,20 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, implicit, &parse_file_meta, false, - filehandle)) { + file_meta)) { + dcm_dataset_destroy(file_meta); return false; } // record the start point for the image metadata if (!dcm_offset(error, filehandle, &filehandle->offset)) { + dcm_dataset_destroy(file_meta); return NULL; } - DcmElement *element = dcm_dataset_get(error, - filehandle->file_meta, - 0x00020010); + DcmElement *element = dcm_dataset_get(error, file_meta, 0x00020010); if (element == NULL) { + dcm_dataset_destroy(file_meta); return NULL; } @@ -1375,15 +1271,257 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, element, 0, &transfer_syntax_uid)) { + dcm_dataset_destroy(file_meta); return NULL; } filehandle->transfer_syntax_uid = dcm_strdup(error, transfer_syntax_uid); if (filehandle->transfer_syntax_uid == NULL) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + dcm_dataset_lock(file_meta); + + return file_meta; +} + + +static bool parse_meta_dataset_begin(DcmError **error, + void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + DcmDataSet *dataset = dcm_dataset_create(error); + if (dataset == NULL) { + return false; + } + + //printf("parse_meta_dataset_begin: created dataset %p\n", dataset); + + utarray_push_back(filehandle->dataset_stack, &dataset); + + return true; +} + + +static bool parse_meta_dataset_end(DcmError **error, + void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + DcmDataSet *dataset = *((DcmDataSet **) + utarray_back(filehandle->dataset_stack)); + DcmSequence *sequence = *((DcmSequence **) + utarray_back(filehandle->sequence_stack)); + + //printf("parse_meta_dataset_end: end dataset %p, adding to sequence %p\n", + // dataset, sequence); + + if (!dcm_sequence_append(error, sequence, dataset)) { + return false; + } + + utarray_pop_back(filehandle->dataset_stack); + + return true; +} + + +static bool parse_meta_sequence_begin(DcmError **error, + void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + DcmSequence *sequence = dcm_sequence_create(error); + if (sequence == NULL) { + return false; + } + + //printf("parse_meta_sequence_begin: created sequence %p\n", sequence); + + utarray_push_back(filehandle->sequence_stack, &sequence); + + return true; +} + + +static bool parse_meta_sequence_end(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + USED(length); + + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + DcmElement *element = dcm_element_create(error, tag, vr); + if (element == NULL) { + return false; + } + + DcmSequence *sequence = *((DcmSequence **) + utarray_back(filehandle->sequence_stack)); + if (!dcm_element_set_value_sequence(error, element, sequence)) { + dcm_element_destroy(element); + return false; + } + + 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; + } + + //printf("parse_meta_sequence_end: end sequence %p\n", sequence); + //printf(" setting as value for element %p\n", element); + //printf(" adding element to dataset %p\n", dataset); + + return true; +} + + +// FIXME share with file_meta parse +static bool parse_meta_element_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) +{ + USED(client); + + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + DcmElement *element = dcm_element_create(error, tag, vr); + if (element == NULL) { + return false; + } + + 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; + } + + //printf("parse_meta_element_create: create element %p on dataset %p\n", + // element, dataset); + + return true; +} + + +static bool parse_meta_stop(void *client, + bool implicit, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + USED(client); + USED(implicit); + USED(vr); + USED(length); + + return tag == TAG_PIXEL_DATA || + tag == TAG_FLOAT_PIXEL_DATA || + tag == TAG_DOUBLE_PIXEL_DATA; +} + + +static DcmParse parse_meta = { + .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, +}; + + +DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, + DcmFilehandle *filehandle) +{ + bool implicit; + + if (filehandle->offset == 0) { + DcmDataSet *file_meta = dcm_filehandle_read_file_meta(error, + filehandle); + if (file_meta == NULL) { + return NULL; + } + dcm_dataset_destroy(file_meta); + } + + if (!dcm_seekset(error, filehandle, filehandle->offset)) { return NULL; } - dcm_dataset_lock(filehandle->file_meta); + implicit = false; + if (filehandle->transfer_syntax_uid) { + if (strcmp(filehandle->transfer_syntax_uid, "1.2.840.10008.1.2") == 0) { + implicit = true; + } + } + + // sanity ... the parse stacks should be empty + if (utarray_len(filehandle->dataset_stack) != 0 || + utarray_len(filehandle->sequence_stack) != 0) { + abort(); + } + + DcmSequence *sequence = dcm_sequence_create(error); + if (sequence == NULL) { + return NULL; + } + utarray_push_back(filehandle->sequence_stack, &sequence); + + // parse as far as the pixel data + if (!dcm_parse_dataset(error, + filehandle->io, + implicit, + &parse_meta, + filehandle->byteswap, + filehandle)) { + // destroy any old stack values, perhaps from a previous failed parse + dcm_filehandle_clear(filehandle); + return false; + } + + // the file pointer will have been left at the start of pixel data + if (!dcm_offset(error, + filehandle, &filehandle->pixel_data_offset)) { + 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(); + } + if (dcm_sequence_count(sequence) != 1) { + abort(); + } + + // FIXME ... add dcm_sequence_steal() so we can destroy sequence without + // also destroying meta + // right now we leak sequence + DcmDataSet *meta = dcm_sequence_get(error, sequence, 0); + if (meta == NULL ) { + return false; + } + + // we need to pop sequence off the dataset stack to stop it being destroyed + utarray_pop_back(filehandle->sequence_stack); + + dcm_dataset_lock(meta); - return filehandle->file_meta; + return meta; } diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 9c76ad4..0e50862 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -90,34 +90,35 @@ static bool dcm_require(DcmParseState *state, } +/* will we need these? static bool dcm_seekset(DcmParseState *state, int64_t offset) { int64_t new_offset = dcm_io_seek(state->error, state->io, offset, SEEK_SET); return new_offset >= 0; } - -static bool dcm_seekcur(DcmParseState *state, int64_t offset, int64_t *position) +static bool dcm_offset(DcmParseState *state, int64_t *offset) { - int64_t new_offset = dcm_io_seek(state->error, state->io, offset, SEEK_CUR); + int64_t new_offset = dcm_io_seek(state->error, state->io, 0, SEEK_CUR); if (new_offset < 0) { return false; } - *position += offset; + *offset = new_offset; return true; } + */ -static bool dcm_offset(DcmParseState *state, int64_t *offset) +static bool dcm_seekcur(DcmParseState *state, int64_t offset, int64_t *position) { - int64_t new_offset = dcm_io_seek(state->error, state->io, 0, SEEK_CUR); + int64_t new_offset = dcm_io_seek(state->error, state->io, offset, SEEK_CUR); if (new_offset < 0) { return false; } - *offset = new_offset; + *position += offset; return true; } @@ -300,6 +301,8 @@ static bool parse_element_header(DcmParseState *state, static bool parse_element_sequence(DcmParseState *state, bool implicit, + uint32_t seq_tag, + DcmVR seq_vr, uint32_t seq_length, int64_t *position) { @@ -387,7 +390,11 @@ static bool parse_element_sequence(DcmParseState *state, } if (state->parse->sequence_end && - !state->parse->sequence_end(state->error, state->client)) { + !state->parse->sequence_end(state->error, + state->client, + seq_tag, + seq_vr, + seq_length)) { return false; } @@ -492,6 +499,8 @@ static bool parse_element_body(DcmParseState *state, int64_t seq_position = 0; if (!parse_element_sequence(state, implicit, + tag, + vr, length, &seq_position)) { return false; diff --git a/src/pdicom.h b/src/pdicom.h index eac6284..e545e79 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -91,9 +91,14 @@ typedef struct _DcmParse { bool (*dataset_end)(DcmError **, void *client); bool (*sequence_begin)(DcmError **, void *client); - bool (*sequence_end)(DcmError **, void *client); - - bool (*element_create)(DcmError **, void *client, + bool (*sequence_end)(DcmError **, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length); + + bool (*element_create)(DcmError **, + void *client, uint32_t tag, DcmVR vr, char *value, diff --git a/tools/dcm-dump.c b/tools/dcm-dump.c index 1de1768..e076414 100644 --- a/tools/dcm-dump.c +++ b/tools/dcm-dump.c @@ -20,6 +20,7 @@ int main(int argc, char *argv[]) DcmFilehandle *handle = NULL; dcm_log_level = DCM_LOG_ERROR; + //dcm_log_level = DCM_LOG_DEBUG; for (i = 1; i < argc && argv[i][0] == '-'; i++) { switch (argv[i][1]) { From 72a5f213f98e50be8092c216a859585fb9165c09 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 15 May 2023 14:14:53 +0100 Subject: [PATCH 09/82] fix some small issues --- src/dicom-file.c | 25 +++++++++++++++---------- src/dicom-parse.c | 5 +++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index 9b50db6..e38bfab 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -397,7 +397,7 @@ static DcmElement *read_element_header(DcmError **error, 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 + // 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, @@ -1002,6 +1002,9 @@ DcmBOT *dcm_filehandle_build_bot(DcmError **error, &length, &position, false); + if (element == NULL) { + return NULL; + } uint32_t tag = dcm_element_get_tag(element); dcm_element_destroy(element); @@ -1410,8 +1413,9 @@ static bool parse_meta_element_create(DcmError **error, return false; } - //printf("parse_meta_element_create: create element %p on dataset %p\n", - // element, dataset); + printf("parse_meta_element_create: create element %p on dataset %p\n", + element, dataset); + dcm_element_print(element, 4); return true; } @@ -1488,17 +1492,10 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, &parse_meta, filehandle->byteswap, filehandle)) { - // destroy any old stack values, perhaps from a previous failed parse dcm_filehandle_clear(filehandle); return false; } - // the file pointer will have been left at the start of pixel data - if (!dcm_offset(error, - filehandle, &filehandle->pixel_data_offset)) { - return NULL; - } - /* Sanity check. We should have parsed a single dataset into the sequence * we put on the stack. */ @@ -1510,6 +1507,14 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, abort(); } + // the file pointer will have been left at the start of pixel data + if (!dcm_offset(error, + filehandle, &filehandle->pixel_data_offset)) { + return NULL; + } + + printf( "pixel_data_offset = %ld\n", filehandle->pixel_data_offset); + // FIXME ... add dcm_sequence_steal() so we can destroy sequence without // also destroying meta // right now we leak sequence diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 0e50862..f1be90b 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -664,7 +664,8 @@ bool dcm_parse_group(DcmError **error, return false; } - while (position < group_length) { + //while (position < group_length) { + for (;;) { int64_t element_start = 0; if (!parse_element_header(&state, implicit, @@ -680,7 +681,7 @@ bool dcm_parse_group(DcmError **error, (state.parse->stop && state.parse->stop(state.client, implicit, tag, vr, length))) { // seek back to the start of this element - if (dcm_seekcur(&state, -element_start, &element_start)) { + if (!dcm_seekcur(&state, -element_start, &element_start)) { return false; } From 2d241c569933518584034bfcfcc980cbe19604ab Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 22 May 2023 17:43:45 +0100 Subject: [PATCH 10/82] fix the parser dcm-dump and getframe both work now --- src/dicom-file.c | 24 ++++++------------------ src/dicom-parse.c | 21 ++++++++++++++++++--- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index e38bfab..93928a7 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -1300,8 +1300,6 @@ static bool parse_meta_dataset_begin(DcmError **error, return false; } - //printf("parse_meta_dataset_begin: created dataset %p\n", dataset); - utarray_push_back(filehandle->dataset_stack, &dataset); return true; @@ -1318,9 +1316,6 @@ static bool parse_meta_dataset_end(DcmError **error, DcmSequence *sequence = *((DcmSequence **) utarray_back(filehandle->sequence_stack)); - //printf("parse_meta_dataset_end: end dataset %p, adding to sequence %p\n", - // dataset, sequence); - if (!dcm_sequence_append(error, sequence, dataset)) { return false; } @@ -1341,8 +1336,6 @@ static bool parse_meta_sequence_begin(DcmError **error, return false; } - //printf("parse_meta_sequence_begin: created sequence %p\n", sequence); - utarray_push_back(filehandle->sequence_stack, &sequence); return true; @@ -1380,10 +1373,6 @@ static bool parse_meta_sequence_end(DcmError **error, return false; } - //printf("parse_meta_sequence_end: end sequence %p\n", sequence); - //printf(" setting as value for element %p\n", element); - //printf(" adding element to dataset %p\n", dataset); - return true; } @@ -1413,10 +1402,6 @@ static bool parse_meta_element_create(DcmError **error, return false; } - printf("parse_meta_element_create: create element %p on dataset %p\n", - element, dataset); - dcm_element_print(element, 4); - return true; } @@ -1503,18 +1488,21 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, utarray_len(filehandle->sequence_stack) != 1) { abort(); } + + // 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(); } - // the file pointer will have been left at the start of pixel data + // the file pointer will have been left at the start of pixel data, or at + // the EOF if (!dcm_offset(error, filehandle, &filehandle->pixel_data_offset)) { return NULL; } - printf( "pixel_data_offset = %ld\n", filehandle->pixel_data_offset); - // FIXME ... add dcm_sequence_steal() so we can destroy sequence without // also destroying meta // right now we leak sequence diff --git a/src/dicom-parse.c b/src/dicom-parse.c index f1be90b..a7861f1 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -27,8 +27,8 @@ #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_TRAILING_PADDING 0xFFFCFFFC #define TAG_PIXEL_DATA 0x7FE00010 #define TAG_FLOAT_PIXEL_DATA 0x7FE00008 #define TAG_DOUBLE_PIXEL_DATA 0x7FE00009 @@ -557,8 +557,9 @@ static bool parse_toplevel_dataset(DcmParseState *state, uint32_t tag; DcmVR vr; uint32_t length; + int64_t element_start = 0; if (!parse_element_header(state, implicit, - &tag, &vr, &length, position)) { + &tag, &vr, &length, &element_start)) { return false; } @@ -570,9 +571,16 @@ static bool parse_toplevel_dataset(DcmParseState *state, if (state->parse->stop && state->parse->stop(state->client, implicit, 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, implicit, tag, vr, length, position)) { return false; } @@ -676,7 +684,14 @@ bool dcm_parse_group(DcmError **error, return false; } - // stop if we run out of the group, or the stop function triggers + if (tag == TAG_TRAILING_PADDING) { + dcm_log_info("Stop reading Data Set", + "Encountered Data Set Trailing Tag"); + break; + } + + // 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, implicit, tag, vr, length))) { From 36ed5c290753ffd2dc81a8692f6222803d49d1cd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 24 May 2023 13:53:38 +0100 Subject: [PATCH 11/82] fix the segv on the test suite --- TODO | 7 +++++-- src/dicom-dict.c | 4 ++-- src/dicom-parse.c | 3 +-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/TODO b/TODO index 7b70465..a266f49 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,11 @@ # TODO -- need to be able to set a stop-at function on filehandles +- remove dupe code -- split parser from AST builder +- look at length checking again in loops + +- need to NOT parse PerFrameFunctionalGroupsSequence etc. and have a + separate thing for this - move implicit into parser state? diff --git a/src/dicom-dict.c b/src/dicom-dict.c index c90597f..992e771 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -54,13 +54,13 @@ struct _DcmVRTable_hash_entry { 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, + {DCM_VR_AS, "AS", DCM_CLASS_STRING_SINGLE, 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, + {DCM_VR_DA, "DA", DCM_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_DA, 2}, {DCM_VR_DS, "DS", DCM_CLASS_STRING_MULTI, 0, DCM_CAPACITY_DS, 2}, diff --git a/src/dicom-parse.c b/src/dicom-parse.c index a7861f1..08f92ac 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -575,11 +575,10 @@ static bool parse_toplevel_dataset(DcmParseState *state, if (!dcm_seekcur(state, -element_start, &element_start)) { return false; } - break; } - position += element_start; + *position += element_start; if (!parse_element_body(state, implicit, tag, vr, length, position)) { return false; From 188d9e132ce7ed9bbf89c98d8fefe119ccb7773d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 24 May 2023 14:58:10 +0100 Subject: [PATCH 12/82] strip out old parser --- src/dicom-data.c | 39 ++ src/dicom-file.c | 359 +---------- src/dicom-file.old.c | 1387 ------------------------------------------ src/dicom-parse.c | 9 - src/pdicom.h | 12 +- 5 files changed, 67 insertions(+), 1739 deletions(-) delete mode 100644 src/dicom-file.old.c diff --git a/src/dicom-data.c b/src/dicom-data.c index ac448c3..a4c8683 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -507,6 +507,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, diff --git a/src/dicom-file.c b/src/dicom-file.c index 93928a7..35abe89 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -24,15 +24,6 @@ #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; @@ -344,42 +335,32 @@ static bool read_iheader(DcmError **error, DcmFilehandle *filehandle, } -char **dcm_parse_character_string(DcmError **error, - char *string, uint32_t *vm) + +static bool get_num_frames(DcmError **error, + const DcmDataSet *metadata, + uint32_t *number_of_frames) { - int n_segments = 1; - for (int i = 0; string[i]; i++) { - if (string[i] == '\\') { - n_segments += 1; - } - } + const uint32_t tag = 0x00280008; + const char *value; + uint32_t num_frames; - char **parts = DCM_NEW_ARRAY(error, n_segments, char *); - if (parts == NULL) { - return NULL; + DcmElement *element = dcm_dataset_get(error, metadata, tag); + if (element == NULL || + !dcm_element_get_value_string(error, element, 0, &value)) { + return false; } - 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; + 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; } - *vm = n_segments; + *number_of_frames = num_frames; - return parts; + return true; } @@ -454,308 +435,6 @@ static DcmElement *read_element_header(DcmError **error, } -// fwd ref this -static DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit); - -static bool read_element_sequence(DcmError **error, - DcmFilehandle *filehandle, - DcmSequence *sequence, - uint32_t length, - int64_t *position, - bool implicit) -{ - 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; - } - - 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(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 (length == 0xFFFFFFFF) { - dcm_log_debug("Item #%d has undefined length.", index); - } else { - dcm_log_debug("Item #%d has defined length %d.", - index, item_length); - } - - DcmDataSet *dataset = dcm_dataset_create(error); - if (dataset == NULL) { - 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; - } - - // back to start of element - if (!dcm_seekcur(error, filehandle, -4, &item_position)) { - dcm_dataset_destroy(dataset); - return false; - } - - DcmElement *element = read_element(error, filehandle, - &item_position, implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return false; - } - - if (!dcm_dataset_insert(error, dataset, element)) { - dcm_dataset_destroy(dataset); - dcm_element_destroy(element); - return false; - } - } - - seq_position += item_position; - - if (!dcm_sequence_append(error, sequence, dataset)) { - dcm_dataset_destroy(dataset); - } - - index += 1; - } - - *position += seq_position; - - return true; -} - - -static bool read_element_body(DcmError **error, - DcmElement *element, - DcmFilehandle *filehandle, - uint32_t length, - int64_t *position, - bool implicit) -{ - 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; - - dcm_log_debug("Read Data Element body '%08X'", tag); - - 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; - - 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; - - char *values = DCM_MALLOC(error, length); - if (values == NULL) { - return false; - } - - if (!dcm_require(error, filehandle, values, length, position)) { - free(values); - return false; - } - - if (filehandle->byteswap) { - byteswap(values, length, size); - } - - if( !dcm_element_set_value_numeric_multi(error, - element, - (int *) values, - vm, - true)) { - free(values); - return false; - } - - break; - - case DCM_CLASS_BINARY: - value = DCM_MALLOC(error, length); - if (value == NULL) { - return false; - } - - if (!dcm_require(error, filehandle, value, length, position)) { - free(value); - return false; - } - - if( !dcm_element_set_value_binary(error, element, - value, length, true)) { - free(value); - return false; - } - - break; - - case DCM_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); - } - - DcmSequence *seq = dcm_sequence_create(error); - if (seq == NULL) { - return NULL; - } - - 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; - } - - return true; -} - - -DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit) -{ - DcmElement *element; - - uint32_t length; - element = read_element_header(error, - filehandle, &length, position, implicit); - if (element == NULL) { - return NULL; - } - - if (!read_element_body(error, element, filehandle, - length, position, implicit)) { - dcm_element_destroy(element); - return NULL; - } - - return element; -} - - -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) diff --git a/src/dicom-file.old.c b/src/dicom-file.old.c deleted file mode 100644 index abeb65a..0000000 --- a/src/dicom-file.old.c +++ /dev/null @@ -1,1387 +0,0 @@ -/* - * 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" - - -#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; -}; - - -struct _DcmFilehandle { - DcmIO *io; - DcmDataSet *meta; - int64_t offset; - char *transfer_syntax_uid; - int64_t pixel_data_offset; - uint64_t *extended_offset_table; - bool byteswap; -}; - - -/* 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; -} - - -DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) -{ - DcmFilehandle *filehandle = DCM_NEW(error, DcmFilehandle); - if (filehandle == NULL) { - return NULL; - } - - filehandle->io = io; - 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(); - - return 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 dcm_filehandle_create(error, io); -} - - -DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, - 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); -} - - -void dcm_filehandle_destroy(DcmFilehandle *filehandle) -{ - if (filehandle) { - if (filehandle->transfer_syntax_uid) { - free(filehandle->transfer_syntax_uid); - } - - (void) dcm_io_close(NULL, filehandle->io); - free(filehandle); - } -} - - -static int64_t dcm_read(DcmError **error, DcmFilehandle *filehandle, - char *buffer, int64_t length, int64_t *position) -{ - int64_t bytes_read = dcm_io_read(error, filehandle->io, buffer, length); - if (bytes_read < 0) { - return bytes_read; - } - - *position += bytes_read; - - return bytes_read; -} - - -static bool dcm_require(DcmError **error, DcmFilehandle *filehandle, - char *buffer, int64_t length, int64_t *position) -{ - while (length > 0) { - int64_t bytes_read = dcm_read(error, - filehandle, buffer, length, position); - - if (bytes_read < 0) { - return false; - } else if (bytes_read == 0) { - dcm_error_set(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_seekset(DcmError **error, - DcmFilehandle *filehandle, int64_t offset) -{ - 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) -{ - int64_t new_offset = dcm_io_seek(error, filehandle->io, offset, SEEK_CUR); - if (new_offset < 0) { - return false; - } - - *position += offset; - - return true; -} - - -static bool dcm_offset(DcmError **error, - DcmFilehandle *filehandle, int64_t *offset) -{ - int64_t new_offset = dcm_io_seek(error, filehandle->io, 0, SEEK_CUR); - if (new_offset < 0) { - return false; - } - - *offset = new_offset; - - return true; -} - - -static bool dcm_is_eof(DcmFilehandle *filehandle) -{ - int64_t position = 0; - bool eof = true; - - char buffer[1]; - int64_t bytes_read = dcm_io_read(NULL, filehandle->io, buffer, 1); - if (bytes_read > 0) { - eof = false; - (void) dcm_seekcur(NULL, filehandle, -1, &position); - } - - return eof; -} - - -static void byteswap(char *data, size_t length, size_t size) -{ - assert(length >= size); - - if (size > 1) { - assert(length % size == 0); - assert(size % 2 == 0); - - size_t half_size = size / 2; - - 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; - } - } - } -} - - -static bool read_uint16(DcmError **error, DcmFilehandle *filehandle, - uint16_t *value, int64_t *position) -{ - union { - uint16_t i; - char c[2]; - } buffer; - - if (!dcm_require(error, filehandle, buffer.c, 2, position)) { - return false; - } - - if (filehandle->byteswap) { - byteswap(buffer.c, 2, 2); - } - - *value = buffer.i; - - return true; -} - - -static bool read_uint32(DcmError **error, DcmFilehandle *filehandle, - uint32_t *value, int64_t *position) -{ - union { - uint32_t i; - char c[4]; - } buffer; - - if (!dcm_require(error, filehandle, buffer.c, 4, position)) { - return false; - } - - if (filehandle->byteswap) { - byteswap(buffer.c, 4, 4); - } - - *value = buffer.i; - - return true; -} - - -static bool read_tag(DcmError **error, DcmFilehandle *filehandle, - uint32_t *value, int64_t *position) -{ - uint16_t group, elem; - - if (!read_uint16(error, filehandle, &group, position) || - !read_uint16(error, filehandle, &elem, position)) { - return false; - } - - *value = ((uint32_t)group << 16) | elem; - - return true; -} - - -static bool read_iheader(DcmError **error, DcmFilehandle *filehandle, - uint32_t *item_tag, uint32_t *item_length, int64_t *position) -{ - if (!read_tag(error, filehandle, item_tag, position) || - !read_uint32(error, filehandle, item_length, position)) { - return false; - } - - return true; -} - - -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; -} - - -static DcmElement *read_element_header(DcmError **error, - DcmFilehandle *filehandle, - uint32_t *length, - int64_t *position, - bool implicit) -{ - uint32_t tag; - if (!read_tag(error, filehandle, &tag, position)) { - 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); - } - - if (!read_uint32(error, filehandle, length, position)) { - return NULL; - } - } else { - // Value Representation - char vr_str[3]; - if (!dcm_require(error, filehandle, vr_str, 2, position)) { - 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); - 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 (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; - } - } - } - - return dcm_element_create(error, tag, vr); -} - - -// fwd ref this -static DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit); - -static bool read_element_sequence(DcmError **error, - DcmFilehandle *filehandle, - DcmSequence *sequence, - uint32_t length, - int64_t *position, - bool implicit) -{ - 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; - } - - 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(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 (length == 0xFFFFFFFF) { - dcm_log_debug("Item #%d has undefined length.", index); - } else { - dcm_log_debug("Item #%d has defined length %d.", - index, item_length); - } - - DcmDataSet *dataset = dcm_dataset_create(error); - if (dataset == NULL) { - 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; - } - - // back to start of element - if (!dcm_seekcur(error, filehandle, -4, &item_position)) { - dcm_dataset_destroy(dataset); - return false; - } - - DcmElement *element = read_element(error, filehandle, - &item_position, implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return false; - } - - if (!dcm_dataset_insert(error, dataset, element)) { - dcm_dataset_destroy(dataset); - dcm_element_destroy(element); - return false; - } - } - - seq_position += item_position; - - if (!dcm_sequence_append(error, sequence, dataset)) { - dcm_dataset_destroy(dataset); - } - - index += 1; - } - - *position += seq_position; - - return true; -} - - -static bool read_element_body(DcmError **error, - DcmElement *element, - DcmFilehandle *filehandle, - uint32_t length, - int64_t *position, - bool implicit) -{ - 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; - - dcm_log_debug("Read Data Element body '%08X'", tag); - - 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; - - 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; - - char *values = DCM_MALLOC(error, length); - if (values == NULL) { - return false; - } - - if (!dcm_require(error, filehandle, values, length, position)) { - free(values); - return false; - } - - if (filehandle->byteswap) { - byteswap(values, length, size); - } - - if( !dcm_element_set_value_numeric_multi(error, - element, - (int *) values, - vm, - true)) { - free(values); - return false; - } - - break; - - case DCM_CLASS_BINARY: - value = DCM_MALLOC(error, length); - if (value == NULL) { - return false; - } - - if (!dcm_require(error, filehandle, value, length, position)) { - free(value); - return false; - } - - if( !dcm_element_set_value_binary(error, element, - value, length, true)) { - free(value); - return false; - } - - break; - - case DCM_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); - } - - DcmSequence *seq = dcm_sequence_create(error); - if (seq == NULL) { - return NULL; - } - - 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; - } - - return true; -} - - -DcmElement *read_element(DcmError **error, - DcmFilehandle *filehandle, - int64_t *position, - bool implicit) -{ - DcmElement *element; - - uint32_t length; - element = read_element_header(error, - filehandle, &length, position, implicit); - if (element == NULL) { - return NULL; - } - - if (!read_element_body(error, element, filehandle, - length, position, implicit)) { - dcm_element_destroy(element); - return NULL; - } - - return element; -} - - -DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, - DcmFilehandle *filehandle) -{ - const bool implicit = false; - - int64_t position; - uint16_t group_number; - DcmElement *element; - - DcmDataSet *file_meta = dcm_dataset_create(error); - if (file_meta == NULL) { - return NULL; - } - - position = 0; - - // 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'; - - // 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; - } - - position = 0; - - // File Meta Information Group Length - element = read_element(error, filehandle, &position, implicit); - if (element == NULL) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - 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; - } - - 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; - } - - group_number = dcm_element_get_group_number(element); - if (group_number != 0x0002) { - dcm_element_destroy(element); - break; - } - - if (!read_element_body(error, element, filehandle, - length, &position, implicit)) { - dcm_element_destroy(element); - dcm_dataset_destroy(file_meta); - return NULL; - } - - // many DICOMs have a FileMetaInformationVersion element, but - // not all ... ignore the version number if present - if (dcm_element_get_tag(element) == 0x00020001) { - dcm_element_destroy(element); - continue; - } - - if (!dcm_dataset_insert(error, file_meta, element)) { - dcm_element_destroy(element); - dcm_dataset_destroy(file_meta); - return NULL; - } - } - - if (!dcm_offset(error, filehandle, &filehandle->offset)) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - 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; - } - - 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; - } - - dcm_dataset_lock(file_meta); - - return file_meta; -} - - -DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, - DcmFilehandle *filehandle) -{ - bool implicit; - - if (filehandle->offset == 0) { - DcmDataSet *meta = dcm_filehandle_read_file_meta(error, filehandle); - if (meta== NULL) { - return NULL; - } - 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; - } - } - - DcmDataSet *dataset = dcm_dataset_create(error); - if (dataset == NULL) { - return NULL; - } - - int64_t position = 0; - for (;;) { - if (dcm_is_eof(filehandle)) { - dcm_log_info("Stop reading Data Set. Reached end of filehandle."); - break; - } - - uint32_t length; - DcmElement *element = read_element_header(error, - filehandle, - &length, - &position, - implicit); - if (element == NULL) { - dcm_dataset_destroy(dataset); - return NULL; - } - 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 (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; - } - - } 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; - } - } - - if (!dcm_offset(error, - filehandle, &filehandle->pixel_data_offset)) { - dcm_dataset_destroy(dataset); - return NULL; - } - - 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; - } - - 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; - } - } - - 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) -{ - uint64_t value; - - dcm_log_debug("Reading Basic Offset Table."); - - 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); - return NULL; - } - - uint32_t num_frames; - if (!get_num_frames(error, metadata, &num_frames)) { - return NULL; - } - - 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_seekset(error, filehandle, filehandle->pixel_data_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"); - return NULL; - } - - // The header of the BOT Item - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - return NULL; - } - 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 NULL; - } - - ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); - if (offsets == NULL) { - 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) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "No Basic Offset Table, " - "and no Extended Offset Table"); - free(offsets); - return NULL; - } - - dcm_log_info("Found Extended Offset Table."); - - 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; - } - - // FIXME is this correct? - first_frame_offset = position; - } - - return dcm_bot_create(error, offsets, num_frames, first_frame_offset); -} - - -static bool set_pixel_description(DcmError **error, - struct PixelDescription *desc, - const DcmDataSet *metadata) -{ - DcmElement *element; - int64_t value; - const char *string; - - element = dcm_dataset_get(error, metadata, 0x00280010); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->rows = value; - - element = dcm_dataset_get(error, metadata, 0x00280011); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->columns = value; - - 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; - - 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; - - 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; - - 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; - - return true; -} - - -DcmBOT *dcm_filehandle_build_bot(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata) -{ - uint64_t i; - - dcm_log_debug("Building Basic Offset Table."); - - uint32_t num_frames; - if (!get_num_frames(error, metadata, &num_frames)) { - return NULL; - } - - 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_seekset(error, filehandle, filehandle->pixel_data_offset)) { - return NULL; - } - - // we measure offsets from this point - int64_t position = 0; - - 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; - } - - ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); - if (offsets == NULL) { - return NULL; - } - - 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; - } - - 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; - } - - // Move filehandlepointer to the first byte of first Frame item - if (!dcm_seekcur(error, filehandle, length, &position)) { - free(offsets); - } - - // and that's the offset to the first frame - first_frame_offset = position; - - // now measure positions from the start of the first frame - position = 0; - - i = 0; - while (true) { - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - free(offsets); - return NULL; - } - - if (tag == TAG_SQ_DELIM) { - break; - } - - 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; - } - - if (dcm_is_eof(filehandle)) { - break; - } - - // step back to the start of the item for this frame - offsets[i] = position - 8; - - if (!dcm_seekcur(error, filehandle, length, &position)) { - free(offsets); - return NULL; - } - - i += 1; - } - - 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; - } - - return dcm_bot_create(error, offsets, num_frames, first_frame_offset); -} - - -DcmFrame *dcm_filehandle_read_frame(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata, - DcmBOT *bot, - uint32_t number) -{ - uint32_t length; - - 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; - } - - 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; - } - - struct PixelDescription desc; - if (!set_pixel_description(error, &desc, metadata)) { - return NULL; - } - - 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; - } - - 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; - } - - char *value = DCM_MALLOC(error, length); - if (value == NULL) { - return NULL; - } - if (!dcm_require(error, filehandle, value, length, &position)) { - free(value); - return NULL; - } - - 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); -} diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 08f92ac..e7931f3 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -24,15 +24,6 @@ #include "pdicom.h" -#define TAG_ITEM 0xFFFEE000 -#define TAG_ITEM_DELIM 0xFFFEE00D -#define TAG_SQ_DELIM 0xFFFEE0DD -#define TAG_EXTENDED_OFFSET_TABLE 0x7FE00001 -#define TAG_TRAILING_PADDING 0xFFFCFFFC -#define TAG_PIXEL_DATA 0x7FE00010 -#define TAG_FLOAT_PIXEL_DATA 0x7FE00008 -#define TAG_DOUBLE_PIXEL_DATA 0x7FE00009 - /* The size of the buffer we use for reading smaller element values. This is * large enough for most VRs. */ diff --git a/src/pdicom.h b/src/pdicom.h index e545e79..983ee7b 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -38,6 +38,15 @@ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define USED(x) (void)(x) +#define TAG_ITEM 0xFFFEE000 +#define TAG_ITEM_DELIM 0xFFFEE00D +#define TAG_SQ_DELIM 0xFFFEE0DD +#define TAG_EXTENDED_OFFSET_TABLE 0x7FE00001 +#define TAG_TRAILING_PADDING 0xFFFCFFFC +#define TAG_PIXEL_DATA 0x7FE00010 +#define TAG_FLOAT_PIXEL_DATA 0x7FE00008 +#define TAG_DOUBLE_PIXEL_DATA 0x7FE00009 + void *dcm_calloc(DcmError **error, size_t n, size_t size); @@ -70,9 +79,6 @@ 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_FL: OPERATION(float); break; \ From 4d9fe01a10ff44f29435be6d8b2471adcd68fad2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 25 May 2023 14:44:35 +0100 Subject: [PATCH 13/82] improve file_meta parsing now uses almost the same callbacks as the main parser --- src/dicom-file.c | 247 ++++++++++++++++++++++++---------------------- src/dicom-parse.c | 9 +- 2 files changed, 128 insertions(+), 128 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index 35abe89..7d7b370 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -861,114 +861,6 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, } -static bool parse_file_meta_element_create(DcmError **error, - void *client, - uint32_t tag, - DcmVR vr, - char *value, - uint32_t length) -{ - DcmDataSet *file_meta = (DcmDataSet *) client; - - DcmElement *element = dcm_element_create(error, tag, vr); - if (element == NULL) { - return false; - } - - if (!dcm_element_set_value(error, element, value, length, false) || - !dcm_dataset_insert(error, file_meta, element)) { - dcm_element_destroy(element); - return false; - } - - return true; -} - - -static DcmParse parse_file_meta = { - .element_create = parse_file_meta_element_create -}; - - -DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, - DcmFilehandle *filehandle) -{ - const bool implicit = false; - - int64_t position = 0; - - // File Preamble - char preamble[129]; - if (!dcm_require(error, - filehandle, preamble, sizeof(preamble) - 1, &position)) { - return NULL; - } - preamble[128] = '\0'; - - // DICOM Prefix - char prefix[5]; - if (!dcm_require(error, - filehandle, prefix, sizeof(prefix) - 1, &position)) { - 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."); - return NULL; - } - - DcmDataSet *file_meta = dcm_dataset_create(error); - if (file_meta == NULL) { - return NULL; - } - - // parse the group elements into ->file_meta - if (!dcm_parse_group(error, - filehandle->io, - implicit, - &parse_file_meta, - false, - file_meta)) { - dcm_dataset_destroy(file_meta); - return false; - } - - // record the start point for the image metadata - if (!dcm_offset(error, filehandle, &filehandle->offset)) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - DcmElement *element = dcm_dataset_get(error, file_meta, 0x00020010); - if (element == NULL) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - const char *transfer_syntax_uid; - if (!dcm_element_get_value_string(error, - element, - 0, - &transfer_syntax_uid)) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - filehandle->transfer_syntax_uid = dcm_strdup(error, transfer_syntax_uid); - if (filehandle->transfer_syntax_uid == NULL) { - dcm_dataset_destroy(file_meta); - return NULL; - } - - dcm_dataset_lock(file_meta); - - return file_meta; -} - - static bool parse_meta_dataset_begin(DcmError **error, void *client) { @@ -1056,7 +948,6 @@ static bool parse_meta_sequence_end(DcmError **error, } -// FIXME share with file_meta parse static bool parse_meta_element_create(DcmError **error, void *client, uint32_t tag, @@ -1085,6 +976,123 @@ static bool parse_meta_element_create(DcmError **error, } +DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, + DcmFilehandle *filehandle) +{ + 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, + }; + + const bool implicit = false; + + int64_t position = 0; + + // File Preamble + char preamble[129]; + if (!dcm_require(error, + filehandle, preamble, sizeof(preamble) - 1, &position)) { + return NULL; + } + preamble[128] = '\0'; + + // DICOM Prefix + char prefix[5]; + if (!dcm_require(error, + filehandle, prefix, sizeof(prefix) - 1, &position)) { + 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."); + return NULL; + } + + // sanity ... the parse stacks should be empty + if (utarray_len(filehandle->dataset_stack) != 0 || + utarray_len(filehandle->sequence_stack) != 0) { + abort(); + } + + DcmSequence *sequence = dcm_sequence_create(error); + if (sequence == NULL) { + return NULL; + } + utarray_push_back(filehandle->sequence_stack, &sequence); + + // parse all of the first group + if (!dcm_parse_group(error, + filehandle->io, + implicit, + &parse, + false, + filehandle)) { + dcm_filehandle_clear(filehandle); + return false; + } + + /* 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(); + } + + // record the start point for the image metadata + if (!dcm_offset(error, filehandle, &filehandle->offset)) { + return NULL; + } + + // 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(); + } + + // FIXME ... add dcm_sequence_steal() so we can destroy sequence without + // also destroying meta + // right now we leak sequence + DcmDataSet *file_meta = dcm_sequence_get(error, sequence, 0); + if (file_meta == NULL ) { + return false; + } + + DcmElement *element = dcm_dataset_get(error, file_meta, 0x00020010); + if (element == NULL) { + return NULL; + } + + const char *transfer_syntax_uid; + if (!dcm_element_get_value_string(error, + element, + 0, + &transfer_syntax_uid)) { + return NULL; + } + + filehandle->transfer_syntax_uid = dcm_strdup(error, transfer_syntax_uid); + if (filehandle->transfer_syntax_uid == NULL) { + return NULL; + } + + // we need to pop sequence off the dataset stack to stop it being destroyed + utarray_pop_back(filehandle->sequence_stack); + + dcm_dataset_lock(file_meta); + + return file_meta; +} + + static bool parse_meta_stop(void *client, bool implicit, uint32_t tag, @@ -1102,19 +1110,18 @@ static bool parse_meta_stop(void *client, } -static DcmParse parse_meta = { - .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, -}; - - DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, DcmFilehandle *filehandle) { + 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 = parse_meta_stop, + }; + bool implicit; if (filehandle->offset == 0) { @@ -1153,7 +1160,7 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, if (!dcm_parse_dataset(error, filehandle->io, implicit, - &parse_meta, + &parse, filehandle->byteswap, filehandle)) { dcm_filehandle_clear(filehandle); diff --git a/src/dicom-parse.c b/src/dicom-parse.c index e7931f3..f89912e 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -662,8 +662,7 @@ bool dcm_parse_group(DcmError **error, return false; } - //while (position < group_length) { - for (;;) { + while (position < group_length) { int64_t element_start = 0; if (!parse_element_header(&state, implicit, @@ -674,12 +673,6 @@ bool dcm_parse_group(DcmError **error, return false; } - if (tag == TAG_TRAILING_PADDING) { - dcm_log_info("Stop reading Data Set", - "Encountered Data Set Trailing Tag"); - break; - } - // stop if we read the first tag of the group beyond, // or if the stop function triggers if ((tag >> 16) != group_number || From 8037b2303f9e159451ebefeab0b7c6b94e8b3ee6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 26 May 2023 16:37:55 +0100 Subject: [PATCH 14/82] read the frame index table with a special parse thing that just records the index --- TODO | 16 ++--- src/dicom-file.c | 166 +++++++++++++++++++++++++++++++++++++++++++---- src/pdicom.h | 18 ++--- 3 files changed, 171 insertions(+), 29 deletions(-) diff --git a/TODO b/TODO index a266f49..6b9318b 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,16 @@ # TODO +- dcm_filehandle_read_bot() should be in dicom-parse.c + + also dcm_filehandle_build_bot() + - remove dupe code -- look at length checking again in loops +- load PerFrameFunctionalGroupsSequence during BOT load + +- getframe should use index -- need to NOT parse PerFrameFunctionalGroupsSequence etc. and have a - separate thing for this +- update openslide for new thing - move implicit into parser state? @@ -17,16 +22,11 @@ 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()? diff --git a/src/dicom-file.c b/src/dicom-file.c index 7d7b370..25f8227 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -44,6 +44,11 @@ struct _DcmFilehandle { int64_t pixel_data_offset; uint64_t *extended_offset_table; bool byteswap; + uint32_t last_tag; + uint32_t frame_number; + uint32_t tiles_across; + uint32_t num_frames; + uint32_t *frame_index; // push and pop these while we parse UT_array *dataset_stack; @@ -78,6 +83,8 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) filehandle->pixel_data_offset = 0; filehandle->extended_offset_table = NULL; filehandle->byteswap = is_big_endian(); + filehandle->last_tag = 0xffffffff; + filehandle->frame_index = NULL; utarray_new(filehandle->dataset_stack, &ut_ptr_icd); utarray_new(filehandle->sequence_stack, &ut_ptr_icd); @@ -142,6 +149,10 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) free(filehandle->transfer_syntax_uid); } + if (filehandle->frame_index) { + free(filehandle->frame_index); + } + (void) dcm_io_close(NULL, filehandle->io); utarray_free(filehandle->dataset_stack); @@ -364,6 +375,36 @@ static bool get_num_frames(DcmError **error, } +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_tiles_across(DcmError **error, + const DcmDataSet *metadata, + uint32_t *tiles_across) +{ + int64_t width; + int64_t tile_width; + + if (!get_tag_int(error, metadata, "TotalPixelMatrixColumns", &width) || + !get_tag_int(error, metadata, "Columns", &tile_width)) { + return false; + } + + *tiles_across = width / tile_width + !!(width % tile_width); + + return true; +} + + static DcmElement *read_element_header(DcmError **error, DcmFilehandle *filehandle, uint32_t *length, @@ -955,8 +996,6 @@ static bool parse_meta_element_create(DcmError **error, char *value, uint32_t length) { - USED(client); - DcmFilehandle *filehandle = (DcmFilehandle *) client; DcmElement *element = dcm_element_create(error, tag, vr); @@ -1093,20 +1132,112 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, } +static bool parse_frame_index_element_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) +{ + USED(error); + + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + 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 true; +} + + +static bool parse_frame_index_stop(void *client, + bool implicit, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + USED(implicit); + USED(vr); + USED(length); + + filehandle->last_tag = tag; + + return tag != TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE; +} + + +static bool read_frame_index(DcmError **error, + DcmFilehandle *filehandle, + bool implicit, + DcmDataSet *metadata) +{ + static DcmParse parse = { + .element_create = parse_frame_index_element_create, + .stop = parse_frame_index_stop, + }; + + if (!get_tiles_across(error, metadata, &filehandle->tiles_across) || + !get_num_frames(error, metadata, &filehandle->num_frames)) { + return false; + } + + filehandle->frame_index = DCM_NEW_ARRAY(error, + filehandle->num_frames, + uint32_t); + if (filehandle->frame_index == NULL) { + return false; + } + + // we may not have all frames ... set to missing initially + for (uint32_t i = 0; i < filehandle->num_frames; i++) { + filehandle->frame_index[i] = -1; + } + + // parse just the per-frame stuff + filehandle->frame_number = 0; + if (!dcm_parse_dataset(error, + filehandle->io, + implicit, + &parse, + filehandle->byteswap, + filehandle)) { + return false; + } + + return true; +} + + static bool parse_meta_stop(void *client, bool implicit, uint32_t tag, DcmVR vr, uint32_t length) { - USED(client); + DcmFilehandle *filehandle = (DcmFilehandle *) client; + USED(implicit); USED(vr); USED(length); - return tag == TAG_PIXEL_DATA || - tag == TAG_FLOAT_PIXEL_DATA || - tag == TAG_DOUBLE_PIXEL_DATA; + filehandle->last_tag = tag; + + return tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE || + tag == TAG_PIXEL_DATA || + tag == TAG_FLOAT_PIXEL_DATA || + tag == TAG_DOUBLE_PIXEL_DATA; } @@ -1182,13 +1313,6 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, abort(); } - // the file pointer will have been left at the start of pixel data, or at - // the EOF - if (!dcm_offset(error, - filehandle, &filehandle->pixel_data_offset)) { - return NULL; - } - // FIXME ... add dcm_sequence_steal() so we can destroy sequence without // also destroying meta // right now we leak sequence @@ -1197,6 +1321,22 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return false; } + // did we stop on per-frame func group? parse that to build the index + if (filehandle->last_tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE && + !read_frame_index(error, filehandle, implicit, meta)) { + return false; + } + + // and hopefully we're now on the pixel data + if (filehandle->last_tag == TAG_PIXEL_DATA || + filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || + filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { + if (!dcm_offset(error, + filehandle, &filehandle->pixel_data_offset)) { + return NULL; + } + } + // we need to pop sequence off the dataset stack to stop it being destroyed utarray_pop_back(filehandle->sequence_stack); diff --git a/src/pdicom.h b/src/pdicom.h index 983ee7b..4d18227 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -38,14 +38,16 @@ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define USED(x) (void)(x) -#define TAG_ITEM 0xFFFEE000 -#define TAG_ITEM_DELIM 0xFFFEE00D -#define TAG_SQ_DELIM 0xFFFEE0DD -#define TAG_EXTENDED_OFFSET_TABLE 0x7FE00001 -#define TAG_TRAILING_PADDING 0xFFFCFFFC -#define TAG_PIXEL_DATA 0x7FE00010 -#define TAG_FLOAT_PIXEL_DATA 0x7FE00008 -#define TAG_DOUBLE_PIXEL_DATA 0x7FE00009 +#define TAG_ITEM 0xFFFEE000 +#define TAG_ITEM_DELIM 0xFFFEE00D +#define TAG_SQ_DELIM 0xFFFEE0DD +#define TAG_EXTENDED_OFFSET_TABLE 0x7FE00001 +#define TAG_TRAILING_PADDING 0xFFFCFFFC +#define TAG_PIXEL_DATA 0x7FE00010 +#define TAG_FLOAT_PIXEL_DATA 0x7FE00008 +#define TAG_DOUBLE_PIXEL_DATA 0x7FE00009 +#define TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE 0x52009230 +#define TAG_DIMENSION_INDEX_VALUES 0x00209157 void *dcm_calloc(DcmError **error, size_t n, size_t size); From e184ac41a5ccb1735b752effaaadb826bc8ceda3 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 26 May 2023 17:20:22 +0100 Subject: [PATCH 15/82] start revising BOT handler --- src/dicom-data.c | 3 +- src/dicom-file.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 3 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index a4c8683..0193739 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -366,8 +366,7 @@ static bool element_check_capacity(DcmError **error, 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)", + "maximum length of Value Representation (%d)", element->tag, capacity); return false; diff --git a/src/dicom-file.c b/src/dicom-file.c index 25f8227..390fbe2 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -476,7 +476,7 @@ static DcmElement *read_element_header(DcmError **error, } -DcmBOT *dcm_filehandle_read_bot(DcmError **error, +DcmBOT *dcm_filehandle_read_bot_old(DcmError **error, DcmFilehandle *filehandle, DcmDataSet *metadata) { @@ -1344,3 +1344,148 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return meta; } + + +DcmBOT *dcm_filehandle_read_bot(DcmError **error, + DcmFilehandle *filehandle, + DcmDataSet *metadata) +{ + uint64_t value; + + dcm_log_debug("Reading Basic Offset Table."); + + 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); + return NULL; + } + + uint32_t num_frames; + if (!get_num_frames(error, metadata, &num_frames)) { + return NULL; + } + + 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_seekset(error, filehandle, filehandle->pixel_data_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"); + return NULL; + } + + // The header of the BOT Item + if (!read_iheader(error, filehandle, &tag, &length, &position)) { + return NULL; + } + 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 NULL; + } + + ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); + if (offsets == NULL) { + 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) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "No Basic Offset Table, " + "and no Extended Offset Table"); + free(offsets); + return NULL; + } + + dcm_log_info("Found Extended Offset Table."); + + 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; + } + + // FIXME is this correct? + first_frame_offset = position; + } + + return dcm_bot_create(error, offsets, num_frames, first_frame_offset); +} + From f75daf826d959512f636a356f914667e62895adc Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 26 May 2023 17:31:06 +0100 Subject: [PATCH 16/82] make capacity checks a non-fatal warning Many DICOMs seem to have overlong DT fields, so make this check a warning rather than a fatal error. See https://github.com/openslide/openslide/pull/439#issuecomment-1564628351 --- src/dicom-data.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index 422138e..852763a 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -363,14 +363,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); } } From 40a692385d8066876caad9bbe450acd67b25e016 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 29 May 2023 11:43:51 +0100 Subject: [PATCH 17/82] note dcm-dump fix --- TODO | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index 6b9318b..4d33a54 100644 --- a/TODO +++ b/TODO @@ -1,14 +1,15 @@ # TODO -- dcm_filehandle_read_bot() should be in dicom-parse.c - - also dcm_filehandle_build_bot() +- dcm-dump could call the parser with a set of print functions + + add a "-a" flag to print everything (inc. pixel data and + perframefunctionalgroupsequence etc.) - remove dupe code - load PerFrameFunctionalGroupsSequence during BOT load -- getframe should use index +- getframe should use frame_index - update openslide for new thing From ae6f8471bc8aec6dce10f5f0224871586c19799b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 29 May 2023 12:30:52 +0100 Subject: [PATCH 18/82] delay func seq read to BOT load and move implicit into the parser state --- TODO | 4 +- src/dicom-file.c | 226 +++++++++++++++++++++++----------------------- src/dicom-parse.c | 56 ++++-------- src/pdicom.h | 5 +- 4 files changed, 136 insertions(+), 155 deletions(-) diff --git a/TODO b/TODO index 4d33a54..18f318f 100644 --- a/TODO +++ b/TODO @@ -7,14 +7,12 @@ - remove dupe code -- load PerFrameFunctionalGroupsSequence during BOT load +- update build-bot - getframe should use frame_index - update openslide for new thing -- move implicit into parser state? - # bot support - need more tests and test images diff --git a/src/dicom-file.c b/src/dicom-file.c index 2496e0d..2006a4a 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -44,6 +44,7 @@ struct _DcmFilehandle { int64_t pixel_data_offset; uint64_t *extended_offset_table; bool byteswap; + bool implicit; uint32_t last_tag; uint32_t frame_number; uint32_t tiles_across; @@ -409,8 +410,7 @@ static bool get_tiles_across(DcmError **error, static DcmElement *read_element_header(DcmError **error, DcmFilehandle *filehandle, uint32_t *length, - int64_t *position, - bool implicit) + int64_t *position) { uint32_t tag; if (!read_tag(error, filehandle, &tag, position)) { @@ -418,7 +418,7 @@ static DcmElement *read_element_header(DcmError **error, } DcmVR vr; - if (implicit) { + if (filehandle->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); @@ -577,8 +577,7 @@ DcmBOT *dcm_filehandle_build_bot(DcmError **error, DcmElement *element = read_element_header(error, filehandle, &length, - &position, - false); + &position); if (element == NULL) { return NULL; } @@ -884,8 +883,6 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, .stop = NULL, }; - const bool implicit = false; - int64_t position = 0; // File Preamble @@ -926,9 +923,9 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, // parse all of the first group if (!dcm_parse_group(error, filehandle->io, - implicit, - &parse, false, + false, + &parse, filehandle)) { dcm_filehandle_clear(filehandle); return false; @@ -989,103 +986,14 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, } -static bool parse_frame_index_element_create(DcmError **error, - void *client, - uint32_t tag, - DcmVR vr, - char *value, - uint32_t length) -{ - USED(error); - - DcmFilehandle *filehandle = (DcmFilehandle *) client; - - 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 true; -} - - -static bool parse_frame_index_stop(void *client, - bool implicit, - uint32_t tag, - DcmVR vr, - uint32_t length) -{ - DcmFilehandle *filehandle = (DcmFilehandle *) client; - - USED(implicit); - USED(vr); - USED(length); - - filehandle->last_tag = tag; - - return tag != TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE; -} - - -static bool read_frame_index(DcmError **error, - DcmFilehandle *filehandle, - bool implicit, - DcmDataSet *metadata) -{ - static DcmParse parse = { - .element_create = parse_frame_index_element_create, - .stop = parse_frame_index_stop, - }; - - if (!get_tiles_across(error, metadata, &filehandle->tiles_across) || - !get_num_frames(error, metadata, &filehandle->num_frames)) { - return false; - } - - filehandle->frame_index = DCM_NEW_ARRAY(error, - filehandle->num_frames, - uint32_t); - if (filehandle->frame_index == NULL) { - return false; - } - - // we may not have all frames ... set to missing initially - for (uint32_t i = 0; i < filehandle->num_frames; i++) { - filehandle->frame_index[i] = -1; - } - - // parse just the per-frame stuff - filehandle->frame_number = 0; - if (!dcm_parse_dataset(error, - filehandle->io, - implicit, - &parse, - filehandle->byteswap, - filehandle)) { - return false; - } - - return true; -} - static bool parse_meta_stop(void *client, - bool implicit, uint32_t tag, DcmVR vr, uint32_t length) { DcmFilehandle *filehandle = (DcmFilehandle *) client; - USED(implicit); USED(vr); USED(length); @@ -1110,8 +1018,6 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, .stop = parse_meta_stop, }; - bool implicit; - if (filehandle->offset == 0) { DcmDataSet *file_meta = dcm_filehandle_read_file_meta(error, filehandle); @@ -1125,10 +1031,9 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return NULL; } - implicit = false; if (filehandle->transfer_syntax_uid) { if (strcmp(filehandle->transfer_syntax_uid, "1.2.840.10008.1.2") == 0) { - implicit = true; + filehandle->implicit = true; } } @@ -1144,12 +1049,12 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } utarray_push_back(filehandle->sequence_stack, &sequence); - // parse as far as the pixel data + // parse up to perframefunctionalgroupsequence, or the pixel data if (!dcm_parse_dataset(error, filehandle->io, - implicit, - &parse, + filehandle->implicit, filehandle->byteswap, + &parse, filehandle)) { dcm_filehandle_clear(filehandle); return false; @@ -1178,13 +1083,7 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return false; } - // did we stop on per-frame func group? parse that to build the index - if (filehandle->last_tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE && - !read_frame_index(error, filehandle, implicit, meta)) { - return false; - } - - // and hopefully we're now on the pixel data + // did we stop on pixel data? record the offset if (filehandle->last_tag == TAG_PIXEL_DATA || filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { @@ -1203,6 +1102,91 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } +static bool parse_frame_index_element_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) +{ + USED(error); + + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + 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 true; +} + + +static bool parse_frame_index_stop(void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + USED(vr); + USED(length); + + filehandle->last_tag = tag; + + return tag != TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE; +} + + +static bool read_frame_index(DcmError **error, + DcmFilehandle *filehandle, + DcmDataSet *metadata) +{ + static DcmParse parse = { + .element_create = parse_frame_index_element_create, + .stop = parse_frame_index_stop, + }; + + if (!get_tiles_across(error, metadata, &filehandle->tiles_across) || + !get_num_frames(error, metadata, &filehandle->num_frames)) { + return false; + } + + filehandle->frame_index = DCM_NEW_ARRAY(error, + filehandle->num_frames, + uint32_t); + if (filehandle->frame_index == NULL) { + return false; + } + + // we may not have all frames ... set to missing initially + for (uint32_t i = 0; i < filehandle->num_frames; i++) { + filehandle->frame_index[i] = -1; + } + + // parse just the per-frame stuff + filehandle->frame_number = 0; + if (!dcm_parse_dataset(error, + filehandle->io, + filehandle->implicit, + filehandle->byteswap, + &parse, + filehandle)) { + return false; + } + + return true; +} + + DcmBOT *dcm_filehandle_read_bot(DcmError **error, DcmFilehandle *filehandle, DcmDataSet *metadata) @@ -1225,6 +1209,22 @@ DcmBOT *dcm_filehandle_read_bot(DcmError **error, return NULL; } + // did we stop on per-frame func group? parse that to build the index + if (filehandle->last_tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE && + !read_frame_index(error, filehandle, metadata)) { + return false; + } + + // and hopefully we're now on the pixel data + if (filehandle->last_tag == TAG_PIXEL_DATA || + filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || + filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { + if (!dcm_offset(error, + filehandle, &filehandle->pixel_data_offset)) { + return NULL; + } + } + if (filehandle->pixel_data_offset == 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading Basic Offset Table failed", @@ -1241,7 +1241,7 @@ DcmBOT *dcm_filehandle_read_bot(DcmError **error, int64_t position = 0; DcmElement *element; uint32_t length; - element = read_element_header(error, filehandle, &length, &position, false); + element = read_element_header(error, filehandle, &length, &position); uint32_t tag = dcm_element_get_tag(element); dcm_element_destroy(element); diff --git a/src/dicom-parse.c b/src/dicom-parse.c index f89912e..23d5b11 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -33,8 +33,9 @@ typedef struct _DcmParseState { DcmError **error; DcmIO *io; - const DcmParse *parse; + bool implicit; bool byteswap; + const DcmParse *parse; void *client; DcmDataSet *meta; @@ -215,12 +216,10 @@ static bool read_tag(DcmParseState *state, uint32_t *tag, int64_t *position) /* This is used recursively. */ static bool parse_element(DcmParseState *state, - bool implicit, int64_t *position); static bool parse_element_header(DcmParseState *state, - bool implicit, uint32_t *tag, DcmVR *vr, uint32_t *length, @@ -230,7 +229,7 @@ static bool parse_element_header(DcmParseState *state, return false; } - if (implicit) { + if (state->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); @@ -291,7 +290,6 @@ static bool parse_element_header(DcmParseState *state, static bool parse_element_sequence(DcmParseState *state, - bool implicit, uint32_t seq_tag, DcmVR seq_vr, uint32_t seq_length, @@ -365,7 +363,7 @@ static bool parse_element_sequence(DcmParseState *state, return false; } - if (!parse_element(state, implicit, &item_position)) { + if (!parse_element(state, &item_position)) { return false; } } @@ -394,7 +392,6 @@ static bool parse_element_sequence(DcmParseState *state, static bool parse_element_body(DcmParseState *state, - bool implicit, uint32_t tag, DcmVR vr, uint32_t length, @@ -489,7 +486,6 @@ static bool parse_element_body(DcmParseState *state, int64_t seq_position = 0; if (!parse_element_sequence(state, - implicit, tag, vr, length, @@ -513,14 +509,13 @@ static bool parse_element_body(DcmParseState *state, static bool parse_element(DcmParseState *state, - bool implicit, int64_t *position) { uint32_t tag; DcmVR vr; uint32_t length; - if (!parse_element_header(state, implicit, &tag, &vr, &length, position) || - !parse_element_body(state, implicit, tag, vr, length, position)) { + if (!parse_element_header(state, &tag, &vr, &length, position) || + !parse_element_body(state, tag, vr, length, position)) { return false; } @@ -531,7 +526,6 @@ static bool parse_element(DcmParseState *state, * stop function. */ static bool parse_toplevel_dataset(DcmParseState *state, - bool implicit, int64_t *position) { if (state->parse->dataset_begin && @@ -549,8 +543,7 @@ static bool parse_toplevel_dataset(DcmParseState *state, DcmVR vr; uint32_t length; int64_t element_start = 0; - if (!parse_element_header(state, implicit, - &tag, &vr, &length, &element_start)) { + if (!parse_element_header(state, &tag, &vr, &length, &element_start)) { return false; } @@ -561,7 +554,7 @@ static bool parse_toplevel_dataset(DcmParseState *state, } if (state->parse->stop && - state->parse->stop(state->client, implicit, tag, vr, length)) { + 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; @@ -571,7 +564,7 @@ static bool parse_toplevel_dataset(DcmParseState *state, *position += element_start; - if (!parse_element_body(state, implicit, tag, vr, length, position)) { + if (!parse_element_body(state, tag, vr, length, position)) { return false; } } @@ -590,20 +583,21 @@ static bool parse_toplevel_dataset(DcmParseState *state, bool dcm_parse_dataset(DcmError **error, DcmIO *io, bool implicit, - const DcmParse *parse, bool byteswap, + const DcmParse *parse, void *client) { DcmParseState state = { .error = error, .io = io, - .parse = parse, + .implicit = implicit, .byteswap = byteswap, + .parse = parse, .client = client }; int64_t position = 0; - if (!parse_toplevel_dataset(&state, implicit, &position)) { + if (!parse_toplevel_dataset(&state, &position)) { return false; } @@ -616,15 +610,16 @@ bool dcm_parse_dataset(DcmError **error, bool dcm_parse_group(DcmError **error, DcmIO *io, bool implicit, - const DcmParse *parse, bool byteswap, + const DcmParse *parse, void *client) { DcmParseState state = { .error = error, .io = io, - .parse = parse, + .implicit = implicit, .byteswap = byteswap, + .parse = parse, .client = client }; @@ -635,12 +630,7 @@ bool dcm_parse_group(DcmError **error, uint32_t tag; DcmVR vr; uint32_t length; - if (!parse_element_header(&state, - implicit, - &tag, - &vr, - &length, - &position)) { + if (!parse_element_header(&state, &tag, &vr, &length, &position)) { return false; } uint16_t element_number = tag & 0xffff; @@ -664,12 +654,7 @@ bool dcm_parse_group(DcmError **error, while (position < group_length) { int64_t element_start = 0; - if (!parse_element_header(&state, - implicit, - &tag, - &vr, - &length, - &element_start)) { + if (!parse_element_header(&state, &tag, &vr, &length, &element_start)) { return false; } @@ -677,7 +662,7 @@ bool dcm_parse_group(DcmError **error, // or if the stop function triggers if ((tag >> 16) != group_number || (state.parse->stop && - state.parse->stop(state.client, implicit, tag, vr, length))) { + 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; @@ -688,7 +673,7 @@ bool dcm_parse_group(DcmError **error, position += element_start; - if (!parse_element_body(&state, implicit, tag, vr, length, &position)) { + if (!parse_element_body(&state, tag, vr, length, &position)) { return false; } } @@ -700,4 +685,3 @@ bool dcm_parse_group(DcmError **error, return true; } - diff --git a/src/pdicom.h b/src/pdicom.h index 4d18227..f3be7de 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -113,7 +113,6 @@ typedef struct _DcmParse { uint32_t length); bool (*stop)(void *client, - bool implicit, uint32_t tag, DcmVR vr, uint32_t length); @@ -123,14 +122,14 @@ DCM_EXTERN bool dcm_parse_dataset(DcmError **error, DcmIO *io, bool implicit, - const DcmParse *parse, bool byteswap, + const DcmParse *parse, void *client); DCM_EXTERN bool dcm_parse_group(DcmError **error, DcmIO *io, bool implicit, - const DcmParse *parse, bool byteswap, + const DcmParse *parse, void *client); From 6c7ee7eb422306704b7ea59828593e905b567680 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Jun 2023 14:35:44 +0100 Subject: [PATCH 19/82] revise bot handling - no more BOT object, it's built into filehandle - a new API call dcm_filehandle_read_pixeldata() gets ready for frame fetch - dcm_filehandle_read_frame() no longer needs metadata or bot --- TODO | 43 +-- include/dicom/dicom.h | 105 +----- src/dicom-data.c | 85 ----- src/dicom-file.c | 785 +++++++++--------------------------------- src/dicom-parse.c | 163 +++++++++ src/pdicom.h | 28 ++ tests/check_dicom.c | 14 +- tools/dcm-getframe.c | 28 +- 8 files changed, 386 insertions(+), 865 deletions(-) diff --git a/TODO b/TODO index 18f318f..7477760 100644 --- a/TODO +++ b/TODO @@ -5,36 +5,12 @@ add a "-a" flag to print everything (inc. pixel data and perframefunctionalgroupsequence etc.) -- remove dupe code +- test getframe -- update build-bot - -- getframe should use frame_index +- update docs - update openslide for new thing -# bot support - -- need more tests and test images - -- extended offset table support seems to be broken? see FIXME comment - - probably will never be used - -# data types - -- bot frame offsets should be int64_t to match dcm_seekset(), - pixel_data_offset, etc. - -- 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 @@ -42,21 +18,6 @@ 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 diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index dfe7fc6..6650737 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -140,11 +140,6 @@ typedef struct _DcmSequence DcmSequence; */ typedef struct _DcmFrame DcmFrame; -/** - * Basic Offset Table (BOT) Item of Pixel Data Element - */ -typedef struct _DcmBOT DcmBOT; - /** * Start up libdicom. * @@ -1374,69 +1369,6 @@ DCM_EXTERN void dcm_frame_destroy(DcmFrame *frame); -/** - * Basic Offset Table (BOT). - */ - -/** - * 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 - */ -DCM_EXTERN -DcmBOT *dcm_bot_create(DcmError **error, - ssize_t *offsets, - uint32_t num_frames, - ssize_t first_frame_offset); - -/** - * Get number of Frame offsets in the Basic Offset Table. - * - * :param bot: Basic Offset Table - * - * :return: number of frames - */ -DCM_EXTERN -uint32_t dcm_bot_get_num_frames(const DcmBOT *bot); - -/** - * Get Frame offset in the Basic Offset Table. - * - * :param bot: Basic Offset Table - * :param index: One-based index of Frame in the Pixel Data Element - * - * :return: offset from pixel_data_offset - */ -DCM_EXTERN -ssize_t dcm_bot_get_frame_offset(const DcmBOT *bot, uint32_t index); - -/** - * Print a Basic Offset Table. - * - * :param bot: Basic Offset Table - */ -DCM_EXTERN -void dcm_bot_print(const DcmBOT *bot); - -/** - * Destroy a Basic Offset Table. - * - * :param bot: Basic Offset Table - */ -DCM_EXTERN -void dcm_bot_destroy(DcmBOT *bot); - - /** * Part 10 File */ @@ -1648,51 +1580,34 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, DcmFilehandle *filehandle); /** - * Read Basic Offset Table from a File. - * - * 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. - * - * :param error: Pointer to error object - * :param filehandle: File - * :param metadata: Metadata + * Read everything necessary to fetch frames from the file. * - * :return: Basic Offset Table - */ -DCM_EXTERN -DcmBOT *dcm_filehandle_read_bot(DcmError **error, DcmFilehandle *filehandle, - DcmDataSet *metadata); - -/** - * Build Basic Offset Table for a File. + * Scans the PixelData sequence and loads the + * PerFrameFunctionalGroupSequence, if present. * * :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_read_pixeldata(DcmError **error, + DcmFilehandle *filehandle); /** * Read an individual Frame from a File. * + * Frames are numbered in row-major order starting at the top left. + * * :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); #endif diff --git a/src/dicom-data.c b/src/dicom-data.c index 3d069e6..e0daf2f 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; }; @@ -1878,84 +1871,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-file.c b/src/dicom-file.c index 2006a4a..5fb0dfa 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -24,24 +24,13 @@ #include "pdicom.h" -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; -}; - - struct _DcmFilehandle { DcmIO *io; int64_t offset; char *transfer_syntax_uid; int64_t pixel_data_offset; + ssize_t first_frame_offset; + int64_t *offset_table; uint64_t *extended_offset_table; bool byteswap; bool implicit; @@ -50,6 +39,7 @@ struct _DcmFilehandle { uint32_t tiles_across; uint32_t num_frames; uint32_t *frame_index; + struct PixelDescription desc; // push and pop these while we parse UT_array *dataset_stack; @@ -210,20 +200,6 @@ static bool dcm_seekset(DcmError **error, } -static bool dcm_seekcur(DcmError **error, DcmFilehandle *filehandle, - int64_t offset, int64_t *position) -{ - int64_t new_offset = dcm_io_seek(error, filehandle->io, offset, SEEK_CUR); - if (new_offset < 0) { - return false; - } - - *position += offset; - - return true; -} - - static bool dcm_offset(DcmError **error, DcmFilehandle *filehandle, int64_t *offset) { @@ -238,117 +214,6 @@ static bool dcm_offset(DcmError **error, } -static bool dcm_is_eof(DcmFilehandle *filehandle) -{ - int64_t position = 0; - bool eof = true; - - char buffer[1]; - int64_t bytes_read = dcm_io_read(NULL, filehandle->io, buffer, 1); - if (bytes_read > 0) { - eof = false; - (void) dcm_seekcur(NULL, filehandle, -1, &position); - } - - return eof; -} - - -static void byteswap(char *data, size_t length, size_t size) -{ - assert(length >= size); - - if (size > 1) { - assert(length % size == 0); - assert(size % 2 == 0); - - size_t half_size = size / 2; - - 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; - } - } - } -} - - -static bool read_uint16(DcmError **error, DcmFilehandle *filehandle, - uint16_t *value, int64_t *position) -{ - union { - uint16_t i; - char c[2]; - } buffer; - - if (!dcm_require(error, filehandle, buffer.c, 2, position)) { - return false; - } - - if (filehandle->byteswap) { - byteswap(buffer.c, 2, 2); - } - - *value = buffer.i; - - return true; -} - - -static bool read_uint32(DcmError **error, DcmFilehandle *filehandle, - uint32_t *value, int64_t *position) -{ - union { - uint32_t i; - char c[4]; - } buffer; - - if (!dcm_require(error, filehandle, buffer.c, 4, position)) { - return false; - } - - if (filehandle->byteswap) { - byteswap(buffer.c, 4, 4); - } - - *value = buffer.i; - - return true; -} - - -static bool read_tag(DcmError **error, DcmFilehandle *filehandle, - uint32_t *value, int64_t *position) -{ - uint16_t group, elem; - - if (!read_uint16(error, filehandle, &group, position) || - !read_uint16(error, filehandle, &elem, position)) { - return false; - } - - *value = ((uint32_t)group << 16) | elem; - - return true; -} - - -static bool read_iheader(DcmError **error, DcmFilehandle *filehandle, - uint32_t *item_tag, uint32_t *item_length, int64_t *position) -{ - if (!read_tag(error, filehandle, item_tag, position) || - !read_uint32(error, filehandle, item_length, position)) { - return false; - } - - return true; -} - - - static bool get_num_frames(DcmError **error, const DcmDataSet *metadata, uint32_t *number_of_frames) @@ -363,7 +228,7 @@ static bool get_num_frames(DcmError **error, return false; } - num_frames = (uint32_t) strtol(value, NULL, 10); + num_frames = strtol(value, NULL, 10); if (num_frames == 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Basic Offset Table read failed", @@ -407,357 +272,6 @@ static bool get_tiles_across(DcmError **error, } -static DcmElement *read_element_header(DcmError **error, - DcmFilehandle *filehandle, - uint32_t *length, - int64_t *position) -{ - uint32_t tag; - if (!read_tag(error, filehandle, &tag, position)) { - return NULL; - } - - DcmVR vr; - if (filehandle->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); - } - - if (!read_uint32(error, filehandle, length, position)) { - return NULL; - } - } else { - // Value Representation - char vr_str[3]; - if (!dcm_require(error, filehandle, vr_str, 2, position)) { - 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); - 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 (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; - } - } - } - - return dcm_element_create(error, tag, vr); -} - - -static bool set_pixel_description(DcmError **error, - struct PixelDescription *desc, - const DcmDataSet *metadata) -{ - DcmElement *element; - int64_t value; - const char *string; - - element = dcm_dataset_get(error, metadata, 0x00280010); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->rows = value; - - element = dcm_dataset_get(error, metadata, 0x00280011); - if (element == NULL || - !dcm_element_get_value_integer(error, element, 0, &value)) { - return false; - } - desc->columns = value; - - 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; - - 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; - - 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; - - 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; - - return true; -} - - -DcmBOT *dcm_filehandle_build_bot(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata) -{ - uint64_t i; - - dcm_log_debug("Building Basic Offset Table."); - - uint32_t num_frames; - if (!get_num_frames(error, metadata, &num_frames)) { - return NULL; - } - - 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_seekset(error, filehandle, filehandle->pixel_data_offset)) { - return NULL; - } - - // we measure offsets from this point - int64_t position = 0; - - uint32_t length; - DcmElement *element = read_element_header(error, - filehandle, - &length, - &position); - if (element == NULL) { - return NULL; - } - 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; - } - - ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); - if (offsets == NULL) { - return NULL; - } - - 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; - } - - 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; - } - - // Move filehandlepointer to the first byte of first Frame item - if (!dcm_seekcur(error, filehandle, length, &position)) { - free(offsets); - } - - // and that's the offset to the first frame - first_frame_offset = position; - - // now measure positions from the start of the first frame - position = 0; - - i = 0; - while (true) { - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - free(offsets); - return NULL; - } - - if (tag == TAG_SQ_DELIM) { - break; - } - - 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; - } - - if (dcm_is_eof(filehandle)) { - break; - } - - // step back to the start of the item for this frame - offsets[i] = position - 8; - - if (!dcm_seekcur(error, filehandle, length, &position)) { - free(offsets); - return NULL; - } - - i += 1; - } - - 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; - } - - return dcm_bot_create(error, offsets, num_frames, first_frame_offset); -} - - -DcmFrame *dcm_filehandle_read_frame(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata, - DcmBOT *bot, - uint32_t number) -{ - uint32_t length; - - 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; - } - - 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; - } - - struct PixelDescription desc; - if (!set_pixel_description(error, &desc, metadata)) { - return NULL; - } - - 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; - } - - 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; - } - - char *value = DCM_MALLOC(error, length); - if (value == NULL) { - return NULL; - } - if (!dcm_require(error, filehandle, value, length, &position)) { - free(value); - return NULL; - } - - 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); -} - - static bool parse_meta_dataset_begin(DcmError **error, void *client) { @@ -986,7 +500,6 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, } - static bool parse_meta_stop(void *client, uint32_t tag, DcmVR vr, @@ -1006,6 +519,74 @@ static bool parse_meta_stop(void *client, } +static bool set_pixel_description(DcmError **error, + struct PixelDescription *desc, + const DcmDataSet *metadata) +{ + DcmElement *element; + int64_t value; + const char *string; + + element = dcm_dataset_get(error, metadata, 0x00280010); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->rows = value; + + element = dcm_dataset_get(error, metadata, 0x00280011); + if (element == NULL || + !dcm_element_get_value_integer(error, element, 0, &value)) { + return false; + } + desc->columns = value; + + 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; + + 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; + + 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; + + 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; + + return true; +} + + DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, DcmFilehandle *filehandle) { @@ -1083,12 +664,24 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return false; } + // useful values for later + if (!get_tiles_across(error, meta, &filehandle->tiles_across) || + !get_num_frames(error, meta, &filehandle->num_frames)) { + return false; + } + + if (!set_pixel_description(error, &filehandle->desc, meta)) { + return false; + } + filehandle->desc.transfer_syntax_uid = filehandle->transfer_syntax_uid; + // did we stop on pixel data? record the offset if (filehandle->last_tag == TAG_PIXEL_DATA || filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { if (!dcm_offset(error, - filehandle, &filehandle->pixel_data_offset)) { + filehandle, + &filehandle->pixel_data_offset)) { return NULL; } } @@ -1147,19 +740,13 @@ static bool parse_frame_index_stop(void *client, static bool read_frame_index(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata) + DcmFilehandle *filehandle) { static DcmParse parse = { .element_create = parse_frame_index_element_create, .stop = parse_frame_index_stop, }; - if (!get_tiles_across(error, metadata, &filehandle->tiles_across) || - !get_num_frames(error, metadata, &filehandle->num_frames)) { - return false; - } - filehandle->frame_index = DCM_NEW_ARRAY(error, filehandle->num_frames, uint32_t); @@ -1169,7 +756,7 @@ static bool read_frame_index(DcmError **error, // we may not have all frames ... set to missing initially for (uint32_t i = 0; i < filehandle->num_frames; i++) { - filehandle->frame_index[i] = -1; + filehandle->frame_index[i] = 0xffffffff; } // parse just the per-frame stuff @@ -1187,31 +774,12 @@ static bool read_frame_index(DcmError **error, } -DcmBOT *dcm_filehandle_read_bot(DcmError **error, - DcmFilehandle *filehandle, - DcmDataSet *metadata) +bool dcm_filehandle_read_pixeldata(DcmError **error, + DcmFilehandle *filehandle) { - uint64_t value; - - dcm_log_debug("Reading Basic Offset Table."); - - 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); - return NULL; - } - - uint32_t num_frames; - if (!get_num_frames(error, metadata, &num_frames)) { - return NULL; - } - // did we stop on per-frame func group? parse that to build the index if (filehandle->last_tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE && - !read_frame_index(error, filehandle, metadata)) { + !read_frame_index(error, filehandle)) { return false; } @@ -1227,7 +795,7 @@ DcmBOT *dcm_filehandle_read_bot(DcmError **error, if (filehandle->pixel_data_offset == 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", + "Reading PixelData failed", "Could not determine offset of Pixel Data Element. " "Read metadata first"); return NULL; @@ -1237,112 +805,101 @@ DcmBOT *dcm_filehandle_read_bot(DcmError **error, 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); - 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"); + filehandle->offset_table = DCM_NEW_ARRAY(error, + filehandle->num_frames, + int64_t); + if (filehandle->offset_table == NULL) { return NULL; } - // The header of the BOT Item - if (!read_iheader(error, filehandle, &tag, &length, &position)) { - return NULL; + dcm_log_debug("Reading PixelData."); + + if (dcm_is_encapsulated_transfer_syntax(filehandle->transfer_syntax_uid)) { + // read the bot if available, otherwise parse pixeldata to find + // offsets + if (!dcm_parse_pixeldata(error, + filehandle->io, + filehandle->implicit, + filehandle->byteswap, + &filehandle->first_frame_offset, + filehandle->offset_table, + filehandle->num_frames)) { + return false; + } + } 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; + } + + // Header of Pixel Data Element + filehandle->first_frame_offset = 10; } - if (tag != TAG_ITEM) { + + return true; +} + + +DcmFrame *dcm_filehandle_read_frame(DcmError **error, + DcmFilehandle *filehandle, + uint32_t frame_number) +{ + dcm_log_debug("Read frame number #%u.", frame_number); + if (frame_number == 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Basic Offset Table failed", - "Unexpected Tag found for Basic Offset Table Item"); + "Reading Frame Item failed", + "Frame Number must be non-zero"); return NULL; } - - ssize_t *offsets = DCM_NEW_ARRAY(error, num_frames, ssize_t); - if (offsets == NULL) { + if (frame_number >= filehandle->num_frames) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Frame Item failed", + "Frame Number must be less than %u", + filehandle->num_frames); 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->frame_index) { + frame_number = filehandle->frame_index[frame_number]; + if (frame_number == 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); - return NULL; - } - - dcm_log_info("Found Extended Offset Table."); - - const char *blob; - if (!dcm_element_get_value_binary(error, eot_element, &blob)) { - free(offsets); + "Reading Frame Item failed", + "No such frame"); 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; - } + ssize_t total_frame_offset = filehandle->pixel_data_offset + + filehandle->first_frame_offset + + filehandle->offset_table[frame_number]; + if (!dcm_seekset(error, filehandle, total_frame_offset)) { + return NULL; + } - // FIXME is this correct? - first_frame_offset = position; + uint32_t length; + char *frame_data = dcm_parse_frame(error, + filehandle->io, + filehandle->implicit, + filehandle->byteswap, + &filehandle->desc, + &length); + if (frame_data == NULL) { + return NULL; } - return dcm_bot_create(error, offsets, num_frames, first_frame_offset); + 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); } - diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 23d5b11..0771cc1 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -685,3 +685,166 @@ bool dcm_parse_group(DcmError **error, 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(DcmError **error, + DcmIO *io, + bool implicit, + bool byteswap, + ssize_t *first_frame_offset, + ssize_t *offsets, + int num_frames) +{ + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .byteswap = byteswap + }; + + int64_t position = 0; + + uint32_t tag; + DcmVR vr; + uint32_t length; + 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++) { + uint32_t ui32; + if (!read_uint32(&state, &ui32, &position)) { + 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"); + return NULL; + } + + offsets[i] = value; + } + + // and that's the offset to the item header on the first frame + *first_frame_offset = position; + } else { + // the BOT is missing, we must scan pixeldata to find the position of + // each frame + + // 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) { + break; + } + + 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; + } + } + } + + return true; +} + +char *dcm_parse_frame(DcmError **error, + DcmIO *io, + bool implicit, + bool byteswap, + struct PixelDescription *desc, + uint32_t *length) +{ + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .byteswap = byteswap, + }; + + 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/pdicom.h b/src/pdicom.h index f3be7de..b405a05 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -133,3 +133,31 @@ bool dcm_parse_group(DcmError **error, bool byteswap, const DcmParse *parse, void *client); + +bool dcm_parse_pixeldata(DcmError **error, + DcmIO *io, + bool implicit, + bool byteswap, + ssize_t *first_frame_offset, + ssize_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, + bool byteswap, + struct PixelDescription *desc, + uint32_t *length); diff --git a/tests/check_dicom.c b/tests/check_dicom.c index d4cf980..868d4a2 100644 --- a/tests/check_dicom.c +++ b/tests/check_dicom.c @@ -709,19 +709,12 @@ START_TEST(test_file_sm_image_frame) DcmDataSet *metadata = dcm_filehandle_read_metadata(NULL, filehandle); ck_assert_ptr_nonnull(metadata); + ck_assert_int_ne(dcm_filehandle_read_pixeldata(NULL, filehandle), 0); + 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); DcmFrame *frame = dcm_filehandle_read_frame(NULL, - filehandle, metadata, bot, + 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,7 +730,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); } diff --git a/tools/dcm-getframe.c b/tools/dcm-getframe.c index 375aea1..75778eb 100644 --- a/tools/dcm-getframe.c +++ b/tools/dcm-getframe.c @@ -49,8 +49,6 @@ int main(int argc, char *argv[]) } const char *file_path = argv[i]; - 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); @@ -69,24 +67,19 @@ int main(int argc, char *argv[]) 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_clear(&error); - dcm_log_info("Build BOT"); - bot = dcm_filehandle_build_bot(&error, filehandle, metadata); - } - if (bot == NULL) { + uint32_t tag = dcm_dict_tag_from_keyword("NumberOfFrames"); + DcmElement *element; + const char *value; + if (!(element = dcm_dataset_get(&error, metadata, tag)) || + !dcm_element_get_value_string(&error, element, 0, &value)) { 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); + int num_frames = atoi(value); + int frame_number = atoi(argv[i + 1]); if (frame_number < 1 || frame_number > num_frames) { dcm_error_set(&error, DCM_ERROR_CODE_INVALID, "Bad frame number", @@ -94,7 +87,6 @@ int main(int argc, char *argv[]) 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; @@ -102,12 +94,11 @@ int main(int argc, char *argv[]) dcm_log_info("Read frame %u", frame_number); DcmFrame *frame = dcm_filehandle_read_frame(&error, - filehandle, metadata, - bot, frame_number); + filehandle, + frame_number); if (frame == NULL) { dcm_error_log(error); dcm_error_clear(&error); - dcm_bot_destroy(bot); dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; @@ -154,7 +145,6 @@ int main(int argc, char *argv[]) } dcm_frame_destroy(frame); - dcm_bot_destroy(bot); dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); From 5db2be4504b09158aa6c11d54f3c953193c53eab Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Jun 2023 17:30:59 +0100 Subject: [PATCH 20/82] test, docs, bump version --- TODO | 4 +--- doc/source/usage.rst | 14 ++++++------- include/dicom/dicom.h | 2 +- meson.build | 4 ++-- src/dicom-file.c | 47 +++++++++++++++++++++++++++++++------------ src/dicom-parse.c | 32 +++++++++++++++++++++++------ tools/dcm-getframe.c | 7 +++++++ 7 files changed, 78 insertions(+), 32 deletions(-) diff --git a/TODO b/TODO index 7477760..d10191a 100644 --- a/TODO +++ b/TODO @@ -5,9 +5,7 @@ add a "-a" flag to print everything (inc. pixel data and perframefunctionalgroupsequence etc.) -- test getframe - -- update docs + expose parser in public API? it'd be tricky to bind to python - update openslide for new thing diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 7605744..dca734e 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -107,14 +107,14 @@ 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()`. +of the Data Set (i.e., all Data Elements with the exception of the +Pixel Data Element and Per Frame Functional Group) 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()`. +instance, the remaining parts of the image metadata may be loaded with +:c:func:`dcm_filehandle_read_pixeldata()`. After this, individual frames +may be read out with :c:func:`dcm_filehandle_read_frame()`. Thread safety +++++++++++++ diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 6650737..a55adae 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1597,7 +1597,7 @@ bool dcm_filehandle_read_pixeldata(DcmError **error, /** * Read an individual Frame from a File. * - * Frames are numbered in row-major order starting at the top left. + * Frames are numbered from 1 in row-major order starting at the top left. * * :param error: Pointer to error object * :param filehandle: File diff --git a/meson.build b/meson.build index e60bc4d..a6a6e29 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,7 @@ project( ], license : 'MIT', meson_version : '>=0.50', - version : '0.3.0' + version : '0.4.0' ) if not meson.is_subproject() meson.add_dist_script( @@ -34,7 +34,7 @@ endif # 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_minor = 4 abi_version_patch = 0 abi_version = '@0@.@1@.@2@'.format( diff --git a/src/dicom-file.c b/src/dicom-file.c index 5fb0dfa..08b759d 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -26,21 +26,32 @@ struct _DcmFilehandle { DcmIO *io; - int64_t offset; char *transfer_syntax_uid; - int64_t pixel_data_offset; - ssize_t first_frame_offset; - int64_t *offset_table; - uint64_t *extended_offset_table; bool byteswap; bool implicit; - uint32_t last_tag; - uint32_t frame_number; + + // start of image metadata + int64_t offset; + // start of pixel metadata + int64_t pixel_data_offset; + // distance from pixel metadata to start of first frame + ssize_t first_frame_offset; + + // image properties we need to track uint32_t tiles_across; uint32_t num_frames; - uint32_t *frame_index; struct PixelDescription desc; + // 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; + + // used to count frames as we scan perframefunctionalgroup + uint32_t frame_number; + // push and pop these while we parse UT_array *dataset_stack; UT_array *sequence_stack; @@ -72,7 +83,6 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) 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_index = NULL; @@ -847,13 +857,21 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, uint32_t frame_number) { dcm_log_debug("Read frame number #%u.", frame_number); + + if (filehandle->offset_table == NULL) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Frame Item failed", + "No offset table loaded"); + return NULL; + } + 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 (frame_number >= filehandle->num_frames) { + if (frame_number > filehandle->num_frames) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading Frame Item failed", "Frame Number must be less than %u", @@ -861,9 +879,12 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, return NULL; } + // we are zero-based from here on + uint32_t i = frame_number - 1; + if (filehandle->frame_index) { - frame_number = filehandle->frame_index[frame_number]; - if (frame_number == 0xffffffff) { + i = filehandle->frame_index[i]; + if (i == 0xffffffff) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading Frame Item failed", "No such frame"); @@ -873,7 +894,7 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, ssize_t total_frame_offset = filehandle->pixel_data_offset + filehandle->first_frame_offset + - filehandle->offset_table[frame_number]; + filehandle->offset_table[i]; if (!dcm_seekset(error, filehandle, total_frame_offset)) { return NULL; } diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 0771cc1..56ab623 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -713,6 +713,7 @@ bool dcm_parse_pixeldata(DcmError **error, uint32_t tag; DcmVR vr; uint32_t length; + uint32_t value; if (!parse_element_header(&state, &tag, &vr, &length, &position)) { return false; } @@ -746,17 +747,15 @@ bool dcm_parse_pixeldata(DcmError **error, // 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++) { - uint32_t ui32; - if (!read_uint32(&state, &ui32, &position)) { - return NULL; + if (!read_uint32(&state, &value, &position)) { + return false; } - 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"); - return NULL; + return false; } offsets[i] = value; @@ -764,6 +763,15 @@ bool dcm_parse_pixeldata(DcmError **error, // 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_uint32(&state, &value, &position) || + value != 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 @@ -779,7 +787,10 @@ bool dcm_parse_pixeldata(DcmError **error, } if (tag == TAG_SQ_DELIM) { - break; + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Basic Offset Table failed", + "Too few frames in PixelData."); + return false; } if (tag != TAG_ITEM) { @@ -799,6 +810,15 @@ bool dcm_parse_pixeldata(DcmError **error, 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; diff --git a/tools/dcm-getframe.c b/tools/dcm-getframe.c index 75778eb..688480f 100644 --- a/tools/dcm-getframe.c +++ b/tools/dcm-getframe.c @@ -67,6 +67,13 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } + if (!dcm_filehandle_read_pixeldata(&error, filehandle)) { + dcm_error_log(error); + dcm_error_clear(&error); + dcm_filehandle_destroy(filehandle); + return EXIT_FAILURE; + } + uint32_t tag = dcm_dict_tag_from_keyword("NumberOfFrames"); DcmElement *element; const char *value; From 99f28c593ece628778e9c3b66639f15aa5b0ce73 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 2 Jun 2023 12:41:44 +0100 Subject: [PATCH 21/82] remove error return from DcmIO close we never used it --- include/dicom/dicom.h | 7 ++----- src/dicom-file.c | 15 +++++++-------- src/dicom-io.c | 31 +++++++------------------------ 3 files changed, 16 insertions(+), 37 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index a55adae..7ecab9b 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1391,7 +1391,7 @@ typedef struct _DcmIOMethods { DcmIO *(*open)(DcmError **error, void *client); /** Close an IO object */ - bool (*close)(DcmError **error, DcmIO *io); + void (*close)(DcmIO *io); /** Read from an IO object, semantics as POSIX read() */ int64_t (*read)(DcmError **error, @@ -1449,13 +1449,10 @@ DcmIO *dcm_io_create_from_memory(DcmError **error, /** * Close an IO object. * - * :param error: Error structure pointer * :param io: Pointer to IO object - * - * :return: true on success */ DCM_EXTERN -bool dcm_io_close(DcmError **error, DcmIO *io); +void dcm_io_close(DcmIO *io); /** * Read from an IO object. diff --git a/src/dicom-file.c b/src/dicom-file.c index 08b759d..c50214d 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -155,7 +155,7 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) free(filehandle->frame_index); } - (void) dcm_io_close(NULL, filehandle->io); + dcm_io_close(filehandle->io); utarray_free(filehandle->dataset_stack); utarray_free(filehandle->sequence_stack); @@ -858,13 +858,6 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, { dcm_log_debug("Read frame number #%u.", frame_number); - if (filehandle->offset_table == NULL) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Frame Item failed", - "No offset table loaded"); - return NULL; - } - if (frame_number == 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading Frame Item failed", @@ -879,6 +872,12 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, return NULL; } + // load metadata around pixeldata, if we've not loaded it already + if (filehandle->offset_table == NULL && + !dcm_filehandle_read_pixeldata(error, filehandle)) { + return NULL; + } + // we are zero-based from here on uint32_t i = frame_number - 1; diff --git a/src/dicom-io.c b/src/dicom-io.c index 23f70c1..4f64a00 100644 --- a/src/dicom-io.c +++ b/src/dicom-io.c @@ -49,30 +49,16 @@ typedef struct _DcmIOFile { } DcmIOFile; -static bool dcm_io_close_file(DcmError **error, DcmIO *io) +static void dcm_io_close_file(DcmIO *io) { DcmIOFile *file = (DcmIOFile *) io; - int close_errno = 0; if (file->fd != -1) { - if (close(file->fd)) { - close_errno = errno; - } - - file->fd = -1; - - if (close_errno) { - dcm_error_set(error, DCM_ERROR_CODE_IO, - "Unable to close filehandle", - "Unable to close %s - %s", - file->filename, strerror(close_errno)); - } + (void) close(file->fd); } free(file->filename); free(file); - - return close_errno == 0; } @@ -89,7 +75,7 @@ static DcmIO *dcm_io_open_file(DcmError **error, void *client) const char *filename = (const char *) client; file->filename = dcm_strdup(error, filename); if (file->filename == NULL) { - (void) dcm_io_close_file(error, (DcmIO *)file); + dcm_io_close_file((DcmIO *)file); return NULL; } @@ -120,7 +106,7 @@ static DcmIO *dcm_io_open_file(DcmError **error, void *client) dcm_error_set(error, DCM_ERROR_CODE_IO, "Unable to open filehandle", "Unable to open %s - %s", file->filename, strerror(open_errno)); - (void) dcm_io_close_file(error, (DcmIO *)file); + dcm_io_close_file((DcmIO *)file); return NULL; } @@ -293,14 +279,11 @@ typedef struct _DcmIOMemory { } DcmIOMemory; -static bool dcm_io_close_memory(DcmError **error, DcmIO *io) +static void dcm_io_close_memory(DcmIO *io) { DcmIOMemory *memory = (DcmIOMemory *) io; - USED(error); free(memory); - - return true; } @@ -393,9 +376,9 @@ DcmIO *dcm_io_create_from_memory(DcmError **error, } -bool dcm_io_close(DcmError **error, DcmIO *io) +void dcm_io_close(DcmIO *io) { - return io->methods->close(error, io); + io->methods->close(io); } From 17561be8997d3c8a3dd5bc01ae4bbfeaf270a26e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 2 Jun 2023 13:04:50 +0100 Subject: [PATCH 22/82] remove stray size_t --- src/dicom-file.c | 2 +- src/dicom-parse.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index c50214d..375514d 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -35,7 +35,7 @@ struct _DcmFilehandle { // start of pixel metadata int64_t pixel_data_offset; // distance from pixel metadata to start of first frame - ssize_t first_frame_offset; + int64_t first_frame_offset; // image properties we need to track uint32_t tiles_across; diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 56ab623..6ea19c1 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -697,8 +697,8 @@ bool dcm_parse_pixeldata(DcmError **error, DcmIO *io, bool implicit, bool byteswap, - ssize_t *first_frame_offset, - ssize_t *offsets, + int64_t *first_frame_offset, + int64_t *offsets, int num_frames) { DcmParseState state = { From 012debfefd012a1fcd8c867032075116a5304c11 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 2 Jun 2023 13:29:11 +0100 Subject: [PATCH 23/82] fix clang build --- src/dicom-data.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index e0daf2f..c2664fc 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -921,6 +921,8 @@ bool dcm_element_set_value(DcmError **error, uint32_t length, bool steal) { + size_t size; + switch (dcm_dict_vr_class(element->vr)) { case DCM_CLASS_STRING_SINGLE: @@ -931,7 +933,7 @@ bool dcm_element_set_value(DcmError **error, break; case DCM_CLASS_NUMERIC: - size_t size = dcm_dict_vr_size(element->vr); + size = dcm_dict_vr_size(element->vr); if (length % size != 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading of Data Element failed", From 68464f857fe0a6b72dfa3b62f434a91226cbd4bf Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 2 Jun 2023 13:33:44 +0100 Subject: [PATCH 24/82] remove another size_t --- src/pdicom.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pdicom.h b/src/pdicom.h index b405a05..1b24578 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -138,8 +138,8 @@ bool dcm_parse_pixeldata(DcmError **error, DcmIO *io, bool implicit, bool byteswap, - ssize_t *first_frame_offset, - ssize_t *offsets, + int64_t *first_frame_offset, + int64_t *offsets, int num_frames); struct PixelDescription { From c275ed56b9e214cd5b85550da52abbe10bddd024 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 2 Jun 2023 16:22:22 +0100 Subject: [PATCH 25/82] also stop on REFERENCED_IMAGE_NAVIGATION_SEQUENCE So we don't load all of localizer images unnecessarily. --- src/dicom-file.c | 1 + src/pdicom.h | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index 375514d..ad928f7 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -523,6 +523,7 @@ static bool parse_meta_stop(void *client, filehandle->last_tag = tag; return tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE || + tag == TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE || tag == TAG_PIXEL_DATA || tag == TAG_FLOAT_PIXEL_DATA || tag == TAG_DOUBLE_PIXEL_DATA; diff --git a/src/pdicom.h b/src/pdicom.h index 1b24578..25d6f47 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -38,16 +38,17 @@ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define USED(x) (void)(x) -#define TAG_ITEM 0xFFFEE000 -#define TAG_ITEM_DELIM 0xFFFEE00D -#define TAG_SQ_DELIM 0xFFFEE0DD +#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_TRAILING_PADDING 0xFFFCFFFC -#define TAG_PIXEL_DATA 0x7FE00010 #define TAG_FLOAT_PIXEL_DATA 0x7FE00008 #define TAG_DOUBLE_PIXEL_DATA 0x7FE00009 -#define TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE 0x52009230 -#define TAG_DIMENSION_INDEX_VALUES 0x00209157 +#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_calloc(DcmError **error, size_t n, size_t size); From aee070852aacccd2d219f5d57d1067e9ac8b6427 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 4 Jun 2023 11:52:00 +0100 Subject: [PATCH 26/82] add dcm_filehandle_read_frame_position to fetch a file at an (x, y) position --- include/dicom/dicom.h | 23 ++++++++++++++++-- src/dicom-file.c | 56 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 7ecab9b..6085f5b 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1591,10 +1591,30 @@ DCM_EXTERN bool dcm_filehandle_read_pixeldata(DcmError **error, DcmFilehandle *filehandle); +/** + * Read the frame at a position in a File. + * + * Read a tile from a File at a specified (column, row), numbered from zero. + * This takes account of any frame positioning given in + * PerFrameFunctionalGroupSequence, if necessary. + * + * :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); + /** * Read an individual Frame from a File. * - * Frames are numbered from 1 in row-major order starting at the top left. + * Frames are numbered from 1 in the order they appear in the PixelData element. * * :param error: Pointer to error object * :param filehandle: File @@ -1606,5 +1626,4 @@ DCM_EXTERN DcmFrame *dcm_filehandle_read_frame(DcmError **error, DcmFilehandle *filehandle, uint32_t frame_number); - #endif diff --git a/src/dicom-file.c b/src/dicom-file.c index ad928f7..26a4c1b 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -882,16 +882,6 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, // we are zero-based from here on uint32_t i = frame_number - 1; - if (filehandle->frame_index) { - i = filehandle->frame_index[i]; - if (i == 0xffffffff) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading Frame Item failed", - "No such frame"); - return NULL; - } - } - ssize_t total_frame_offset = filehandle->pixel_data_offset + filehandle->first_frame_offset + filehandle->offset_table[i]; @@ -924,3 +914,49 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, 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); + + // load metadata around pixeldata, if we've not loaded it already + if (filehandle->offset_table == NULL && + !dcm_filehandle_read_pixeldata(error, filehandle)) { + return NULL; + } + + if (column >= filehandle->tiles_across) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Frame position failed", + "Column must be less than %u", + filehandle->tiles_across); + return 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; + } + + if (filehandle->frame_index) { + index = filehandle->frame_index[index]; + if (index == 0xffffffff) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading Frame position failed", + "No Frame at position (%u, %u)", column, row); + return NULL; + } + } + + // read_frame() numbers from 1 + return dcm_filehandle_read_frame(error, filehandle, index + 1); +} From 343eecc2fc0e8af00415675b67ce4919a5e04b64 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 4 Jun 2023 11:59:41 +0100 Subject: [PATCH 27/82] make TotalPixelMatrixColumns optional helps openslide tests --- src/dicom-file.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index 26a4c1b..ac95adc 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -271,11 +271,15 @@ static bool get_tiles_across(DcmError **error, int64_t width; int64_t tile_width; - if (!get_tag_int(error, metadata, "TotalPixelMatrixColumns", &width) || - !get_tag_int(error, metadata, "Columns", &tile_width)) { + if (!get_tag_int(error, metadata, "Columns", &tile_width)) { return false; } + // 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; From 9dfbacfa37cae494599cad5ad6d05addc5ab8d8a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 4 Jun 2023 12:20:57 +0100 Subject: [PATCH 28/82] fix sanity check in BOT read we had the high and low words swapped --- src/dicom-parse.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 6ea19c1..cc743cc 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -765,8 +765,8 @@ bool dcm_parse_pixeldata(DcmError **error, *first_frame_offset = position; // the next thing should be the tag for frame 1 - if (!read_uint32(&state, &value, &position) || - value != TAG_ITEM) { + 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"); From d2cc5e78048d27aa84da908ae046410ae896e656 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 5 Jun 2023 13:58:57 +0100 Subject: [PATCH 29/82] improve handling of localizer in 3dhistech we were not skipping to pixeldata correctly if there was localizer and no per frame func group --- src/dicom-file.c | 57 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index ac95adc..d555291 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -85,6 +85,7 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) filehandle->pixel_data_offset = 0; filehandle->byteswap = is_big_endian(); filehandle->last_tag = 0xffffffff; + filehandle->frame_number = 0; filehandle->frame_index = NULL; utarray_new(filehandle->dataset_stack, &ut_ptr_icd); utarray_new(filehandle->sequence_stack, &ut_ptr_icd); @@ -526,8 +527,8 @@ static bool parse_meta_stop(void *client, filehandle->last_tag = tag; - return tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE || - tag == TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE || + return tag == TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE || + tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE || tag == TAG_PIXEL_DATA || tag == TAG_FLOAT_PIXEL_DATA || tag == TAG_DOUBLE_PIXEL_DATA; @@ -710,6 +711,7 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } + static bool parse_frame_index_element_create(DcmError **error, void *client, uint32_t tag, @@ -789,10 +791,56 @@ static bool read_frame_index(DcmError **error, } +static bool parse_skip_to_index(void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + USED(vr); + USED(length); + + filehandle->last_tag = tag; + + return tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE || + tag == TAG_PIXEL_DATA || + tag == TAG_FLOAT_PIXEL_DATA || + tag == TAG_DOUBLE_PIXEL_DATA; +} + + +static bool read_skip_to_index(DcmError **error, + DcmFilehandle *filehandle) + +{ + static DcmParse parse = { + .stop = parse_skip_to_index, + }; + + if (!dcm_parse_dataset(error, + filehandle->io, + filehandle->implicit, + filehandle->byteswap, + &parse, + filehandle)) { + return false; + } + + return true; +} + + bool dcm_filehandle_read_pixeldata(DcmError **error, DcmFilehandle *filehandle) { - // did we stop on per-frame func group? parse that to build the index + // 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; + } + + // 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; @@ -811,8 +859,7 @@ bool dcm_filehandle_read_pixeldata(DcmError **error, if (filehandle->pixel_data_offset == 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading PixelData failed", - "Could not determine offset of Pixel Data Element. " - "Read metadata first"); + "Could not determine offset of Pixel Data Element."); return NULL; } From 4828495d3c5c325fb258b9df915f7948e8d4fbde Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 6 Jun 2023 10:17:11 +0100 Subject: [PATCH 30/82] only look up tiles in 3d and sparse mode since some DICOMs have an empty perframefunctionalgroupsequence even though they are not sparse tiled images --- src/dicom-file.c | 64 +++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index d555291..3480d8e 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -23,6 +23,10 @@ #include #include "pdicom.h" +typedef enum _DcmLayout { + DCM_LAYOUT_SPARSE, + DCM_LAYOUT_FULL, +} DcmLayout; struct _DcmFilehandle { DcmIO *io; @@ -41,6 +45,7 @@ struct _DcmFilehandle { uint32_t tiles_across; uint32_t num_frames; struct PixelDescription desc; + DcmLayout layout; // both zero-indexed and length num_frames uint32_t *frame_index; @@ -86,6 +91,7 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) 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->dataset_stack, &ut_ptr_icd); utarray_new(filehandle->sequence_stack, &ut_ptr_icd); @@ -225,21 +231,40 @@ static bool dcm_offset(DcmError **error, } +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) { - 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)) { + if (!get_tag_str(error, metadata, "NumberOfFrames", &value)) { return false; } - num_frames = strtol(value, NULL, 10); + 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", @@ -253,18 +278,6 @@ static bool get_num_frames(DcmError **error, } -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_tiles_across(DcmError **error, const DcmDataSet *metadata, uint32_t *tiles_across) @@ -691,6 +704,14 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } filehandle->desc.transfer_syntax_uid = filehandle->transfer_syntax_uid; + // we support sparse and full tile layout + const char *type; + if (get_tag_str(NULL, meta, "DimensionOrganizationType", &type) && + strcmp(type, "TILED_SPARSE") == 0 && + strcmp(type, "3D") == 0) { + filehandle->layout = DCM_LAYOUT_SPARSE; + } + // did we stop on pixel data? record the offset if (filehandle->last_tag == TAG_PIXEL_DATA || filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || @@ -711,7 +732,6 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } - static bool parse_frame_index_element_create(DcmError **error, void *client, uint32_t tag, @@ -791,7 +811,7 @@ static bool read_frame_index(DcmError **error, } -static bool parse_skip_to_index(void *client, +static bool parse_skip_to_index(void *client, uint32_t tag, DcmVR vr, uint32_t length) @@ -998,7 +1018,7 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, return NULL; } - if (filehandle->frame_index) { + if (filehandle->layout == DCM_LAYOUT_SPARSE) { index = filehandle->frame_index[index]; if (index == 0xffffffff) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, From a8062a4e5f221f41d8a9944153681e5041ead49d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 6 Jun 2023 16:54:43 +0100 Subject: [PATCH 31/82] add dcm_sequence_steal() resolves a memleak just internal API for now --- src/dicom-data.c | 18 ++++++++++++++++++ src/dicom-file.c | 42 +++++++++++++----------------------------- src/pdicom.h | 3 +++ 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index c2664fc..0710a4c 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -1631,6 +1631,24 @@ DcmDataSet *dcm_sequence_get(DcmError **error, } +DcmDataSet *dcm_sequence_steal(DcmError **error, + const DcmSequence *seq, uint32_t index) +{ + 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; +} + + void dcm_sequence_foreach(const DcmSequence *seq, void (*fn)(const DcmDataSet *item)) { diff --git a/src/dicom-file.c b/src/dicom-file.c index 3480d8e..6806cff 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -138,7 +138,7 @@ static void dcm_filehandle_clear(DcmFilehandle *filehandle) utarray_clear(filehandle->dataset_stack); - for (i = 0; i < utarray_len(filehandle->dataset_stack); i++) { + for (i = 0; i < utarray_len(filehandle->sequence_stack); i++) { DcmSequence *sequence = *((DcmSequence **) utarray_eltptr(filehandle->sequence_stack, i)); @@ -162,6 +162,10 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) free(filehandle->frame_index); } + if (filehandle->offset_table) { + free(filehandle->offset_table); + } + dcm_io_close(filehandle->io); utarray_free(filehandle->dataset_stack); @@ -450,12 +454,7 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, return NULL; } - // sanity ... the parse stacks should be empty - if (utarray_len(filehandle->dataset_stack) != 0 || - utarray_len(filehandle->sequence_stack) != 0) { - abort(); - } - + dcm_filehandle_clear(filehandle); DcmSequence *sequence = dcm_sequence_create(error); if (sequence == NULL) { return NULL; @@ -469,7 +468,6 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, false, &parse, filehandle)) { - dcm_filehandle_clear(filehandle); return false; } @@ -493,9 +491,6 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, abort(); } - // FIXME ... add dcm_sequence_steal() so we can destroy sequence without - // also destroying meta - // right now we leak sequence DcmDataSet *file_meta = dcm_sequence_get(error, sequence, 0); if (file_meta == NULL ) { return false; @@ -519,10 +514,9 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, return NULL; } - // we need to pop sequence off the dataset stack to stop it being destroyed - utarray_pop_back(filehandle->sequence_stack); - - dcm_dataset_lock(file_meta); + // steal file_meta to stop it being destroyed + (void) dcm_sequence_steal(NULL, sequence, 0); + dcm_filehandle_clear(filehandle); return file_meta; } @@ -647,12 +641,7 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } } - // sanity ... the parse stacks should be empty - if (utarray_len(filehandle->dataset_stack) != 0 || - utarray_len(filehandle->sequence_stack) != 0) { - abort(); - } - + dcm_filehandle_clear(filehandle); DcmSequence *sequence = dcm_sequence_create(error); if (sequence == NULL) { return NULL; @@ -666,7 +655,6 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, filehandle->byteswap, &parse, filehandle)) { - dcm_filehandle_clear(filehandle); return false; } @@ -685,9 +673,6 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, abort(); } - // FIXME ... add dcm_sequence_steal() so we can destroy sequence without - // also destroying meta - // right now we leak sequence DcmDataSet *meta = dcm_sequence_get(error, sequence, 0); if (meta == NULL ) { return false; @@ -723,10 +708,9 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } } - // we need to pop sequence off the dataset stack to stop it being destroyed - utarray_pop_back(filehandle->sequence_stack); - - dcm_dataset_lock(meta); + // steal meta to stop it being destroyed + (void) dcm_sequence_steal(NULL, sequence, 0); + dcm_filehandle_clear(filehandle); return meta; } diff --git a/src/pdicom.h b/src/pdicom.h index 25d6f47..0837cef 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -95,6 +95,9 @@ int dcm_dict_vr_header_length(DcmVR vr); 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); From 07a866b22e4f506dc7beb8a31b5fda511374c136 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 7 Jun 2023 12:48:00 +0100 Subject: [PATCH 32/82] make DOT test stricter --- src/dicom-file.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index 6806cff..280918c 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -26,6 +26,7 @@ typedef enum _DcmLayout { DCM_LAYOUT_SPARSE, DCM_LAYOUT_FULL, + DCM_LAYOUT_UNKNOWN, } DcmLayout; struct _DcmFilehandle { @@ -689,12 +690,17 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } filehandle->desc.transfer_syntax_uid = filehandle->transfer_syntax_uid; - // we support sparse and full tile layout + // 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) && - strcmp(type, "TILED_SPARSE") == 0 && - strcmp(type, "3D") == 0) { - filehandle->layout = DCM_LAYOUT_SPARSE; + 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 { + filehandle->layout = DCM_LAYOUT_UNKNOWN; + } } // did we stop on pixel data? record the offset @@ -838,6 +844,13 @@ static bool read_skip_to_index(DcmError **error, bool dcm_filehandle_read_pixeldata(DcmError **error, DcmFilehandle *filehandle) { + if (filehandle->layout == DCM_LAYOUT_UNKNOWN) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading PixelData failed", + "Unsupported DimensionOrganisationType."); + return false; + } + // 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)) { From 7559ae45f8ba776afb8a9abb03618c248273fb26 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 7 Jun 2023 12:51:50 +0100 Subject: [PATCH 33/82] remove redef of DcmIOMethods to please clang --- include/dicom/dicom.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 6085f5b..08099ee 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1386,7 +1386,7 @@ typedef struct _DcmIO { /** * A set of IO methods, see dcm_io_create(). */ -typedef struct _DcmIOMethods { +struct _DcmIOMethods { /** Open an IO object */ DcmIO *(*open)(DcmError **error, void *client); @@ -1404,7 +1404,7 @@ typedef struct _DcmIOMethods { DcmIO *io, int64_t offset, int whence); -} DcmIOMethods; +}; /** * Create an IO object using a set of IO methods. From fd777251f5219a7fcc92ebda26828f26618a1c4e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 8 Jun 2023 09:03:18 +0100 Subject: [PATCH 34/82] revise TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index d10191a..e6ecd20 100644 --- a/TODO +++ b/TODO @@ -7,8 +7,6 @@ expose parser in public API? it'd be tricky to bind to python -- update openslide for new thing - # asserts From e47bcd46b08577694ef6649c6d1f9b851e92e8a9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 9 Jun 2023 09:43:03 +0100 Subject: [PATCH 35/82] fix foreach dataset and sequence iterators they were missing a client param and early termination --- include/dicom/dicom.h | 41 +++++++++++++++++++++++++++++++---------- meson.build | 4 ++-- src/dicom-data.c | 27 ++++++++++++++++++++------- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 08099ee..4af1394 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -989,15 +989,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 - * :param fn: Pointer to function that should be called for each Data Element + * 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. @@ -1124,14 +1133,26 @@ 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, void *client), + void *client); /** * Remove a Data Set item from a Sequence. diff --git a/meson.build b/meson.build index a6a6e29..9fab032 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,7 @@ project( ], license : 'MIT', meson_version : '>=0.50', - version : '0.4.0' + version : '0.5.0' ) if not meson.is_subproject() meson.add_dist_script( @@ -34,7 +34,7 @@ endif # 2. Backward-compatible ABI change: bump minor, reset patch # 3. Other, eg. bugfix: bump patch abi_version_major = 0 -abi_version_minor = 4 +abi_version_minor = 5 abi_version_patch = 0 abi_version = '@0@.@1@.@2@'.format( diff --git a/src/dicom-data.c b/src/dicom-data.c index 0710a4c..78e7b9a 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -1440,14 +1440,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; } @@ -1649,17 +1654,25 @@ DcmDataSet *dcm_sequence_steal(DcmError **error, } -void dcm_sequence_foreach(const DcmSequence *seq, - void (*fn)(const DcmDataSet *item)) +bool dcm_sequence_foreach(const DcmSequence *seq, + bool (*fn)(const DcmDataSet *item, void *client), + void *client) { uint32_t i; 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); + DcmDataSet *dataset = seq_item->dataset; + + dcm_dataset_lock(dataset); + + if (!fn(dataset, client)) { + return false; + } } + + return true; } From dfd725403a2c84e5e24697efcf1cc02f3df5c0bb Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 9 Jun 2023 12:32:22 +0100 Subject: [PATCH 36/82] expose vr class in the public API openslide needs it for iterating over properties --- include/dicom/dicom.h | 36 ++++++++++++++++++++++++++++++++++++ src/pdicom.h | 21 --------------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 4af1394..d96e0d0 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -229,6 +229,42 @@ typedef enum _DcmVR { DCM_VR_LAST } DcmVR; +/** + * The general class of the value associated with a Value Representation. + * + * DCM_CLASS_STRING_MULTI -- one or more null-terminated strings, cannot + * contain backslash + * + * DCM_CLASS_STRING_SINGLE -- a single null-terminated string, backslash + * allowed + * + * DCM_CLASS_NUMERIC -- one or more binary numeric values (float etc.), other + * fields give sizeof(type) + * + * DCM_CLASS_BINARY -- an uninterpreted array of bytes, length in the + * element header + * + * DCM_CLASS_SEQUENCE -- Value Representation is a seqeunce + */ +typedef enum _DcmVRClass { + DCM_CLASS_ERROR, + DCM_CLASS_STRING_MULTI, + DCM_CLASS_STRING_SINGLE, + DCM_CLASS_NUMERIC, + DCM_CLASS_BINARY, + DCM_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); + /** * Convert an error code to a human-readable string. * diff --git a/src/pdicom.h b/src/pdicom.h index 0837cef..5894f76 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -57,28 +57,7 @@ char *dcm_strdup(DcmError **error, const char *str); 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); From 9c39605e758f7dd0cd0c6b91de044e839db5bfae Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 10 Jun 2023 11:18:38 +0100 Subject: [PATCH 37/82] split NUMERIC class, add element->str --- include/dicom/dicom.h | 13 +++- src/dicom-data.c | 140 +++++++++++++++++++++++------------------- src/dicom-dict.c | 16 ++--- src/dicom-parse.c | 9 ++- src/dicom.c | 49 +++++++++++++++ src/pdicom.h | 3 +- 6 files changed, 153 insertions(+), 77 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index d96e0d0..31829e2 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -250,7 +250,8 @@ typedef enum _DcmVRClass { DCM_CLASS_ERROR, DCM_CLASS_STRING_MULTI, DCM_CLASS_STRING_SINGLE, - DCM_CLASS_NUMERIC, + DCM_CLASS_NUMERIC_FLOATINGPOINT, + DCM_CLASS_NUMERIC_INTEGER, DCM_CLASS_BINARY, DCM_CLASS_SEQUENCE } DcmVRClass; @@ -927,6 +928,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. * diff --git a/src/dicom-data.c b/src/dicom-data.c index 78e7b9a..a649a80 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -387,7 +387,8 @@ static bool dcm_element_validate(DcmError **error, DcmElement *element) return false; } - if (klass == DCM_CLASS_NUMERIC) { + if (klass == DCM_CLASS_NUMERIC_FLOATINGPOINT || + klass == DCM_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", @@ -626,7 +627,8 @@ static bool element_check_numeric(DcmError **error, const DcmElement *element) { DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass != DCM_CLASS_NUMERIC) { + if (klass != DCM_CLASS_NUMERIC_FLOATINGPOINT && + klass != DCM_CLASS_NUMERIC_INTEGER) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not numeric", "Element tag %08X is not numeric", @@ -932,7 +934,8 @@ bool dcm_element_set_value(DcmError **error, } break; - case DCM_CLASS_NUMERIC: + case DCM_CLASS_NUMERIC_FLOATINGPOINT: + case DCM_CLASS_NUMERIC_INTEGER: size = dcm_dict_vr_size(element->vr); if (length % size != 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, @@ -1144,7 +1147,8 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) } break; - case DCM_CLASS_NUMERIC: + case DCM_CLASS_NUMERIC_FLOATINGPOINT: + case DCM_CLASS_NUMERIC_INTEGER: if (element->vm == 1) { clone->value = element->value; clone->vm = 1; @@ -1180,40 +1184,79 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) // printing elements -static void element_print_integer(const DcmElement *element, - uint32_t index) +char *dcm_element_value_to_string(const DcmElement *element) { - 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); - } -} - + DcmVRClass klass = dcm_dict_vr_class(element->vr); -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); -} + char *result = NULL; + + if (element->vm > 1) { + result = dcm_printf_append(result, "["); + } + + for (uint32_t i = 0; i < element->vm; i++) { + switch (klass) { + case DCM_CLASS_NUMERIC_FLOATINGPOINT: + double d; + (void) dcm_element_get_value_floatingpoint(NULL, + element, + i, + &d); + result = dcm_printf_append(result, "%g", d); + break; + + case DCM_CLASS_NUMERIC_INTEGER: + int64_t i64; + (void) dcm_element_get_value_integer(NULL, + element, + i, + &i64); + + if (element->vr == DCM_VR_UV) { + result = dcm_printf_append(result, + "%"PRIu64, + (uint64_t)i64); + } else { + result = dcm_printf_append(result, "%"PRId64, i64); + } + break; + + case DCM_CLASS_STRING_SINGLE: + case DCM_CLASS_STRING_MULTI: + const char *str; + (void) dcm_element_get_value_string(NULL, + element, + i, + &str); + result = dcm_printf_append(result, "%s", str); + break; + + case DCM_CLASS_BINARY: + result = dcm_printf_append(result, + "", + dcm_element_get_length(element)); + break; + + case DCM_CLASS_SEQUENCE: + default: + dcm_log_warning("Unexpected Value Representation."); + } + if (element->vm > 1) { + if (i == element->vm - 1) { + result = dcm_printf_append(result, "]"); + } else { + result = dcm_printf_append(result, ", "); + } + } + } -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); + 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; @@ -1265,42 +1308,11 @@ void dcm_element_print(const DcmElement *element, int indentation) " "); } 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(", "); - } - } + char *str = dcm_element_value_to_string(element); + if (str != NULL) { + printf("%s\n", str); + free(str); } - printf("\n"); } } diff --git a/src/dicom-dict.c b/src/dicom-dict.c index 992e771..30981ef 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -67,9 +67,9 @@ static const struct _DcmVRTable vr_table[] = { {DCM_VR_DT, "DT", DCM_CLASS_STRING_MULTI, 0, DCM_CAPACITY_DT, 2}, - {DCM_VR_FL, "FL", DCM_CLASS_NUMERIC, + {DCM_VR_FL, "FL", DCM_CLASS_NUMERIC_FLOATINGPOINT, sizeof(float), 0, 2}, - {DCM_VR_FD, "FD", DCM_CLASS_NUMERIC, + {DCM_VR_FD, "FD", DCM_CLASS_NUMERIC_FLOATINGPOINT, sizeof(double), 0, 2}, {DCM_VR_IS, "IS", DCM_CLASS_STRING_SINGLE, @@ -96,13 +96,13 @@ static const struct _DcmVRTable vr_table[] = { {DCM_VR_SH, "SH", DCM_CLASS_STRING_MULTI, 0, DCM_CAPACITY_SH, 2}, - {DCM_VR_SL, "SL", DCM_CLASS_NUMERIC, + {DCM_VR_SL, "SL", DCM_CLASS_NUMERIC_INTEGER, sizeof(int32_t), 0, 2}, {DCM_VR_SQ, "SQ", DCM_CLASS_SEQUENCE, 0, 0, 4}, - {DCM_VR_SS, "SS", DCM_CLASS_NUMERIC, + {DCM_VR_SS, "SS", DCM_CLASS_NUMERIC_INTEGER, sizeof(int16_t), 0, 2}, {DCM_VR_ST, "ST", DCM_CLASS_STRING_SINGLE, @@ -114,13 +114,13 @@ static const struct _DcmVRTable vr_table[] = { {DCM_VR_UI, "UI", DCM_CLASS_STRING_MULTI, 0, DCM_CAPACITY_UI, 2}, - {DCM_VR_UL, "UL", DCM_CLASS_NUMERIC, + {DCM_VR_UL, "UL", DCM_CLASS_NUMERIC_INTEGER, sizeof(uint32_t), 0, 2}, {DCM_VR_UN, "UN", DCM_CLASS_BINARY, 0, 0, 4}, - {DCM_VR_US, "US", DCM_CLASS_NUMERIC, + {DCM_VR_US, "US", DCM_CLASS_NUMERIC_INTEGER, sizeof(uint16_t), 0, 2}, {DCM_VR_UT, "UT", DCM_CLASS_STRING_SINGLE, @@ -136,9 +136,9 @@ static const struct _DcmVRTable vr_table[] = { {DCM_VR_OV, "OV", DCM_CLASS_BINARY, 0, 0, 4}, - {DCM_VR_SV, "SV", DCM_CLASS_NUMERIC, + {DCM_VR_SV, "SV", DCM_CLASS_NUMERIC_INTEGER, sizeof(int64_t), 0, 4}, - {DCM_VR_UV, "UV", DCM_CLASS_NUMERIC, + {DCM_VR_UV, "UV", DCM_CLASS_NUMERIC_INTEGER, sizeof(uint64_t), 0, 4}, }; diff --git a/src/dicom-parse.c b/src/dicom-parse.c index cc743cc..0061c66 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -409,9 +409,11 @@ static bool parse_element_body(DcmParseState *state, switch (klass) { case DCM_CLASS_STRING_SINGLE: case DCM_CLASS_STRING_MULTI: - case DCM_CLASS_NUMERIC: + case DCM_CLASS_NUMERIC_FLOATINGPOINT: + case DCM_CLASS_NUMERIC_INTEGER: case DCM_CLASS_BINARY: - if (klass == DCM_CLASS_NUMERIC) { + if (klass == DCM_CLASS_NUMERIC_FLOATINGPOINT || + klass == DCM_CLASS_NUMERIC_INTEGER) { // all numeric classes have a size if (length % size != 0) { dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, @@ -448,7 +450,8 @@ static bool parse_element_body(DcmParseState *state, } } - if (klass == DCM_CLASS_NUMERIC) { + if (klass == DCM_CLASS_NUMERIC_FLOATINGPOINT || + klass == DCM_CLASS_NUMERIC_INTEGER) { if (state->byteswap) { byteswap(value, length, size); } diff --git a/src/dicom.c b/src/dicom.c index 6ebe4a7..1ef23ca 100644 --- a/src/dicom.c +++ b/src/dicom.c @@ -48,6 +48,19 @@ void *dcm_calloc(DcmError **error, size_t n, size_t size) } +void *dcm_realloc(DcmError **error, void *ptr, size_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 +78,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++) { diff --git a/src/pdicom.h b/src/pdicom.h index 5894f76..643b015 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -52,8 +52,9 @@ void *dcm_calloc(DcmError **error, size_t n, size_t size); - +void *dcm_realloc(DcmError **error, void *ptr, size_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); From 96caf7fa510ff6311c6ba6d5c742b95c821cfba8 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 10 Jun 2023 11:50:30 +0100 Subject: [PATCH 38/82] add index to sequence_foreach the callback needs to know which sequence item it is --- include/dicom/dicom.h | 4 +++- src/dicom-data.c | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 31829e2..627285e 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1198,7 +1198,9 @@ DcmDataSet *dcm_sequence_get(DcmError **error, */ DCM_EXTERN bool dcm_sequence_foreach(const DcmSequence *seq, - bool (*fn)(const DcmDataSet *dataset, void *client), + bool (*fn)(const DcmDataSet *dataset, + uint32_t index, + void *client), void *client); /** diff --git a/src/dicom-data.c b/src/dicom-data.c index a649a80..7908f7e 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -1667,19 +1667,19 @@ DcmDataSet *dcm_sequence_steal(DcmError **error, bool dcm_sequence_foreach(const DcmSequence *seq, - bool (*fn)(const DcmDataSet *item, void *client), + bool (*fn)(const DcmDataSet *item, + uint32_t index, + void *client), void *client) { - uint32_t i; - uint32_t length = utarray_len(seq->items); - for (i = 0; i < length; i++) { - struct SequenceItem *seq_item = utarray_eltptr(seq->items, i); + 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, client)) { + if (!fn(dataset, index, client)) { return false; } } From 3eb3b0f502d687384f1b58ec5d01f812dab01212 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 11 Jun 2023 10:17:50 +0100 Subject: [PATCH 39/82] rename floatingpoint as decimal --- include/dicom/dicom.h | 14 +++++++------- src/dicom-data.c | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 627285e..bd198a4 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -811,10 +811,10 @@ bool dcm_element_set_value_numeric_multi(DcmError **error, * :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. @@ -830,9 +830,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. diff --git a/src/dicom-data.c b/src/dicom-data.c index 7908f7e..bc6387a 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -792,10 +792,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) || @@ -818,9 +818,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) || @@ -1198,10 +1198,10 @@ char *dcm_element_value_to_string(const DcmElement *element) switch (klass) { case DCM_CLASS_NUMERIC_FLOATINGPOINT: double d; - (void) dcm_element_get_value_floatingpoint(NULL, - element, - i, - &d); + (void) dcm_element_get_value_decimal(NULL, + element, + i, + &d); result = dcm_printf_append(result, "%g", d); break; From cf38a30e5f89dd16578bad7bc30cdc17b3f0a176 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 11 Jun 2023 14:19:55 +0100 Subject: [PATCH 40/82] fix build with clang --- src/dicom-data.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index bc6387a..3480d06 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -1190,43 +1190,44 @@ char *dcm_element_value_to_string(const DcmElement *element) char *result = NULL; + double d; + int64_t i; + const char *str; + if (element->vm > 1) { result = dcm_printf_append(result, "["); } - for (uint32_t i = 0; i < element->vm; i++) { + for (uint32_t index = 0; index < element->vm; index++) { switch (klass) { case DCM_CLASS_NUMERIC_FLOATINGPOINT: - double d; (void) dcm_element_get_value_decimal(NULL, element, - i, + index, &d); result = dcm_printf_append(result, "%g", d); break; case DCM_CLASS_NUMERIC_INTEGER: - int64_t i64; (void) dcm_element_get_value_integer(NULL, element, - i, - &i64); + index, + &i); if (element->vr == DCM_VR_UV) { result = dcm_printf_append(result, "%"PRIu64, - (uint64_t)i64); + (uint64_t)i); } else { - result = dcm_printf_append(result, "%"PRId64, i64); + result = dcm_printf_append(result, "%"PRId64, i); } break; case DCM_CLASS_STRING_SINGLE: case DCM_CLASS_STRING_MULTI: - const char *str; (void) dcm_element_get_value_string(NULL, element, - i, + index, &str); result = dcm_printf_append(result, "%s", str); break; From a6c42cbcf309677df933dc3617b9e760e2af41ee Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 11 Jun 2023 14:25:22 +0100 Subject: [PATCH 41/82] rename NUMERIC_FLOATINGPOINT as NUMERIC_INTEGER --- include/dicom/dicom.h | 9 ++++++--- src/dicom-data.c | 10 +++++----- src/dicom-dict.c | 4 ++-- src/dicom-parse.c | 6 +++--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index bd198a4..96ede12 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -238,8 +238,11 @@ typedef enum _DcmVR { * DCM_CLASS_STRING_SINGLE -- a single null-terminated string, backslash * allowed * - * DCM_CLASS_NUMERIC -- one or more binary numeric values (float etc.), other - * fields give sizeof(type) + * DCM_CLASS_NUMERIC_DECIMAL -- one or more binary floating point numeric + * values, other fields give sizeof(type) + * + * DCM_CLASS_NUMERIC_INTEGER -- one or more binary integer numeric + * values, other fields give sizeof(type) * * DCM_CLASS_BINARY -- an uninterpreted array of bytes, length in the * element header @@ -250,7 +253,7 @@ typedef enum _DcmVRClass { DCM_CLASS_ERROR, DCM_CLASS_STRING_MULTI, DCM_CLASS_STRING_SINGLE, - DCM_CLASS_NUMERIC_FLOATINGPOINT, + DCM_CLASS_NUMERIC_DECIMAL, DCM_CLASS_NUMERIC_INTEGER, DCM_CLASS_BINARY, DCM_CLASS_SEQUENCE diff --git a/src/dicom-data.c b/src/dicom-data.c index 3480d06..e4df057 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -387,7 +387,7 @@ static bool dcm_element_validate(DcmError **error, DcmElement *element) return false; } - if (klass == DCM_CLASS_NUMERIC_FLOATINGPOINT || + if (klass == DCM_CLASS_NUMERIC_DECIMAL || klass == DCM_CLASS_NUMERIC_INTEGER) { if (element->length != element->vm * dcm_dict_vr_size(element->vr)) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, @@ -627,7 +627,7 @@ static bool element_check_numeric(DcmError **error, const DcmElement *element) { DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass != DCM_CLASS_NUMERIC_FLOATINGPOINT && + if (klass != DCM_CLASS_NUMERIC_DECIMAL && klass != DCM_CLASS_NUMERIC_INTEGER) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not numeric", @@ -934,7 +934,7 @@ bool dcm_element_set_value(DcmError **error, } break; - case DCM_CLASS_NUMERIC_FLOATINGPOINT: + case DCM_CLASS_NUMERIC_DECIMAL: case DCM_CLASS_NUMERIC_INTEGER: size = dcm_dict_vr_size(element->vr); if (length % size != 0) { @@ -1147,7 +1147,7 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) } break; - case DCM_CLASS_NUMERIC_FLOATINGPOINT: + case DCM_CLASS_NUMERIC_DECIMAL: case DCM_CLASS_NUMERIC_INTEGER: if (element->vm == 1) { clone->value = element->value; @@ -1200,7 +1200,7 @@ char *dcm_element_value_to_string(const DcmElement *element) for (uint32_t index = 0; index < element->vm; index++) { switch (klass) { - case DCM_CLASS_NUMERIC_FLOATINGPOINT: + case DCM_CLASS_NUMERIC_DECIMAL: (void) dcm_element_get_value_decimal(NULL, element, index, diff --git a/src/dicom-dict.c b/src/dicom-dict.c index 30981ef..d9ae4d8 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -67,9 +67,9 @@ static const struct _DcmVRTable vr_table[] = { {DCM_VR_DT, "DT", DCM_CLASS_STRING_MULTI, 0, DCM_CAPACITY_DT, 2}, - {DCM_VR_FL, "FL", DCM_CLASS_NUMERIC_FLOATINGPOINT, + {DCM_VR_FL, "FL", DCM_CLASS_NUMERIC_DECIMAL, sizeof(float), 0, 2}, - {DCM_VR_FD, "FD", DCM_CLASS_NUMERIC_FLOATINGPOINT, + {DCM_VR_FD, "FD", DCM_CLASS_NUMERIC_DECIMAL, sizeof(double), 0, 2}, {DCM_VR_IS, "IS", DCM_CLASS_STRING_SINGLE, diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 0061c66..b76ab5b 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -409,10 +409,10 @@ static bool parse_element_body(DcmParseState *state, switch (klass) { case DCM_CLASS_STRING_SINGLE: case DCM_CLASS_STRING_MULTI: - case DCM_CLASS_NUMERIC_FLOATINGPOINT: + case DCM_CLASS_NUMERIC_DECIMAL: case DCM_CLASS_NUMERIC_INTEGER: case DCM_CLASS_BINARY: - if (klass == DCM_CLASS_NUMERIC_FLOATINGPOINT || + if (klass == DCM_CLASS_NUMERIC_DECIMAL || klass == DCM_CLASS_NUMERIC_INTEGER) { // all numeric classes have a size if (length % size != 0) { @@ -450,7 +450,7 @@ static bool parse_element_body(DcmParseState *state, } } - if (klass == DCM_CLASS_NUMERIC_FLOATINGPOINT || + if (klass == DCM_CLASS_NUMERIC_DECIMAL || klass == DCM_CLASS_NUMERIC_INTEGER) { if (state->byteswap) { byteswap(value, length, size); From 9541fb6e83ed8867e5ef770559eb6f66ba2c9e8f Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 12 Jun 2023 21:28:48 -0400 Subject: [PATCH 42/82] skip unnecessary read of File Preamble We don't use its contents, so just seek over it. --- src/dicom-file.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index 280918c..1a436b5 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -430,15 +430,11 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, .stop = NULL, }; - int64_t position = 0; - - // File Preamble - char preamble[129]; - if (!dcm_require(error, - filehandle, preamble, sizeof(preamble) - 1, &position)) { + // skip File Preamble + int64_t position = 128; + if (!dcm_seekset(error, filehandle, position)) { return NULL; } - preamble[128] = '\0'; // DICOM Prefix char prefix[5]; From 6dfb2b870143e37b4f5620b59e12b3dbefb40c8f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 18 Jun 2023 14:13:16 +0100 Subject: [PATCH 43/82] add dcm_filehandle_get_transfer_syntax_uid() since openslide needs to test this pretty early on --- include/dicom/dicom.h | 10 ++++++++++ src/dicom-data.c | 2 +- src/dicom-file.c | 18 ++++++++++++------ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 96ede12..d152159 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1633,6 +1633,16 @@ DCM_EXTERN DcmDataSet *dcm_filehandle_read_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. * diff --git a/src/dicom-data.c b/src/dicom-data.c index e4df057..7c83263 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -1244,7 +1244,7 @@ char *dcm_element_value_to_string(const DcmElement *element) } if (element->vm > 1) { - if (i == element->vm - 1) { + if (index == element->vm - 1) { result = dcm_printf_append(result, "]"); } else { result = dcm_printf_append(result, ", "); diff --git a/src/dicom-file.c b/src/dicom-file.c index 280918c..c500640 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -523,6 +523,12 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, } +const char *dcm_filehandle_get_transfer_syntax_uid(const DcmFilehandle *filehandle) +{ + return filehandle->transfer_syntax_uid; +} + + static bool parse_meta_stop(void *client, uint32_t tag, DcmVR vr, @@ -636,10 +642,9 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return NULL; } - if (filehandle->transfer_syntax_uid) { - if (strcmp(filehandle->transfer_syntax_uid, "1.2.840.10008.1.2") == 0) { - filehandle->implicit = true; - } + const char *syntax = dcm_filehandle_get_transfer_syntax_uid(filehandle); + if (syntax && strcmp(syntax, "1.2.840.10008.1.2") == 0) { + filehandle->implicit = true; } dcm_filehandle_clear(filehandle); @@ -688,7 +693,7 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, if (!set_pixel_description(error, &filehandle->desc, meta)) { return false; } - filehandle->desc.transfer_syntax_uid = filehandle->transfer_syntax_uid; + filehandle->desc.transfer_syntax_uid = syntax; // we support sparse and full tile layout, defaulting to full if no type // is specified @@ -893,7 +898,8 @@ bool dcm_filehandle_read_pixeldata(DcmError **error, dcm_log_debug("Reading PixelData."); - if (dcm_is_encapsulated_transfer_syntax(filehandle->transfer_syntax_uid)) { + 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(error, From 7aafa70f27981b2283660ead8262406d4825b96a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 20 Jun 2023 12:15:39 +0100 Subject: [PATCH 44/82] a set of cleanups - add a log function API - something to set the log level (not a global) - reorganise dicom.h - dcm_init will enable debug logging if DCM_DEBUG is set --- TODO | 4 + include/dicom/dicom.h | 294 +++++++++++++++++------------------------- src/dicom-dict.c | 4 + src/dicom-parse.c | 21 --- src/dicom.c | 14 -- src/pdicom.h | 5 + tests/check_dicom.c | 4 +- tools/dcm-dump.c | 2 +- tools/dcm-getframe.c | 7 +- 9 files changed, 141 insertions(+), 214 deletions(-) diff --git a/TODO b/TODO index e6ecd20..fe9b054 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,9 @@ # TODO +- popt as a subproject? + +- read metadata needs an "all" flag, or perhaps a set of stop tags? + - dcm-dump could call the parser with a set of print functions add a "-a" flag to print everything (inc. pixel data and diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 800ecb7..63fec2f 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 @@ -115,71 +110,10 @@ typedef SSIZE_T ssize_t; */ #define DCM_CAPACITY_UT 4294967294 -/** - * Error return object. - */ -typedef struct _DcmError DcmError; - -/** - * 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; - -/** - * A set of read functions. - */ -typedef struct _DcmIOMethods DcmIOMethods; - -/** - * An object we can read from. - */ -typedef struct _DcmIO DcmIO; - -/** - * Error codes - */ -typedef enum _DcmErrorCode DcmErrorCode; - -/** - * Value Representations - */ -typedef enum _DcmVR DcmVR; - -/** - * Classes of Value Representations - */ -typedef enum _DcmVRClass DcmVRClass; - -/** - * Log function. See dcm_log_set_logf(). - */ -typedef void (*DcmLogf)(const char *level, const char *format, va_list args); - -/** - * Log level - */ -typedef enum _DcmLogLevel DcmLogLevel; - /** * Start up libdicom. * @@ -196,10 +130,15 @@ DCM_EXTERN DCM_CONSTRUCTOR void dcm_init(void); +/** + * 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 */ @@ -208,101 +147,7 @@ enum _DcmErrorCode { DCM_ERROR_CODE_PARSE = 3, /** IO error */ DCM_ERROR_CODE_IO = 4, -}; - -/** - * 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. - */ -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 -}; - -/** - * The general class of the value associated with a Value Representation. - * - * DCM_CLASS_STRING_MULTI -- one or more null-terminated strings, cannot - * contain backslash - * - * DCM_CLASS_STRING_SINGLE -- a single null-terminated string, backslash - * allowed - * - * DCM_CLASS_NUMERIC_DECIMAL -- one or more binary floating point numeric - * values, other fields give sizeof(type) - * - * DCM_CLASS_NUMERIC_INTEGER -- one or more binary integer numeric - * values, other fields give sizeof(type) - * - * DCM_CLASS_BINARY -- an uninterpreted array of bytes, length in the - * element header - * - * DCM_CLASS_SEQUENCE -- Value Representation is a seqeunce - */ -enum _DcmVRClass { - DCM_CLASS_ERROR, - DCM_CLASS_STRING_MULTI, - DCM_CLASS_STRING_SINGLE, - DCM_CLASS_NUMERIC_DECIMAL, - DCM_CLASS_NUMERIC_INTEGER, - DCM_CLASS_BINARY, - DCM_CLASS_SEQUENCE -}; - -/** - * 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); +} DcmErrorCode; /** * Convert an error code to a human-readable string. @@ -397,7 +242,7 @@ void dcm_error_log(DcmError *error); /** * Enumeration of log levels */ -enum _DcmLogLevel { +typedef enum _DcmLogLevel { /** Critical */ DCM_LOG_CRITICAL = 50, /** Error */ @@ -410,7 +255,7 @@ enum _DcmLogLevel { DCM_LOG_DEBUG = 10, /** Not set (no logging) */ DCM_LOG_NOTSET = 0, -}; +} DcmLogLevel; /** * Set the log level. @@ -421,6 +266,11 @@ enum _DcmLogLevel { DCM_EXTERN DcmLogLevel dcm_log_set_level(DcmLogLevel 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. * @@ -487,6 +337,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_CLASS_STRING_MULTI -- one or more null-terminated strings, cannot + * contain backslash + * + * DCM_CLASS_STRING_SINGLE -- a single null-terminated string, backslash + * allowed + * + * DCM_CLASS_NUMERIC_DECIMAL -- one or more binary floating point numeric + * values, other fields give sizeof(type) + * + * DCM_CLASS_NUMERIC_INTEGER -- one or more binary integer numeric + * values, other fields give sizeof(type) + * + * DCM_CLASS_BINARY -- an uninterpreted array of bytes, length in the + * element header + * + * DCM_CLASS_SEQUENCE -- Value Representation is a seqeunce + */ +typedef enum _DcmVRClass { + DCM_CLASS_ERROR, + DCM_CLASS_STRING_MULTI, + DCM_CLASS_STRING_SINGLE, + DCM_CLASS_NUMERIC_DECIMAL, + DCM_CLASS_NUMERIC_INTEGER, + DCM_CLASS_BINARY, + DCM_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. * @@ -608,8 +552,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. @@ -1003,6 +948,7 @@ void dcm_element_destroy(DcmElement *element); /** * Data Set */ +typedef struct _DcmDataSet DcmDataSet; /** * Create an empty Data Set. @@ -1182,8 +1128,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. @@ -1297,11 +1243,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. @@ -1486,16 +1433,17 @@ void dcm_frame_destroy(DcmFrame *frame); /** * Part 10 File */ +typedef struct _DcmFilehandle DcmFilehandle; -struct _DcmIOMethods DcmIOMethods; +typedef struct _DcmIOMethods DcmIOMethods; /** - * Something we can read from. + * An object we can read from. */ -struct _DcmIO { +typedef struct _DcmIO { const DcmIOMethods *methods; // more private fields follow -}; +} DcmIO; /** * A set of IO methods, see dcm_io_create(). diff --git a/src/dicom-dict.c b/src/dicom-dict.c index d9ae4d8..3471955 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -5187,6 +5187,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); + } } diff --git a/src/dicom-parse.c b/src/dicom-parse.c index b76ab5b..db9ff6c 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -82,27 +82,6 @@ static bool dcm_require(DcmParseState *state, } -/* will we need these? -static bool dcm_seekset(DcmParseState *state, int64_t offset) -{ - int64_t new_offset = dcm_io_seek(state->error, state->io, offset, SEEK_SET); - return new_offset >= 0; -} - -static bool dcm_offset(DcmParseState *state, int64_t *offset) -{ - int64_t new_offset = dcm_io_seek(state->error, state->io, 0, SEEK_CUR); - if (new_offset < 0) { - return false; - } - - *offset = new_offset; - - 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); diff --git a/src/dicom.c b/src/dicom.c index c8e3be2..3126596 100644 --- a/src/dicom.c +++ b/src/dicom.c @@ -394,17 +394,3 @@ void dcm_log_debug(const char *format, ...) va_end(args); } } - - -DcmError *dcm_error_newf(const char *domain, int code, const char *format, - va_list args) { - DcmError *error; - - error = DCM_NEW (DcmError); - if (error == NULL) { - } -} - - -DcmError *dcm_error_new(const char *domain, int code, const char *format, ...) { -} diff --git a/src/pdicom.h b/src/pdicom.h index 643b015..3d699ca 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 diff --git a/tests/check_dicom.c b/tests/check_dicom.c index 2484c8e..edea744 100644 --- a/tests/check_dicom.c +++ b/tests/check_dicom.c @@ -714,8 +714,6 @@ START_TEST(test_file_sm_image_frame) ck_assert_int_ne(dcm_filehandle_read_pixeldata(NULL, filehandle), 0); - dcm_log_level = DCM_LOG_INFO; - DcmFrame *frame = dcm_filehandle_read_frame(NULL, filehandle, frame_number); @@ -854,6 +852,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 002b247..7d1443b 100644 --- a/tools/dcm-dump.c +++ b/tools/dcm-dump.c @@ -20,7 +20,7 @@ int main(int argc, char *argv[]) DcmDataSet *meta = NULL; DcmFilehandle *handle = NULL; - dcm_log_set_level(DCM_LOG_ERROR); + dcm_init(); for (i = 1; i < argc && argv[i][0] == '-'; i++) { switch (argv[i][1]) { diff --git a/tools/dcm-getframe.c b/tools/dcm-getframe.c index 688480f..028a5f0 100644 --- a/tools/dcm-getframe.c +++ b/tools/dcm-getframe.c @@ -16,11 +16,12 @@ static const char usage[] = "usage: " int main(int argc, char *argv[]) { + int i; + DcmError *error = NULL; char *output_filehandle = NULL; - int i; - dcm_log_level = DCM_LOG_ERROR; + dcm_init(); for (i = 1; i < argc && argv[i][0] == '-'; i++) { switch (argv[i][1]) { @@ -31,7 +32,7 @@ int main(int argc, char *argv[]) printf("%s\n", dcm_get_version()); return EXIT_SUCCESS; case 'v': - dcm_log_level = DCM_LOG_INFO; + dcm_log_set_level(DCM_LOG_INFO); break; case 'o': output_filehandle = argv[i + 1]; From 1cc7c00d41fcd0549bcc411b226b7c09cae4e588 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 20 Jun 2023 17:31:35 +0100 Subject: [PATCH 45/82] better CLI option parsing using a versoion of getopt --- include/dicom/dicom.h | 19 +++++++++ meson.build | 1 + src/dicom.c | 11 +++++ src/getopt.c | 75 +++++++++++++++++++++++++++++++++ src/pdicom.h | 1 - tools/dcm-dump.c | 96 +++++++++++++++++++++---------------------- tools/dcm-getframe.c | 65 +++++++++++++++++------------ 7 files changed, 192 insertions(+), 76 deletions(-) create mode 100644 src/getopt.c diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 63fec2f..d2e3c98 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -130,6 +130,16 @@ 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. */ @@ -239,6 +249,15 @@ 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); + + /** * Enumeration of log levels */ diff --git a/meson.build b/meson.build index 9fab032..7d1773c 100644 --- a/meson.build +++ b/meson.build @@ -148,6 +148,7 @@ 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', diff --git a/src/dicom.c b/src/dicom.c index 3126596..0ab1678 100644 --- a/src/dicom.c +++ b/src/dicom.c @@ -279,6 +279,17 @@ void dcm_error_log(DcmError *error) } +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) 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 3d699ca..0462b85 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -55,7 +55,6 @@ typedef SSIZE_T ssize_t; #define TAG_ITEM_DELIM 0xFFFEE00D #define TAG_SQ_DELIM 0xFFFEE0DD - void *dcm_calloc(DcmError **error, size_t n, size_t size); void *dcm_realloc(DcmError **error, void *ptr, size_t size); char *dcm_strdup(DcmError **error, const char *str); diff --git a/tools/dcm-dump.c b/tools/dcm-dump.c index 7d1443b..5f8ac7b 100644 --- a/tools/dcm-dump.c +++ b/tools/dcm-dump.c @@ -7,79 +7,79 @@ #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 i; - - const char *file_path = NULL; - DcmError *error = NULL; - DcmDataSet *metadata = NULL; - DcmDataSet *meta = NULL; - DcmFilehandle *handle = NULL; + int c; 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': + + 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); - handle = dcm_filehandle_create_from_file(&error, file_path); - if (handle == 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, handle); - if (meta == NULL) { - dcm_error_log(error); - dcm_error_clear(&error); - dcm_filehandle_destroy(handle); - return EXIT_FAILURE; - } + for (i = dcm_optind; i < argc; i++) { + DcmError *error = NULL; + DcmDataSet *metadata = NULL; + DcmDataSet *meta = NULL; + DcmFilehandle *handle = NULL; + + dcm_log_info("Read file '%s'", argv[i]); + handle = dcm_filehandle_create_from_file(&error, argv[i]); + if (handle == NULL) { + dcm_error_print(error); + dcm_error_clear(&error); + return EXIT_FAILURE; + } + dcm_log_info("Read File Meta Information"); + meta = dcm_filehandle_read_file_meta(&error, handle); + if (meta == NULL) { + dcm_error_print(error); + dcm_error_clear(&error); + dcm_filehandle_destroy(handle); + return EXIT_FAILURE; + } - printf("===File Meta Information===\n"); - dcm_dataset_print(meta, 0); + printf("===File Meta Information===\n"); + dcm_dataset_print(meta, 0); + + dcm_log_info("Read metadata"); + metadata = dcm_filehandle_read_metadata(&error, handle); + if (metadata == NULL) { + dcm_error_print(error); + dcm_error_clear(&error); + dcm_dataset_destroy(meta); + dcm_filehandle_destroy(handle); + return EXIT_FAILURE; + } + + printf("===Dataset===\n"); + dcm_dataset_print(metadata, 0); - dcm_log_info("Read metadata"); - metadata = dcm_filehandle_read_metadata(&error, handle); - if (metadata == NULL) { - dcm_error_log(error); - dcm_error_clear(&error); dcm_dataset_destroy(meta); + dcm_dataset_destroy(metadata); dcm_filehandle_destroy(handle); - return EXIT_FAILURE; } - printf("===Dataset===\n"); - dcm_dataset_print(metadata, 0); - - dcm_dataset_destroy(meta); - dcm_dataset_destroy(metadata); - dcm_filehandle_destroy(handle); - return EXIT_SUCCESS; } diff --git a/tools/dcm-getframe.c b/tools/dcm-getframe.c index 028a5f0..5646c95 100644 --- a/tools/dcm-getframe.c +++ b/tools/dcm-getframe.c @@ -11,50 +11,56 @@ 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 i; + char *output_file = NULL; - DcmError *error = NULL; - char *output_filehandle = NULL; + int c; 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': + + 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]); - dcm_log_info("Read filehandle '%s'", file_path); + dcm_log_info("Read filehandle '%s'", input_file); DcmFilehandle *filehandle = dcm_filehandle_create_from_file(&error, - file_path); + input_file); if (filehandle == NULL) { - dcm_error_log(error); + dcm_error_print(error); dcm_error_clear(&error); return EXIT_FAILURE; } @@ -62,14 +68,14 @@ int main(int argc, char *argv[]) dcm_log_info("Read metadata"); DcmDataSet *metadata = dcm_filehandle_read_metadata(&error, filehandle); if (metadata == NULL) { - dcm_error_log(error); + dcm_error_print(error); dcm_error_clear(&error); dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; } if (!dcm_filehandle_read_pixeldata(&error, filehandle)) { - dcm_error_log(error); + dcm_error_print(error); dcm_error_clear(&error); dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; @@ -80,20 +86,19 @@ int main(int argc, char *argv[]) const char *value; if (!(element = dcm_dataset_get(&error, metadata, tag)) || !dcm_element_get_value_string(&error, element, 0, &value)) { - dcm_error_log(error); + dcm_error_print(error); dcm_error_clear(&error); dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; } - int num_frames = atoi(value); - int frame_number = atoi(argv[i + 1]); + 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_print(error); dcm_error_clear(&error); dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); @@ -105,7 +110,7 @@ int main(int argc, char *argv[]) filehandle, frame_number); if (frame == NULL) { - dcm_error_log(error); + dcm_error_print(error); dcm_error_clear(&error); dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); @@ -134,12 +139,18 @@ int main(int argc, char *argv[]) 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_dataset_destroy(metadata); + dcm_filehandle_destroy(filehandle); + return EXIT_FAILURE; } } else @@ -147,7 +158,7 @@ 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; } From ec552e81cfa20db513a2d8613e87d3fc4b414c4f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 21 Jun 2023 16:06:09 +0100 Subject: [PATCH 46/82] start a new print system --- TODO | 35 ++++++++- include/dicom/dicom.h | 13 ++++ src/dicom-file.c | 168 ++++++++++++++++++++++++++++++++++++------ src/dicom-io.c | 2 +- src/dicom-parse.c | 6 +- tools/dcm-dump.c | 40 +++------- 6 files changed, 206 insertions(+), 58 deletions(-) diff --git a/TODO b/TODO index fe9b054..f15410a 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,39 @@ # TODO -- popt as a subproject? +- pixeldata has length -1 ... we must parse this as a sequence, not as OB/OW + + maybe an implicit sequence? + + pyr level from dcmdump, DCM_3.dcm + + tag 7fe0,0010, vr OB, length -1 (pixel data) + tag fffe,e000, length 0 (bot) + tag fffe,e000, length 6460 (frame 1) + tag fffe,e000, length 6398 (frame 2) + ... + tag fffe,e0dd, length 0 (sequence delimitation item) + + overview DCM_5.dcm + + tag 7fe0,010, vr OB, length 1655040 (pixeldata) + +- rules for pixeldata + + - encapsulated trasfer syntax + - little endian + - explicit VR + - pixeldata length == -1 means undefined value length + - undefined length means encapsulated pixeldata + - only applies to top-level pixeldata elements, so an + encapsulated transfer syntax might have native sub + images eg, icon sequence + length == 234234 for example means fixed length pixeldata + - defined length means native data + - native is always little endian + + + + - read metadata needs an "all" flag, or perhaps a set of stop tags? diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index d2e3c98..470bc0c 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1717,4 +1717,17 @@ DCM_EXTERN DcmFrame *dcm_filehandle_read_frame(DcmError **error, DcmFilehandle *filehandle, uint32_t frame_number); + +/** + * 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 +bool dcm_filehandle_print(DcmError **error, + DcmFilehandle *filehandle); + #endif diff --git a/src/dicom-file.c b/src/dicom-file.c index 0fa840f..785063f 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -418,29 +418,20 @@ static bool parse_meta_element_create(DcmError **error, } -DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, - DcmFilehandle *filehandle) +static bool parse_preamble(DcmError **error, + DcmFilehandle *filehandle, + int64_t *position) { - 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 = 128; - if (!dcm_seekset(error, filehandle, position)) { - return NULL; + *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 NULL; + filehandle, prefix, sizeof(prefix) - 1, position)) { + return false; } prefix[4] = '\0'; @@ -448,6 +439,28 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading of File Meta Information failed", "Prefix 'DICM' not found."); + return false; + } + + return true; +} + + +DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, + DcmFilehandle *filehandle) +{ + 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; } @@ -511,6 +524,10 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, return NULL; } + if (strcmp(filehandle->transfer_syntax_uid, "1.2.840.10008.1.2") == 0) { + filehandle->implicit = true; + } + // steal file_meta to stop it being destroyed (void) dcm_sequence_steal(NULL, sequence, 0); dcm_filehandle_clear(filehandle); @@ -638,11 +655,6 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return NULL; } - const char *syntax = dcm_filehandle_get_transfer_syntax_uid(filehandle); - if (syntax && strcmp(syntax, "1.2.840.10008.1.2") == 0) { - filehandle->implicit = true; - } - dcm_filehandle_clear(filehandle); DcmSequence *sequence = dcm_sequence_create(error); if (sequence == NULL) { @@ -689,7 +701,9 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, if (!set_pixel_description(error, &filehandle->desc, meta)) { return false; } - filehandle->desc.transfer_syntax_uid = syntax; + + filehandle->desc.transfer_syntax_uid = + dcm_filehandle_get_transfer_syntax_uid(filehandle); // we support sparse and full tile layout, defaulting to full if no type // is specified @@ -1030,3 +1044,111 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, // read_frame() numbers from 1 return dcm_filehandle_read_frame(error, filehandle, index + 1); } + + +static bool print_dataset_begin(DcmError **error, + void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + printf("dataset_begin\n"); + + return true; +} + + +static bool print_dataset_end(DcmError **error, + void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + printf("dataset_end\n"); + + return true; +} + + +static bool print_sequence_begin(DcmError **error, + void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + printf("sequence_begin\n"); + + return true; +} + + +static bool print_sequence_end(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + printf("sequence_end\n"); + + return true; +} + + +static bool print_element_create(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + printf("sequence_end\n"); + + return true; +} + +bool dcm_filehandle_print(DcmError **error, + DcmFilehandle *filehandle) +{ + static DcmParse parse = { + .dataset_begin = print_dataset_begin, + .dataset_end = print_dataset_end, + .sequence_begin = print_sequence_begin, + .sequence_end = print_sequence_end, + .element_create = print_element_create, + .stop = NULL, + }; + + // skip File Preamble + int64_t position = 0; + if (!parse_preamble(error, filehandle, &position)) { + return false; + } + + // print the first group + printf("===File Meta Information===\n"); + dcm_log_info("Read File Meta Information"); + if (!dcm_parse_group(error, + filehandle->io, + false, + false, + &parse, + filehandle)) { + return false; + } + + // 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, + is_big_endian(), + &parse, + filehandle)) { + return false; + } + + return true; +} diff --git a/src/dicom-io.c b/src/dicom-io.c index 4f64a00..0ac70e0 100644 --- a/src/dicom-io.c +++ b/src/dicom-io.c @@ -172,7 +172,7 @@ static int64_t dcm_io_read_file(DcmError **error, DcmIO *io, 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; } } diff --git a/src/dicom-parse.c b/src/dicom-parse.c index db9ff6c..a9d3c80 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -404,8 +404,8 @@ static bool parse_element_body(DcmParseState *state, } // read to a static char buffer, if possible - if (length + 1 >= INPUT_BUFFER_SIZE) { - value = value_free = DCM_MALLOC(state->error, length + 1); + if ((int64_t) length + 1 >= INPUT_BUFFER_SIZE) { + value = value_free = DCM_MALLOC(state->error, (size_t) length + 1); if (value == NULL) { return false; } @@ -413,6 +413,8 @@ static bool parse_element_body(DcmParseState *state, value = input_buffer; } + + if (!dcm_require(state, value, length, position)) { if (value_free != NULL) { free(value_free); diff --git a/tools/dcm-dump.c b/tools/dcm-dump.c index 5f8ac7b..2678deb 100644 --- a/tools/dcm-dump.c +++ b/tools/dcm-dump.c @@ -12,8 +12,7 @@ static const char usage[] = "usage: dcm-dump [-v] [-V] [-h] FILE_PATH ..."; int main(int argc, char *argv[]) { - int i; - int c; + int i, c; dcm_init(); @@ -40,45 +39,24 @@ int main(int argc, char *argv[]) for (i = dcm_optind; i < argc; i++) { DcmError *error = NULL; - DcmDataSet *metadata = NULL; - DcmDataSet *meta = NULL; - DcmFilehandle *handle = NULL; + DcmFilehandle *filehandle = NULL; dcm_log_info("Read file '%s'", argv[i]); - handle = dcm_filehandle_create_from_file(&error, argv[i]); - if (handle == NULL) { + filehandle = dcm_filehandle_create_from_file(&error, argv[i]); + if (filehandle == NULL) { dcm_error_print(error); dcm_error_clear(&error); return EXIT_FAILURE; - } - dcm_log_info("Read File Meta Information"); - meta = dcm_filehandle_read_file_meta(&error, handle); - if (meta == NULL) { - dcm_error_print(error); - dcm_error_clear(&error); - dcm_filehandle_destroy(handle); - return EXIT_FAILURE; - } - - printf("===File Meta Information===\n"); - dcm_dataset_print(meta, 0); - - dcm_log_info("Read metadata"); - metadata = dcm_filehandle_read_metadata(&error, handle); - if (metadata == NULL) { + } + + if (!dcm_filehandle_print(&error, filehandle)) { dcm_error_print(error); dcm_error_clear(&error); - dcm_dataset_destroy(meta); - dcm_filehandle_destroy(handle); + dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; } - printf("===Dataset===\n"); - dcm_dataset_print(metadata, 0); - - dcm_dataset_destroy(meta); - dcm_dataset_destroy(metadata); - dcm_filehandle_destroy(handle); + dcm_filehandle_destroy(filehandle); } return EXIT_SUCCESS; From e7bd087bc4a3878be2f1e6b4aa998046a2e8b252 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 22 Jun 2023 16:53:45 +0100 Subject: [PATCH 47/82] add filehandle print --- TODO | 50 ------- src/dicom-dict.c | 108 +++++---------- src/dicom-file.c | 340 ++++++++++++++++++++++++++++++++++------------ src/dicom-parse.c | 267 ++++++++++++++++++++++++++++-------- src/pdicom.h | 60 ++++---- 5 files changed, 535 insertions(+), 290 deletions(-) diff --git a/TODO b/TODO index f15410a..bb26121 100644 --- a/TODO +++ b/TODO @@ -1,57 +1,7 @@ # TODO -- pixeldata has length -1 ... we must parse this as a sequence, not as OB/OW - - maybe an implicit sequence? - - pyr level from dcmdump, DCM_3.dcm - - tag 7fe0,0010, vr OB, length -1 (pixel data) - tag fffe,e000, length 0 (bot) - tag fffe,e000, length 6460 (frame 1) - tag fffe,e000, length 6398 (frame 2) - ... - tag fffe,e0dd, length 0 (sequence delimitation item) - - overview DCM_5.dcm - - tag 7fe0,010, vr OB, length 1655040 (pixeldata) - -- rules for pixeldata - - - encapsulated trasfer syntax - - little endian - - explicit VR - - pixeldata length == -1 means undefined value length - - undefined length means encapsulated pixeldata - - only applies to top-level pixeldata elements, so an - encapsulated transfer syntax might have native sub - images eg, icon sequence - length == 234234 for example means fixed length pixeldata - - defined length means native data - - native is always little endian - - - - - - read metadata needs an "all" flag, or perhaps a set of stop tags? -- dcm-dump could call the parser with a set of print functions - - add a "-a" flag to print everything (inc. pixel data and - perframefunctionalgroupsequence etc.) - - expose parser in public API? it'd be tricky to bind to python - - -# asserts - -- should the foreach funcs take two params (one for error)? are they used? - - nope, unused within libdicom - - # Tips - debug failing test with eg. diff --git a/src/dicom-dict.c b/src/dicom-dict.c index 3471955..7f73484 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -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_SINGLE, - 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_SINGLE, - 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_CLASS_STRING_MULTI, 0, DCM_CAPACITY_AE, 2}, + {DCM_VR_AS, "AS", DCM_CLASS_STRING_SINGLE, 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_SINGLE, 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_FL, "FL", DCM_CLASS_NUMERIC_DECIMAL, - sizeof(float), 0, 2}, - {DCM_VR_FD, "FD", DCM_CLASS_NUMERIC_DECIMAL, - sizeof(double), 0, 2}, + {DCM_VR_FL, "FL", DCM_CLASS_NUMERIC_DECIMAL, 4, 0, 2}, + {DCM_VR_FD, "FD", DCM_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_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_IS, 2}, + {DCM_VR_LO, "LO", DCM_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_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_LT, 2}, - {DCM_VR_OB, "OB", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_OB, "OB", DCM_CLASS_BINARY, 0, 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_CLASS_BINARY, 8, 0, 4}, + {DCM_VR_OF, "OF", DCM_CLASS_BINARY, 4, 0, 4}, - {DCM_VR_OW, "OW", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_OW, "OW", DCM_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_CLASS_STRING_MULTI, 0, DCM_CAPACITY_PN, 2}, + {DCM_VR_SH, "SH", DCM_CLASS_STRING_MULTI, 0, DCM_CAPACITY_SH, 2}, - {DCM_VR_SL, "SL", DCM_CLASS_NUMERIC_INTEGER, - sizeof(int32_t), 0, 2}, + {DCM_VR_SL, "SL", DCM_CLASS_NUMERIC_INTEGER, 4, 0, 2}, - {DCM_VR_SQ, "SQ", DCM_CLASS_SEQUENCE, - 0, 0, 4}, + {DCM_VR_SQ, "SQ", DCM_CLASS_SEQUENCE, 0, 0, 4}, - {DCM_VR_SS, "SS", DCM_CLASS_NUMERIC_INTEGER, - sizeof(int16_t), 0, 2}, + {DCM_VR_SS, "SS", DCM_CLASS_NUMERIC_INTEGER, 2, 0, 2}, - {DCM_VR_ST, "ST", DCM_CLASS_STRING_SINGLE, - 0, DCM_CAPACITY_ST, 2}, + {DCM_VR_ST, "ST", DCM_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_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_CLASS_STRING_MULTI, 0, DCM_CAPACITY_UI, 2}, - {DCM_VR_UL, "UL", DCM_CLASS_NUMERIC_INTEGER, - sizeof(uint32_t), 0, 2}, + {DCM_VR_UL, "UL", DCM_CLASS_NUMERIC_INTEGER, 4, 0, 2}, - {DCM_VR_UN, "UN", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_UN, "UN", DCM_CLASS_BINARY, 0, 0, 4}, - {DCM_VR_US, "US", DCM_CLASS_NUMERIC_INTEGER, - sizeof(uint16_t), 0, 2}, + {DCM_VR_US, "US", DCM_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_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_UT, 4}, + {DCM_VR_UR, "UR", DCM_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_UR, 4}, - {DCM_VR_UC, "UC", DCM_CLASS_BINARY, - 0, 0, 4}, + {DCM_VR_UC, "UC", DCM_CLASS_BINARY, 0, 0, 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_CLASS_BINARY, 0, 0, 4}, + {DCM_VR_OV, "OV", DCM_CLASS_BINARY, 0, 0, 4}, - {DCM_VR_SV, "SV", DCM_CLASS_NUMERIC_INTEGER, - sizeof(int64_t), 0, 4}, - {DCM_VR_UV, "UV", DCM_CLASS_NUMERIC_INTEGER, - sizeof(uint64_t), 0, 4}, + {DCM_VR_SV, "SV", DCM_CLASS_NUMERIC_INTEGER, 8, 0, 4}, + {DCM_VR_UV, "UV", DCM_CLASS_NUMERIC_INTEGER, 8, 0, 4}, }; static const int n_vrs = sizeof(vr_table) / sizeof(struct _DcmVRTable); @@ -5347,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 785063f..3071a10 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -32,7 +32,6 @@ typedef enum _DcmLayout { struct _DcmFilehandle { DcmIO *io; char *transfer_syntax_uid; - bool byteswap; bool implicit; // start of image metadata @@ -58,26 +57,18 @@ struct _DcmFilehandle { // used to count frames as we scan perframefunctionalgroup uint32_t frame_number; + // indent for file print + int indent; + + // dataset index for file print + int index; + // push and pop these while we parse UT_array *dataset_stack; UT_array *sequence_stack; }; -/* 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; -} - DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) { DcmFilehandle *filehandle = DCM_NEW(error, DcmFilehandle); @@ -89,19 +80,18 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) filehandle->offset = 0; filehandle->transfer_syntax_uid = NULL; filehandle->pixel_data_offset = 0; - 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->dataset_stack, &ut_ptr_icd); - utarray_new(filehandle->sequence_stack, &ut_ptr_icd); + utarray_new(filehandle->dataset_stack, &ut_ptr_icd); + utarray_new(filehandle->sequence_stack, &ut_ptr_icd); return filehandle; } -DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, +DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, const char *filepath) { DcmIO *io = dcm_io_create_from_file(error, filepath); @@ -114,7 +104,7 @@ DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, - const char *buffer, + const char *buffer, int64_t length) { DcmIO *io = dcm_io_create_from_memory(error, buffer, length); @@ -131,7 +121,7 @@ static void dcm_filehandle_clear(DcmFilehandle *filehandle) unsigned int i; for (i = 0; i < utarray_len(filehandle->dataset_stack); i++) { - DcmDataSet *dataset = *((DcmDataSet **) + DcmDataSet *dataset = *((DcmDataSet **) utarray_eltptr(filehandle->dataset_stack, i)); dcm_dataset_destroy(dataset); @@ -140,7 +130,7 @@ static void dcm_filehandle_clear(DcmFilehandle *filehandle) utarray_clear(filehandle->dataset_stack); for (i = 0; i < utarray_len(filehandle->sequence_stack); i++) { - DcmSequence *sequence = *((DcmSequence **) + DcmSequence *sequence = *((DcmSequence **) utarray_eltptr(filehandle->sequence_stack, i)); dcm_sequence_destroy(sequence); @@ -239,7 +229,7 @@ static bool dcm_offset(DcmError **error, static bool get_tag_int(DcmError **error, const DcmDataSet *dataset, const char *keyword, - int64_t *result) + int64_t *result) { uint32_t tag = dcm_dict_tag_from_keyword(keyword); DcmElement *element = dcm_dataset_get(error, dataset, tag); @@ -251,7 +241,7 @@ static bool get_tag_int(DcmError **error, static bool get_tag_str(DcmError **error, const DcmDataSet *dataset, const char *keyword, - const char **result) + const char **result) { uint32_t tag = dcm_dict_tag_from_keyword(keyword); DcmElement *element = dcm_dataset_get(error, dataset, tag); @@ -305,8 +295,8 @@ static bool get_tiles_across(DcmError **error, } -static bool parse_meta_dataset_begin(DcmError **error, - void *client) +static bool parse_meta_dataset_begin(DcmError **error, + void *client) { DcmFilehandle *filehandle = (DcmFilehandle *) client; @@ -321,12 +311,12 @@ static bool parse_meta_dataset_begin(DcmError **error, } -static bool parse_meta_dataset_end(DcmError **error, - void *client) +static bool parse_meta_dataset_end(DcmError **error, + void *client) { DcmFilehandle *filehandle = (DcmFilehandle *) client; - DcmDataSet *dataset = *((DcmDataSet **) + DcmDataSet *dataset = *((DcmDataSet **) utarray_back(filehandle->dataset_stack)); DcmSequence *sequence = *((DcmSequence **) utarray_back(filehandle->sequence_stack)); @@ -341,9 +331,16 @@ static bool parse_meta_dataset_end(DcmError **error, } -static bool parse_meta_sequence_begin(DcmError **error, - void *client) +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); @@ -357,7 +354,7 @@ static bool parse_meta_sequence_begin(DcmError **error, } -static bool parse_meta_sequence_end(DcmError **error, +static bool parse_meta_sequence_end(DcmError **error, void *client, uint32_t tag, DcmVR vr, @@ -372,7 +369,7 @@ static bool parse_meta_sequence_end(DcmError **error, return false; } - DcmSequence *sequence = *((DcmSequence **) + DcmSequence *sequence = *((DcmSequence **) utarray_back(filehandle->sequence_stack)); if (!dcm_element_set_value_sequence(error, element, sequence)) { dcm_element_destroy(element); @@ -392,8 +389,8 @@ static bool parse_meta_sequence_end(DcmError **error, } -static bool parse_meta_element_create(DcmError **error, - void *client, +static bool parse_meta_element_create(DcmError **error, + void *client, uint32_t tag, DcmVR vr, char *value, @@ -475,7 +472,6 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, if (!dcm_parse_group(error, filehandle->io, false, - false, &parse, filehandle)) { return false; @@ -513,8 +509,8 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, const char *transfer_syntax_uid; if (!dcm_element_get_value_string(error, - element, - 0, + element, + 0, &transfer_syntax_uid)) { return NULL; } @@ -542,7 +538,7 @@ const char *dcm_filehandle_get_transfer_syntax_uid(const DcmFilehandle *filehand } -static bool parse_meta_stop(void *client, +static bool parse_meta_stop(void *client, uint32_t tag, DcmVR vr, uint32_t length) @@ -666,7 +662,6 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, if (!dcm_parse_dataset(error, filehandle->io, filehandle->implicit, - filehandle->byteswap, &parse, filehandle)) { return false; @@ -702,7 +697,7 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return false; } - filehandle->desc.transfer_syntax_uid = + filehandle->desc.transfer_syntax_uid = dcm_filehandle_get_transfer_syntax_uid(filehandle); // we support sparse and full tile layout, defaulting to full if no type @@ -723,7 +718,7 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { if (!dcm_offset(error, - filehandle, + filehandle, &filehandle->pixel_data_offset)) { return NULL; } @@ -737,8 +732,8 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } -static bool parse_frame_index_element_create(DcmError **error, - void *client, +static bool parse_frame_index_element_create(DcmError **error, + void *client, uint32_t tag, DcmVR vr, char *value, @@ -765,7 +760,7 @@ static bool parse_frame_index_element_create(DcmError **error, } -static bool parse_frame_index_stop(void *client, +static bool parse_frame_index_stop(void *client, uint32_t tag, DcmVR vr, uint32_t length) @@ -789,8 +784,8 @@ static bool read_frame_index(DcmError **error, .stop = parse_frame_index_stop, }; - filehandle->frame_index = DCM_NEW_ARRAY(error, - filehandle->num_frames, + filehandle->frame_index = DCM_NEW_ARRAY(error, + filehandle->num_frames, uint32_t); if (filehandle->frame_index == NULL) { return false; @@ -806,7 +801,6 @@ static bool read_frame_index(DcmError **error, if (!dcm_parse_dataset(error, filehandle->io, filehandle->implicit, - filehandle->byteswap, &parse, filehandle)) { return false; @@ -846,7 +840,6 @@ static bool read_skip_to_index(DcmError **error, if (!dcm_parse_dataset(error, filehandle->io, filehandle->implicit, - filehandle->byteswap, &parse, filehandle)) { return false; @@ -872,7 +865,7 @@ bool dcm_filehandle_read_pixeldata(DcmError **error, return false; } - // if we're on per frame func, read that in + // 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; @@ -899,8 +892,8 @@ bool dcm_filehandle_read_pixeldata(DcmError **error, return NULL; } - filehandle->offset_table = DCM_NEW_ARRAY(error, - filehandle->num_frames, + filehandle->offset_table = DCM_NEW_ARRAY(error, + filehandle->num_frames, int64_t); if (filehandle->offset_table == NULL) { return NULL; @@ -912,20 +905,19 @@ bool dcm_filehandle_read_pixeldata(DcmError **error, if (dcm_is_encapsulated_transfer_syntax(syntax)) { // read the bot if available, otherwise parse pixeldata to find // offsets - if (!dcm_parse_pixeldata(error, - filehandle->io, - filehandle->implicit, - filehandle->byteswap, - &filehandle->first_frame_offset, - filehandle->offset_table, - filehandle->num_frames)) { + if (!dcm_parse_pixeldata_offsets(error, + filehandle->io, + filehandle->implicit, + &filehandle->first_frame_offset, + filehandle->offset_table, + filehandle->num_frames)) { return false; } } else { for (uint32_t i = 0; i < filehandle->num_frames; i++) { - filehandle->offset_table[i] = i * - filehandle->desc.rows * - filehandle->desc.columns * + filehandle->offset_table[i] = i * + filehandle->desc.rows * + filehandle->desc.columns * filehandle->desc.samples_per_pixel; } @@ -952,7 +944,7 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, if (frame_number > filehandle->num_frames) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading Frame Item failed", - "Frame Number must be less than %u", + "Frame Number must be less than %u", filehandle->num_frames); return NULL; } @@ -966,8 +958,8 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, // 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 + + 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; @@ -977,7 +969,6 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, char *frame_data = dcm_parse_frame(error, filehandle->io, filehandle->implicit, - filehandle->byteswap, &filehandle->desc, &length); if (frame_data == NULL) { @@ -1016,7 +1007,7 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, if (column >= filehandle->tiles_across) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading Frame position failed", - "Column must be less than %u", + "Column must be less than %u", filehandle->tiles_across); return NULL; } @@ -1026,7 +1017,7 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, if (index >= filehandle->num_frames) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading Frame position failed", - "Row must be less than %u", + "Row must be less than %u", filehandle->num_frames / filehandle->tiles_across); return NULL; } @@ -1046,40 +1037,60 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, } -static bool print_dataset_begin(DcmError **error, - void *client) +static bool print_dataset_begin(DcmError **error, + void *client) { DcmFilehandle *filehandle = (DcmFilehandle *) client; - printf("dataset_begin\n"); + USED(error); + + filehandle->index += 1; + + if (filehandle->indent > 0) { + printf("%*.*s---Item #%d---\n", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + filehandle->index); + } return true; } -static bool print_dataset_end(DcmError **error, - void *client) +static bool print_sequence_begin(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) { DcmFilehandle *filehandle = (DcmFilehandle *) client; - printf("dataset_end\n"); + USED(error); + USED(vr); + USED(length); - return true; -} + printf("%*.*s(%04x,%04x) ", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + (tag & 0xffff0000) >> 16, + tag >> 16); + if (dcm_is_public_tag(tag)) { + printf("%s ", dcm_dict_keyword_from_tag(tag)); + } -static bool print_sequence_begin(DcmError **error, - void *client) -{ - DcmFilehandle *filehandle = (DcmFilehandle *) client; + printf("[\n"); - printf("sequence_begin\n"); + filehandle->indent += 1; + filehandle->index = 0; return true; } -static bool print_sequence_end(DcmError **error, +static bool print_sequence_end(DcmError **error, void *client, uint32_t tag, DcmVR vr, @@ -1087,14 +1098,73 @@ static bool print_sequence_end(DcmError **error, { DcmFilehandle *filehandle = (DcmFilehandle *) client; - printf("sequence_end\n"); + USED(error); + USED(tag); + USED(vr); + USED(length); + + filehandle->indent -= 1; + + printf("%*.*s]\n", + filehandle->indent * 2, + filehandle->indent * 2, + " "); + + return true; +} + + +static bool print_pixeldata_begin(DcmError **error, + void *client, + uint32_t tag, + DcmVR vr, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + USED(error); + + printf("%*.*s(%04x,%04x) ", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + (tag & 0xffff0000) >> 16, + tag >> 16); + + if (dcm_is_public_tag(tag)) { + printf("%s ", dcm_dict_keyword_from_tag(tag)); + } + + printf("| %s | %u ", dcm_dict_str_from_vr(vr), length); + + printf("[\n"); + + filehandle->indent += 1; + filehandle->index = 0; + + return true; +} + + +static bool print_pixeldata_end(DcmError **error, void *client) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + + USED(error); + + filehandle->indent -= 1; + + printf("%*.*s]\n", + filehandle->indent * 2, + filehandle->indent * 2, + " "); return true; } -static bool print_element_create(DcmError **error, - void *client, +static bool print_element_create(DcmError **error, + void *client, uint32_t tag, DcmVR vr, char *value, @@ -1102,7 +1172,97 @@ static bool print_element_create(DcmError **error, { DcmFilehandle *filehandle = (DcmFilehandle *) client; - printf("sequence_end\n"); + USED(error); + + printf("%*.*s(%04x,%04x) ", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + (tag & 0xffff0000) >> 16, + tag >> 16); + + if (dcm_is_public_tag(tag)) { + printf("%s ", dcm_dict_keyword_from_tag(tag)); + } + + printf("| %s | %u ", dcm_dict_str_from_vr(vr), length); + + // 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("| %s\n", str); + free(str); + } + + dcm_element_destroy(element); + } + + return true; +} + + +static bool print_pixeldata_create(DcmError **, + void *client, + uint32_t tag, + DcmVR vr, + char *value, + uint32_t length) +{ + DcmFilehandle *filehandle = (DcmFilehandle *) client; + size_t size = dcm_dict_vr_size(vr); + + USED(tag); + + printf("%*.*sframe %d ", + filehandle->indent * 2, + filehandle->indent * 2, + " ", + filehandle->index); + + printf("| %u | ", length); + + switch (size) { + default: + case 1: + for (uint32_t i = 0; i < MIN(length, 17); i++) { + printf("%02x ", value[i]); + } + + if (length > 17) { + printf("..."); + } + + break; + + case 2: + for (uint32_t i = 0; i < MIN(length, 10); i++) { + printf("%04x ", ((uint16_t *)value)[i]); + } + + if (length > 10) { + printf("..."); + } + + break; + + case 4: + for (uint32_t i = 0; i < MIN(length, 6); i++) { + printf("%08x ", ((uint32_t *)value)[i]); + } + + if (length > 6) { + printf("..."); + } + + break; + } + + printf("\n"); + + filehandle->index += 1; return true; } @@ -1112,15 +1272,19 @@ bool dcm_filehandle_print(DcmError **error, { static DcmParse parse = { .dataset_begin = print_dataset_begin, - .dataset_end = print_dataset_end, .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; + filehandle->index = 0; if (!parse_preamble(error, filehandle, &position)) { return false; } @@ -1131,7 +1295,6 @@ bool dcm_filehandle_print(DcmError **error, if (!dcm_parse_group(error, filehandle->io, false, - false, &parse, filehandle)) { return false; @@ -1144,7 +1307,6 @@ bool dcm_filehandle_print(DcmError **error, filehandle->io, // FIXME we should check transfer syntax for this false, - is_big_endian(), &parse, filehandle)) { return false; diff --git a/src/dicom-parse.c b/src/dicom-parse.c index a9d3c80..a58bad8 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -24,7 +24,7 @@ #include "pdicom.h" -/* The size of the buffer we use for reading smaller element values. This is +/* The size of the buffer we use for reading smaller element values. This is * large enough for most VRs. */ #define INPUT_BUFFER_SIZE (256) @@ -34,7 +34,7 @@ typedef struct _DcmParseState { DcmError **error; DcmIO *io; bool implicit; - bool byteswap; + bool big_endian; const DcmParse *parse; void *client; @@ -111,14 +111,28 @@ static bool dcm_is_eof(DcmParseState *state) } -static void byteswap(char *data, size_t length, size_t size) +/* 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) { - assert(length >= size); + union { + uint32_t i; + char c[4]; + } bint = {0x01020304}; + + return bint.c[0] == 1; +} - if (size > 1) { - assert(length % size == 0); - assert(size % 2 == 0); +static void byteswap(char *data, size_t length, size_t size) +{ + // only swap if the data is "swappable" + if (size > 1 && + length > size && + length % size == 0 && + size % 2 == 0) { size_t half_size = size / 2; for (size_t i = 0; i < length; i += size) { @@ -145,7 +159,7 @@ static bool read_uint16(DcmParseState *state, return false; } - if (state->byteswap) { + if (state->big_endian) { byteswap(buffer.c, 2, 2); } @@ -155,7 +169,7 @@ static bool read_uint16(DcmParseState *state, } -static bool read_uint32(DcmParseState *state, +static bool read_uint32(DcmParseState *state, uint32_t *value, int64_t *position) { union { @@ -167,7 +181,7 @@ static bool read_uint32(DcmParseState *state, return false; } - if (state->byteswap) { + if (state->big_endian) { byteswap(buffer.c, 4, 4); } @@ -198,7 +212,7 @@ static bool parse_element(DcmParseState *state, int64_t *position); -static bool parse_element_header(DcmParseState *state, +static bool parse_element_header(DcmParseState *state, uint32_t *tag, DcmVR *vr, uint32_t *length, @@ -268,14 +282,18 @@ static bool parse_element_header(DcmParseState *state, } -static bool parse_element_sequence(DcmParseState *state, +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)) { + !state->parse->sequence_begin(state->error, + state->client, + seq_tag, + seq_vr, + seq_length)) { return false; } @@ -358,7 +376,7 @@ static bool parse_element_sequence(DcmParseState *state, } if (state->parse->sequence_end && - !state->parse->sequence_end(state->error, + !state->parse->sequence_end(state->error, state->client, seq_tag, seq_vr, @@ -370,7 +388,135 @@ static bool parse_element_sequence(DcmParseState *state, } -static bool parse_element_body(DcmParseState *state, +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, @@ -383,6 +529,15 @@ static bool parse_element_body(DcmParseState *state, 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 (klass) { @@ -414,7 +569,6 @@ static bool parse_element_body(DcmParseState *state, } - if (!dcm_require(state, value, length, position)) { if (value_free != NULL) { free(value_free); @@ -433,13 +587,13 @@ static bool parse_element_body(DcmParseState *state, if (klass == DCM_CLASS_NUMERIC_DECIMAL || klass == DCM_CLASS_NUMERIC_INTEGER) { - if (state->byteswap) { + if (state->big_endian) { byteswap(value, length, size); } } if (state->parse->element_create && - !state->parse->element_create(state->error, + !state->parse->element_create(state->error, state->client, tag, vr, @@ -469,7 +623,7 @@ static bool parse_element_body(DcmParseState *state, } int64_t seq_position = 0; - if (!parse_element_sequence(state, + if (!parse_element_sequence(state, tag, vr, length, @@ -564,20 +718,19 @@ static bool parse_toplevel_dataset(DcmParseState *state, /* Parse a dataset from a filehandle. */ -bool dcm_parse_dataset(DcmError **error, +bool dcm_parse_dataset(DcmError **error, DcmIO *io, bool implicit, - bool byteswap, - const DcmParse *parse, + const DcmParse *parse, void *client) { - DcmParseState state = { - .error = error, - .io = io, - .implicit = implicit, - .byteswap = byteswap, - .parse = parse, - .client = client + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .big_endian = is_big_endian(), + .parse = parse, + .client = client }; int64_t position = 0; @@ -591,20 +744,19 @@ bool dcm_parse_dataset(DcmError **error, /* Parse a group. A length element, followed by a list of elements. */ -bool dcm_parse_group(DcmError **error, +bool dcm_parse_group(DcmError **error, DcmIO *io, bool implicit, - bool byteswap, - const DcmParse *parse, + const DcmParse *parse, void *client) { - DcmParseState state = { - .error = error, - .io = io, - .implicit = implicit, - .byteswap = byteswap, - .parse = parse, - .client = client + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .big_endian = is_big_endian(), + .parse = parse, + .client = client }; int64_t position = 0; @@ -677,19 +829,18 @@ bool dcm_parse_group(DcmError **error, * Each offset is the seek from the start of pixeldata to the ITEM for that * frame. */ -bool dcm_parse_pixeldata(DcmError **error, - DcmIO *io, - bool implicit, - bool byteswap, - int64_t *first_frame_offset, - int64_t *offsets, - int num_frames) +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, - .byteswap = byteswap + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .big_endian = is_big_endian() }; int64_t position = 0; @@ -760,6 +911,9 @@ bool dcm_parse_pixeldata(DcmError **error, // 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; @@ -811,15 +965,14 @@ bool dcm_parse_pixeldata(DcmError **error, char *dcm_parse_frame(DcmError **error, DcmIO *io, bool implicit, - bool byteswap, struct PixelDescription *desc, uint32_t *length) { - DcmParseState state = { - .error = error, - .io = io, - .implicit = implicit, - .byteswap = byteswap, + DcmParseState state = { + .error = error, + .io = io, + .implicit = implicit, + .big_endian = is_big_endian(), }; int64_t position = 0; diff --git a/src/pdicom.h b/src/pdicom.h index 0462b85..46f3c76 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -86,49 +86,64 @@ typedef struct _DcmParse { bool (*dataset_begin)(DcmError **, void *client); bool (*dataset_end)(DcmError **, void *client); - bool (*sequence_begin)(DcmError **, void *client); - bool (*sequence_end)(DcmError **, + 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 (*element_create)(DcmError **, - void *client, + 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 (*stop)(void *client, - uint32_t tag, - DcmVR vr, + 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, +DCM_EXTERN +bool dcm_parse_dataset(DcmError **error, DcmIO *io, bool implicit, - bool byteswap, - const DcmParse *parse, + const DcmParse *parse, void *client); -DCM_EXTERN -bool dcm_parse_group(DcmError **error, +DCM_EXTERN +bool dcm_parse_group(DcmError **error, DcmIO *io, bool implicit, - bool byteswap, - const DcmParse *parse, + const DcmParse *parse, void *client); -bool dcm_parse_pixeldata(DcmError **error, - DcmIO *io, - bool implicit, - bool byteswap, - int64_t *first_frame_offset, - int64_t *offsets, - int num_frames); +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; @@ -146,6 +161,5 @@ struct PixelDescription { char *dcm_parse_frame(DcmError **error, DcmIO *io, bool implicit, - bool byteswap, struct PixelDescription *desc, uint32_t *length); From c7a455f4eac056456141093f2b9b21042d95873e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 23 Jun 2023 12:29:10 +0100 Subject: [PATCH 48/82] stack of index counters --- TODO | 12 ++++++- src/dicom-data.c | 90 +++++++++++++++++++++++------------------------ src/dicom-file.c | 30 +++++++++++----- src/dicom-parse.c | 22 ++++++------ 4 files changed, 89 insertions(+), 65 deletions(-) diff --git a/TODO b/TODO index bb26121..a3deaf6 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,16 @@ # TODO -- read metadata needs an "all" flag, or perhaps a set of stop tags? +- read metadata needs to define callbacks for pixeldata + +- note tag positions in the stop function + +- read metadata needs a set of stop tags + + DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, + DcmFilehandle *filehandle, + uint32_t *stop_tags) + + where stop_tags == NULL means stop set as now # Tips diff --git a/src/dicom-data.c b/src/dicom-data.c index 7c83263..64ba09c 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -172,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; @@ -192,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); } @@ -257,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); @@ -275,7 +275,7 @@ static bool element_check_string(DcmError **error, if (klass != DCM_CLASS_STRING_MULTI && klass != DCM_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; @@ -291,7 +291,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; } @@ -306,7 +306,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; } @@ -357,7 +357,7 @@ static bool element_check_capacity(DcmError **error, if (length > capacity) { element->assigned = was_assigned; dcm_log_warning("Data Element capacity check failed -- " - "Value of Data Element '%08X' exceeds " + "Value of Data Element '%08x' exceeds " "maximum length of Value Representation (%d)", element->tag, capacity); @@ -381,18 +381,18 @@ 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_DECIMAL || + if (klass == DCM_CLASS_NUMERIC_DECIMAL || klass == DCM_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; } @@ -449,7 +449,7 @@ bool dcm_element_set_value_string_multi(DcmError **error, if (klass != DCM_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; @@ -627,11 +627,11 @@ static bool element_check_numeric(DcmError **error, const DcmElement *element) { DcmVRClass klass = dcm_dict_vr_class(element->vr); - if (klass != DCM_CLASS_NUMERIC_DECIMAL && + if (klass != DCM_CLASS_NUMERIC_DECIMAL && klass != DCM_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; } @@ -646,7 +646,7 @@ static bool element_check_integer(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 integer", - "Element tag %08X is not integer", + "Element tag %08x is not integer", element->tag); return false; } @@ -783,7 +783,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; } @@ -850,7 +850,7 @@ static bool element_check_binary(DcmError **error, if (klass != DCM_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; } @@ -917,7 +917,7 @@ 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, +bool dcm_element_set_value(DcmError **error, DcmElement *element, char *value, uint32_t length, @@ -925,7 +925,7 @@ bool dcm_element_set_value(DcmError **error, { size_t size; - switch (dcm_dict_vr_class(element->vr)) + switch (dcm_dict_vr_class(element->vr)) { case DCM_CLASS_STRING_SINGLE: case DCM_CLASS_STRING_MULTI: @@ -953,10 +953,10 @@ bool dcm_element_set_value(DcmError **error, break; case DCM_CLASS_BINARY: - if (!dcm_element_set_value_binary(error, + if (!dcm_element_set_value_binary(error, element, - value, - length, + value, + length, steal)) { return false; } @@ -967,7 +967,7 @@ bool dcm_element_set_value(DcmError **error, default: dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading of Data Element failed", - "Data Element '%08X' has unexpected " + "Data Element '%08x' has unexpected " "Value Representation", element->tag); return false; } @@ -984,7 +984,7 @@ static bool element_check_sequence(DcmError **error, if (klass != DCM_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; } @@ -1051,7 +1051,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) { @@ -1201,22 +1201,22 @@ char *dcm_element_value_to_string(const DcmElement *element) for (uint32_t index = 0; index < element->vm; index++) { switch (klass) { case DCM_CLASS_NUMERIC_DECIMAL: - (void) dcm_element_get_value_decimal(NULL, - element, - index, + (void) dcm_element_get_value_decimal(NULL, + element, + index, &d); result = dcm_printf_append(result, "%g", d); break; case DCM_CLASS_NUMERIC_INTEGER: - (void) dcm_element_get_value_integer(NULL, - element, - index, + (void) dcm_element_get_value_integer(NULL, + element, + index, &i); if (element->vr == DCM_VR_UV) { - result = dcm_printf_append(result, - "%"PRIu64, + result = dcm_printf_append(result, + "%"PRIu64, (uint64_t)i); } else { result = dcm_printf_append(result, "%"PRId64, i); @@ -1225,16 +1225,16 @@ char *dcm_element_value_to_string(const DcmElement *element) case DCM_CLASS_STRING_SINGLE: case DCM_CLASS_STRING_MULTI: - (void) dcm_element_get_value_string(NULL, - element, - index, + (void) dcm_element_get_value_string(NULL, + element, + index, &str); result = dcm_printf_append(result, "%s", str); break; case DCM_CLASS_BINARY: - result = dcm_printf_append(result, - "", + result = dcm_printf_append(result, + "", dcm_element_get_length(element)); break; @@ -1265,7 +1265,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, " ", @@ -1276,7 +1276,7 @@ void dcm_element_print(const DcmElement *element, int indentation) } else { // private tag, or unknown public tag // in any case, we can't display the keyword - printf("%*.*s (%04X,%04X) | %s", + printf("%*.*s (%04x,%04x) | %s", num_indent, num_indent, " ", @@ -1392,7 +1392,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; @@ -1407,13 +1407,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); } @@ -1424,7 +1424,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) { @@ -1668,8 +1668,8 @@ DcmDataSet *dcm_sequence_steal(DcmError **error, bool dcm_sequence_foreach(const DcmSequence *seq, - bool (*fn)(const DcmDataSet *item, - uint32_t index, + bool (*fn)(const DcmDataSet *item, + uint32_t index, void *client), void *client) { diff --git a/src/dicom-file.c b/src/dicom-file.c index 3071a10..23136aa 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -61,7 +61,7 @@ struct _DcmFilehandle { int indent; // dataset index for file print - int index; + UT_array *index_stack; // push and pop these while we parse UT_array *dataset_stack; @@ -84,6 +84,7 @@ DcmFilehandle *dcm_filehandle_create(DcmError **error, DcmIO *io) 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); @@ -120,6 +121,12 @@ 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)); @@ -159,6 +166,7 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) dcm_io_close(filehandle->io); + utarray_free(filehandle->index_stack); utarray_free(filehandle->dataset_stack); utarray_free(filehandle->sequence_stack); @@ -1041,17 +1049,18 @@ static bool print_dataset_begin(DcmError **error, void *client) { DcmFilehandle *filehandle = (DcmFilehandle *) client; + int *index = (int *) utarray_back(filehandle->index_stack); USED(error); - filehandle->index += 1; + *index += 1; if (filehandle->indent > 0) { printf("%*.*s---Item #%d---\n", filehandle->indent * 2, filehandle->indent * 2, " ", - filehandle->index); + *index); } return true; @@ -1084,7 +1093,8 @@ static bool print_sequence_begin(DcmError **error, printf("[\n"); filehandle->indent += 1; - filehandle->index = 0; + int index = 0; + utarray_push_back(filehandle->index_stack, &index); return true; } @@ -1104,6 +1114,7 @@ static bool print_sequence_end(DcmError **error, USED(length); filehandle->indent -= 1; + utarray_pop_back(filehandle->index_stack); printf("%*.*s]\n", filehandle->indent * 2, @@ -1140,7 +1151,8 @@ static bool print_pixeldata_begin(DcmError **error, printf("[\n"); filehandle->indent += 1; - filehandle->index = 0; + int index = 0; + utarray_push_back(filehandle->index_stack, &index); return true; } @@ -1153,6 +1165,7 @@ static bool print_pixeldata_end(DcmError **error, void *client) USED(error); filehandle->indent -= 1; + utarray_pop_back(filehandle->index_stack); printf("%*.*s]\n", filehandle->indent * 2, @@ -1213,6 +1226,7 @@ static bool print_pixeldata_create(DcmError **, { DcmFilehandle *filehandle = (DcmFilehandle *) client; size_t size = dcm_dict_vr_size(vr); + int *index = (int *) utarray_back(filehandle->index_stack); USED(tag); @@ -1220,7 +1234,7 @@ static bool print_pixeldata_create(DcmError **, filehandle->indent * 2, filehandle->indent * 2, " ", - filehandle->index); + *index); printf("| %u | ", length); @@ -1262,7 +1276,7 @@ static bool print_pixeldata_create(DcmError **, printf("\n"); - filehandle->index += 1; + *index += 1; return true; } @@ -1284,7 +1298,7 @@ bool dcm_filehandle_print(DcmError **error, // skip File Preamble int64_t position = 0; filehandle->indent = 0; - filehandle->index = 0; + dcm_filehandle_clear(filehandle); if (!parse_preamble(error, filehandle, &position)) { return false; } diff --git a/src/dicom-parse.c b/src/dicom-parse.c index a58bad8..ee8c92e 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -229,7 +229,7 @@ static bool parse_element_header(DcmParseState *state, 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); + "Tag %08x not allowed in implicit mode", *tag); return false; } @@ -248,7 +248,7 @@ static bool parse_element_header(DcmParseState *state, 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); + "Tag %08x cannot have VR '%s'", *tag, vr_str); return false; } @@ -271,7 +271,7 @@ static bool parse_element_header(DcmParseState *state, 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'.", + "of Data Element %08x with VR '%s'.", tag, vr); return false; } @@ -316,7 +316,7 @@ static bool parse_element_sequence(DcmParseState *state, 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' " + "Expected tag '%08x' instead of '%08x' " "for Item #%d", TAG_ITEM, item_tag, @@ -482,7 +482,7 @@ static bool parse_pixeldata(DcmParseState *state, 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' " + "Expected tag '%08x' instead of '%08x' " "for Item #%d", TAG_ITEM, item_tag, @@ -538,7 +538,7 @@ static bool parse_element_body(DcmParseState *state, return parse_pixeldata(state, tag, vr, length, position); } - dcm_log_debug("Read Data Element body '%08X'", tag); + dcm_log_debug("Read Data Element body '%08x'", tag); switch (klass) { case DCM_CLASS_STRING_SINGLE: @@ -552,7 +552,7 @@ static bool parse_element_body(DcmParseState *state, if (length % size != 0) { dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, "Reading of Data Element failed", - "Bad length for tag '%08X'", + "Bad length for tag '%08x'", tag); return false; } @@ -613,11 +613,11 @@ static bool parse_element_body(DcmParseState *state, case DCM_CLASS_SEQUENCE: if (length == 0xFFFFFFFF) { - dcm_log_debug("Sequence of Data Element '%08X' " + dcm_log_debug("Sequence of Data Element '%08x' " "has undefined length.", tag); } else { - dcm_log_debug("Sequence of Data Element '%08X' " + dcm_log_debug("Sequence of Data Element '%08x' " "has defined length %d.", tag, length); } @@ -637,7 +637,7 @@ static bool parse_element_body(DcmParseState *state, default: dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, "Reading of Data Element failed", - "Data Element '%08X' has unexpected " + "Data Element '%08x' has unexpected " "Value Representation", tag); return false; } @@ -934,7 +934,7 @@ bool dcm_parse_pixeldata_offsets(DcmError **error, if (tag != TAG_ITEM) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Building Basic Offset Table failed", - "Frame Item #%d has wrong Tag '%08X'", + "Frame Item #%d has wrong Tag '%08x'", i + 1, tag); return false; From 3003042ba71bc5651b05b4e1374a2f6ae0f04d3d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 23 Jun 2023 14:00:58 +0100 Subject: [PATCH 49/82] add stop_tags to metadata read So users can control how much metadata they read. --- include/dicom/dicom.h | 14 ++++++++++---- src/dicom-file.c | 34 ++++++++++++++++++++++++++-------- tests/check_dicom.c | 28 ++++++++++++++-------------- tools/dcm-getframe.c | 20 +++++++++++--------- 4 files changed, 61 insertions(+), 35 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 470bc0c..f6b6edf 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -130,11 +130,11 @@ DCM_EXTERN DCM_CONSTRUCTOR void dcm_init(void); -/* Our copy of getopt, since non-glibc platforms are missing this. +/* Our copy of getopt, since non-glibc platforms are missing this. * Used by our tools. */ DCM_EXTERN -char *dcm_optarg; +char *dcm_optarg; DCM_EXTERN int dcm_optind, dcm_opterr, dcm_optopt, dcm_optreset; DCM_EXTERN @@ -1147,7 +1147,7 @@ void dcm_dataset_destroy(DcmDataSet *dataset); */ /** - * Create a Sequence, i.e., an ordered list of Data Set items that represent + * 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 @@ -1658,14 +1658,20 @@ const char *dcm_filehandle_get_transfer_syntax_uid(const DcmFilehandle *filehand * beginning of the filehandle to speed up subsequent access to individual * Frame items. * + * Reading stops when any of the tags in the stop_tags array are seen. If this + * pointer is NULL, then reading will stop on any tag that is likely to take a + * long time to parse. + * * :param error: Pointer to error object * :param filehandle: File + * :param stop_tags: Zero-terminated array of tags to stop on * * :return: metadata */ DCM_EXTERN DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, - DcmFilehandle *filehandle); + DcmFilehandle *filehandle, + uint32_t *stop_tags); /** * Read everything necessary to fetch frames from the file. diff --git a/src/dicom-file.c b/src/dicom-file.c index 23136aa..25dd7d0 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -33,6 +33,7 @@ struct _DcmFilehandle { DcmIO *io; char *transfer_syntax_uid; bool implicit; + uint32_t *stop_tags; // start of image metadata int64_t offset; @@ -558,11 +559,13 @@ static bool parse_meta_stop(void *client, filehandle->last_tag = tag; - return tag == TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE || - tag == TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE || - tag == TAG_PIXEL_DATA || - tag == TAG_FLOAT_PIXEL_DATA || - tag == TAG_DOUBLE_PIXEL_DATA; + for (int i = 0; filehandle->stop_tags[i]; i++) { + if (tag == filehandle->stop_tags[i]) { + return true; + } + } + + return false; } @@ -635,9 +638,23 @@ static bool set_pixel_description(DcmError **error, DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, - DcmFilehandle *filehandle) + DcmFilehandle *filehandle, + uint32_t *stop_tags) { + // by default, we stop on any of the tags that start a huge group that + // would take a long time to parse + static uint32_t default_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, + }; + 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, @@ -646,6 +663,8 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, .stop = parse_meta_stop, }; + filehandle->stop_tags = stop_tags == NULL ? default_stop_tags : stop_tags; + if (filehandle->offset == 0) { DcmDataSet *file_meta = dcm_filehandle_read_file_meta(error, filehandle); @@ -666,7 +685,6 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } utarray_push_back(filehandle->sequence_stack, &sequence); - // parse up to perframefunctionalgroupsequence, or the pixel data if (!dcm_parse_dataset(error, filehandle->io, filehandle->implicit, @@ -1242,7 +1260,7 @@ static bool print_pixeldata_create(DcmError **, default: case 1: for (uint32_t i = 0; i < MIN(length, 17); i++) { - printf("%02x ", value[i]); + printf("%02x ", ((uint8_t *)value)[i]); } if (length > 17) { diff --git a/tests/check_dicom.c b/tests/check_dicom.c index edea744..586359d 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); @@ -505,7 +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, + (void) dcm_element_set_value_numeric_multi(NULL, element, (int*) value, vm, false); ck_assert_int_eq(dcm_element_get_tag(element), tag); @@ -533,7 +533,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, + (void) dcm_element_set_value_numeric_multi(NULL, element, (int*) &value, vm, false); ck_assert_int_eq(dcm_element_get_tag(element), tag); @@ -647,7 +647,7 @@ 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); @@ -677,12 +677,12 @@ 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); + DcmDataSet *metadata = dcm_filehandle_read_metadata(NULL, filehandle, NULL); ck_assert_ptr_nonnull(metadata); // SOP Class UID @@ -704,18 +704,18 @@ 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); + DcmDataSet *metadata = dcm_filehandle_read_metadata(NULL, filehandle, NULL); ck_assert_ptr_nonnull(metadata); ck_assert_int_ne(dcm_filehandle_read_pixeldata(NULL, filehandle), 0); - DcmFrame *frame = dcm_filehandle_read_frame(NULL, - filehandle, + 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); @@ -746,7 +746,7 @@ 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); diff --git a/tools/dcm-getframe.c b/tools/dcm-getframe.c index 5646c95..4f59e5a 100644 --- a/tools/dcm-getframe.c +++ b/tools/dcm-getframe.c @@ -14,7 +14,7 @@ static const char usage[] = "usage: " "dcm-getframe [-v] [-V] [-h] [-o OUTPUT-FILE] FILE_PATH FRAME_NUMBER"; -int main(int argc, char *argv[]) +int main(int argc, char *argv[]) { char *output_file = NULL; @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) int frame_number = atoi(argv[dcm_optind + 1]); dcm_log_info("Read filehandle '%s'", input_file); - DcmFilehandle *filehandle = dcm_filehandle_create_from_file(&error, + DcmFilehandle *filehandle = dcm_filehandle_create_from_file(&error, input_file); if (filehandle == NULL) { dcm_error_print(error); @@ -66,7 +66,9 @@ int main(int argc, char *argv[]) } dcm_log_info("Read metadata"); - DcmDataSet *metadata = dcm_filehandle_read_metadata(&error, filehandle); + DcmDataSet *metadata = dcm_filehandle_read_metadata(&error, + filehandle, + NULL); if (metadata == NULL) { dcm_error_print(error); dcm_error_clear(&error); @@ -106,7 +108,7 @@ int main(int argc, char *argv[]) } dcm_log_info("Read frame %u", frame_number); - DcmFrame *frame = dcm_filehandle_read_frame(&error, + DcmFrame *frame = dcm_filehandle_read_frame(&error, filehandle, frame_number); if (frame == NULL) { @@ -124,18 +126,18 @@ 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; From 4970018d13d16aa45f3d87ff1c4d4e53037e220f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 23 Jun 2023 15:30:42 +0100 Subject: [PATCH 50/82] revise base offset for native frames --- src/dicom-file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index 25dd7d0..c7a2f8e 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -948,7 +948,7 @@ bool dcm_filehandle_read_pixeldata(DcmError **error, } // Header of Pixel Data Element - filehandle->first_frame_offset = 10; + filehandle->first_frame_offset = 12; } return true; From fbba3061a6fe669187d0cb5de6338784b6cccc79 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 24 Jun 2023 16:09:38 +0100 Subject: [PATCH 51/82] revisw byteswapping code --- src/dicom-parse.c | 66 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/src/dicom-parse.c b/src/dicom-parse.c index ee8c92e..a1ae858 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -126,22 +126,62 @@ static bool is_big_endian(void) } +#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 (size > 1 && - length > size && - length % size == 0 && - size % 2 == 0) { - size_t half_size = size / 2; - - 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; - } + 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; } } } From b90805a48593b08516b61d26295289488066e152 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 25 Jun 2023 01:58:33 +0100 Subject: [PATCH 52/82] start reworking the API to hold more state --- src/dicom-file.c | 198 ++++++++++++++++++++++++++++++----------------- 1 file changed, 127 insertions(+), 71 deletions(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index c7a2f8e..cf6e5de 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -42,6 +42,10 @@ struct _DcmFilehandle { // 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; + // image properties we need to track uint32_t tiles_across; uint32_t num_frames; @@ -171,6 +175,10 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) utarray_free(filehandle->dataset_stack); utarray_free(filehandle->sequence_stack); + if (filehandle->meta) { + dcm_dataset_destroy(filehandle->meta); + } + free(filehandle); } } @@ -452,8 +460,8 @@ static bool parse_preamble(DcmError **error, } -DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, - DcmFilehandle *filehandle) +static DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, + DcmFilehandle *filehandle) { static DcmParse parse = { .dataset_begin = parse_meta_dataset_begin, @@ -494,11 +502,6 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, abort(); } - // record the start point for the image metadata - if (!dcm_offset(error, filehandle, &filehandle->offset)) { - return NULL; - } - // we must read sequence back off the stack since it may have been // realloced sequence = *((DcmSequence **) utarray_back(filehandle->sequence_stack)); @@ -511,33 +514,64 @@ DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, return false; } - DcmElement *element = dcm_dataset_get(error, file_meta, 0x00020010); - if (element == NULL) { - return NULL; - } + // steal file_meta to stop it being destroyed + (void) dcm_sequence_steal(NULL, sequence, 0); + dcm_filehandle_clear(filehandle); - const char *transfer_syntax_uid; - if (!dcm_element_get_value_string(error, - element, - 0, - &transfer_syntax_uid)) { - return NULL; - } + return file_meta; +} - filehandle->transfer_syntax_uid = dcm_strdup(error, transfer_syntax_uid); - if (filehandle->transfer_syntax_uid == NULL) { - return NULL; - } +DcmDataSet *dcm_filehandle_get_file_meta(DcmError **error, + DcmFilehandle *filehandle) +{ + if (filehandle->file_meta == NULL) { + DcmDataSet *file_meta = + dcm_filehandle_read_file_meta(error, filehandle); + if (file_meta == NULL) { + return NULL; + } - if (strcmp(filehandle->transfer_syntax_uid, "1.2.840.10008.1.2") == 0) { - filehandle->implicit = true; + // record the start point for the image metadata + if (!dcm_offset(error, filehandle, &filehandle->offset)) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + DcmElement *element = dcm_dataset_get(error, file_meta, 0x00020010); + if (element == NULL) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + const char *transfer_syntax_uid; + if (!dcm_element_get_value_string(error, + element, + 0, + &transfer_syntax_uid)) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + filehandle->transfer_syntax_uid = + dcm_strdup(error, transfer_syntax_uid); + if (filehandle->transfer_syntax_uid == NULL) { + dcm_dataset_destroy(file_meta); + return NULL; + } + + if (strcmp(filehandle->transfer_syntax_uid, "1.2.840.10008.1.2") == 0) { + filehandle->implicit = true; + } + + filehandle->desc.transfer_syntax_uid = filehandle->transfer_syntax_uid; + + filehandle->file_meta = file_meta; } - // steal file_meta to stop it being destroyed - (void) dcm_sequence_steal(NULL, sequence, 0); - dcm_filehandle_clear(filehandle); - return file_meta; + FIXME need to always leave the file read point in the same place + + return filehandle->file_meta; } @@ -570,8 +604,8 @@ static bool parse_meta_stop(void *client, static bool set_pixel_description(DcmError **error, - struct PixelDescription *desc, - const DcmDataSet *metadata) + const DcmDataSet *metadata, + struct PixelDescription *desc) { DcmElement *element; int64_t value; @@ -641,11 +675,8 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, DcmFilehandle *filehandle, uint32_t *stop_tags) { - // by default, we stop on any of the tags that start a huge group that - // would take a long time to parse + // by default, we don't stop anywhere (except pixeldata) static uint32_t default_stop_tags[] = { - TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE, - TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE, TAG_PIXEL_DATA, TAG_FLOAT_PIXEL_DATA, TAG_DOUBLE_PIXEL_DATA, @@ -666,12 +697,11 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, filehandle->stop_tags = stop_tags == NULL ? default_stop_tags : stop_tags; if (filehandle->offset == 0) { - DcmDataSet *file_meta = dcm_filehandle_read_file_meta(error, - filehandle); + DcmDataSet *file_meta = dcm_filehandle_get_file_meta(error, + filehandle); if (file_meta == NULL) { return NULL; } - dcm_dataset_destroy(file_meta); } if (!dcm_seekset(error, filehandle, filehandle->offset)) { @@ -713,48 +743,70 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, return false; } - // useful values for later - if (!get_tiles_across(error, meta, &filehandle->tiles_across) || - !get_num_frames(error, meta, &filehandle->num_frames)) { - return false; - } + // steal meta to stop it being destroyed + (void) dcm_sequence_steal(NULL, sequence, 0); + dcm_filehandle_clear(filehandle); - if (!set_pixel_description(error, &filehandle->desc, meta)) { - return false; - } + return meta; +} + + +DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, + DcmFilehandle *filehandle) +{ + // we stop on any of the tags that start a huge group that + // would take a long time to parse + static uint32_t stop_tags[] = { + TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE, + TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE, + TAG_PIXEL_DATA, + TAG_FLOAT_PIXEL_DATA, + TAG_DOUBLE_PIXEL_DATA, + 0, + }; - filehandle->desc.transfer_syntax_uid = - dcm_filehandle_get_transfer_syntax_uid(filehandle); + if (filehandle->meta == NULL) { + Dataset *meta = dcm_filehandle_read_metadata(error, + filehandle, + stop_tags); - // 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 { - filehandle->layout = DCM_LAYOUT_UNKNOWN; + // 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; } - } - // did we stop on pixel data? record the offset - if (filehandle->last_tag == TAG_PIXEL_DATA || - filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || - filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { - if (!dcm_offset(error, - filehandle, - &filehandle->pixel_data_offset)) { - return NULL; + // 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 { + filehandle->layout = DCM_LAYOUT_UNKNOWN; + } } - } - // steal meta to stop it being destroyed - (void) dcm_sequence_steal(NULL, sequence, 0); - dcm_filehandle_clear(filehandle); + // did we stop on pixel data? record the offset + if (filehandle->last_tag == TAG_PIXEL_DATA || + filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || + filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { + if (!dcm_offset(error, + filehandle, + &filehandle->pixel_data_offset)) { + dcm_dataset_destroy(meta); + return NULL; + } + } - return meta; + filehandle->meta = meta; + } + + return filehandle->meta; } @@ -878,6 +930,10 @@ static bool read_skip_to_index(DcmError **error, bool dcm_filehandle_read_pixeldata(DcmError **error, DcmFilehandle *filehandle) { + if (dcm_filehandle_get_metadata(error, filehandle) == NULL) { + return false; + } + if (filehandle->layout == DCM_LAYOUT_UNKNOWN) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading PixelData failed", From 532879925086b3d8cfc7b3c090af96fa2ea6589a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 25 Jun 2023 02:00:37 +0100 Subject: [PATCH 53/82] fix clang build --- src/dicom-file.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dicom-file.c b/src/dicom-file.c index c7a2f8e..6a510f8 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -1235,7 +1235,7 @@ static bool print_element_create(DcmError **error, } -static bool print_pixeldata_create(DcmError **, +static bool print_pixeldata_create(DcmError **error, void *client, uint32_t tag, DcmVR vr, @@ -1246,6 +1246,7 @@ static bool print_pixeldata_create(DcmError **, size_t size = dcm_dict_vr_size(vr); int *index = (int *) utarray_back(filehandle->index_stack); + USED(error); USED(tag); printf("%*.*sframe %d ", From 71218dab6cd06b62925a4f4031edd508141f139e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 25 Jun 2023 12:59:14 +0100 Subject: [PATCH 54/82] API revision done docs next --- include/dicom/dicom.h | 109 +++++++++++++++------- src/dicom-file.c | 210 ++++++++++++++++++++++-------------------- tests/check_dicom.c | 12 +-- tools/dcm-getframe.c | 45 --------- 4 files changed, 190 insertions(+), 186 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index f6b6edf..76569b2 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1626,11 +1626,21 @@ DCM_EXTERN void dcm_filehandle_destroy(DcmFilehandle *filehandle); /** - * Read File Meta Information from a File. + * Get File Meta Information from a File. * - * 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. + * Reads the File Meta Information and saves it in the File handle. A pointer + * to libdicom's copy of the File Meta Information is returned. The File Meta + * Information is used to set the Transfer Syntax and enable or disable + * implicit mode. + * + * The resturn result must not be destroyed. Make a clone of it with + * 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 @@ -1638,8 +1648,8 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle); * :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. @@ -1654,59 +1664,76 @@ const char *dcm_filehandle_get_transfer_syntax_uid(const DcmFilehandle *filehand /** * 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 dcm_dataset_destroy(). * - * Reading stops when any of the tags in the stop_tags array are seen. If this - * pointer is NULL, then reading will stop on any tag that is likely to take a - * long time to parse. + * After calling this function, the filehandle read point is always + * positioned at the tag that stopped the read. You can call this function + * with a different stop set to read more of the metadata. * * :param error: Pointer to error object * :param filehandle: File - * :param stop_tags: Zero-terminated array of tags to stop on + * :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, - uint32_t *stop_tags); + const uint32_t *stop_tags); /** - * Read everything necessary to fetch frames from the file. + * Get metadata from a File. + * + * Gets the File's metadata and saves it in the File handle. A pointer + * to libdicom's copy of the File metadata is returned. The metadata is used + * to set various internal fields. + * + * The return result must not be destroyed. Make a clone of it with + * dcm_dataset_clone() if you need it to remain valid after closing the File + * handle. * - * Scans the PixelData sequence and loads the - * PerFrameFunctionalGroupSequence, if present. + * After calling this function, the filehandle read point is always + * positioned at the tag that stopped the read. + * + * It is safe to call this function many times. * * :param error: Pointer to error object * :param filehandle: File * - * :return: true on success + * :return: metadata */ DCM_EXTERN -bool dcm_filehandle_read_pixeldata(DcmError **error, - DcmFilehandle *filehandle); +const DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, + DcmFilehandle *filehandle); /** - * Read the frame at a position in a File. + * Read everything necessary to fetch frames from the file. * - * Read a tile from a File at a specified (column, row), numbered from zero. - * This takes account of any frame positioning given in - * PerFrameFunctionalGroupSequence, if necessary. + * Scans the PixelData sequence and loads the PerFrameFunctionalGroupSequence, + * if present. + * + * This function will be called automatically on the first call to + * dcm_filehandle_read_frame_position() or dcm_filehandle_read_frame(). It can + * take some time to execute, so it is available as a separate function call + * in case this delay need 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 column: Column number, from 0 - * :param row: Row number, from 0 * - * :return: Frame + * :return: true on success */ DCM_EXTERN -DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, - DcmFilehandle *filehandle, - uint32_t column, - uint32_t row); +bool dcm_filehandle_read_pixeldata(DcmError **error, + DcmFilehandle *filehandle); /** * Read an individual Frame from a File. @@ -1724,6 +1751,26 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, DcmFilehandle *filehandle, uint32_t frame_number); +/** + * Read the frame at a position in a File. + * + * Read a tile from a File at a specified (column, row), numbered from zero. + * This takes account of any frame positioning given in + * PerFrameFunctionalGroupSequence, if necessary. + * + * :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); + /** * Scan a file and print the entire structure to stdout. * diff --git a/src/dicom-file.c b/src/dicom-file.c index 64baa90..b56b47b 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -33,10 +33,12 @@ struct _DcmFilehandle { DcmIO *io; char *transfer_syntax_uid; bool implicit; - uint32_t *stop_tags; + 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; // distance from pixel metadata to start of first frame @@ -521,8 +523,9 @@ static DcmDataSet *dcm_filehandle_read_file_meta(DcmError **error, return file_meta; } -DcmDataSet *dcm_filehandle_get_file_meta(DcmError **error, - DcmFilehandle *filehandle) + +const DcmDataSet *dcm_filehandle_get_file_meta(DcmError **error, + DcmFilehandle *filehandle) { if (filehandle->file_meta == NULL) { DcmDataSet *file_meta = @@ -566,11 +569,13 @@ DcmDataSet *dcm_filehandle_get_file_meta(DcmError **error, 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; + } } - - FIXME need to always leave the file read point in the same place - return filehandle->file_meta; } @@ -673,10 +678,10 @@ static bool set_pixel_description(DcmError **error, DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, DcmFilehandle *filehandle, - uint32_t *stop_tags) + const uint32_t *stop_tags) { // by default, we don't stop anywhere (except pixeldata) - static uint32_t default_stop_tags[] = { + static const uint32_t default_stop_tags[] = { TAG_PIXEL_DATA, TAG_FLOAT_PIXEL_DATA, TAG_DOUBLE_PIXEL_DATA, @@ -694,21 +699,18 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, .stop = parse_meta_stop, }; - filehandle->stop_tags = stop_tags == NULL ? default_stop_tags : stop_tags; - - if (filehandle->offset == 0) { - DcmDataSet *file_meta = dcm_filehandle_get_file_meta(error, - filehandle); + // 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; } } - if (!dcm_seekset(error, filehandle, filehandle->offset)) { - return NULL; - } - 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; @@ -751,8 +753,8 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } -DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, - DcmFilehandle *filehandle) +const DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, + DcmFilehandle *filehandle) { // we stop on any of the tags that start a huge group that // would take a long time to parse @@ -766,9 +768,22 @@ DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, }; if (filehandle->meta == NULL) { - Dataset *meta = dcm_filehandle_read_metadata(error, - filehandle, - stop_tags); + // always rewind the filehandle + const DcmDataSet *file_meta = dcm_filehandle_get_file_meta(error, + filehandle); + if (file_meta == NULL) { + return NULL; + } + + DcmDataSet *meta = dcm_filehandle_read_metadata(error, + filehandle, + stop_tags); + + // 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; + } // useful values for later if (!get_tiles_across(error, meta, &filehandle->tiles_across) || @@ -791,19 +806,12 @@ DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, } } - // did we stop on pixel data? record the offset - if (filehandle->last_tag == TAG_PIXEL_DATA || - filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || - filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { - if (!dcm_offset(error, - filehandle, - &filehandle->pixel_data_offset)) { - dcm_dataset_destroy(meta); - return NULL; - } - } - 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 filehandle->meta; @@ -930,81 +938,82 @@ static bool read_skip_to_index(DcmError **error, bool dcm_filehandle_read_pixeldata(DcmError **error, DcmFilehandle *filehandle) { - if (dcm_filehandle_get_metadata(error, filehandle) == NULL) { - return false; - } - - if (filehandle->layout == DCM_LAYOUT_UNKNOWN) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading PixelData failed", - "Unsupported DimensionOrganisationType."); - return false; - } + if (filehandle->offset_table == NULL) { + // move to the first of our stop tags + if (dcm_filehandle_get_metadata(error, filehandle) == NULL) { + return false; + } - // 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; - } + if (filehandle->layout == DCM_LAYOUT_UNKNOWN) { + dcm_error_set(error, DCM_ERROR_CODE_PARSE, + "Reading PixelData failed", + "Unsupported DimensionOrganisationType."); + return false; + } - // 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; - } + // 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; + } - // and hopefully we're now on the pixel data - if (filehandle->last_tag == TAG_PIXEL_DATA || - filehandle->last_tag == TAG_FLOAT_PIXEL_DATA || - filehandle->last_tag == TAG_DOUBLE_PIXEL_DATA) { - if (!dcm_offset(error, - filehandle, &filehandle->pixel_data_offset)) { - return NULL; + // 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; } - } - if (filehandle->pixel_data_offset == 0) { - dcm_error_set(error, DCM_ERROR_CODE_PARSE, - "Reading PixelData failed", - "Could not determine offset of Pixel Data Element."); - 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; + } - if (!dcm_seekset(error, filehandle, filehandle->pixel_data_offset)) { - return NULL; - } + if (!dcm_offset(error, filehandle, &filehandle->pixel_data_offset)) { + return false; + } - filehandle->offset_table = DCM_NEW_ARRAY(error, - filehandle->num_frames, - int64_t); - if (filehandle->offset_table == NULL) { - 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; + } - dcm_log_debug("Reading PixelData."); + 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; + } + } 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; + } - 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; + // Header of Pixel Data Element + filehandle->first_frame_offset = 12; } } 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; + // always position at pixel_data + if (!dcm_seekset(error, filehandle, filehandle->pixel_data_offset)) { + return false; } - - // Header of Pixel Data Element - filehandle->first_frame_offset = 12; } return true; @@ -1031,9 +1040,7 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, return NULL; } - // load metadata around pixeldata, if we've not loaded it already - if (filehandle->offset_table == NULL && - !dcm_filehandle_read_pixeldata(error, filehandle)) { + if (!dcm_filehandle_read_pixeldata(error, filehandle)) { return NULL; } @@ -1081,8 +1088,7 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, dcm_log_debug("Read frame position (%u, %u)", column, row); // load metadata around pixeldata, if we've not loaded it already - if (filehandle->offset_table == NULL && - !dcm_filehandle_read_pixeldata(error, filehandle)) { + if (!dcm_filehandle_read_pixeldata(error, filehandle)) { return NULL; } diff --git a/tests/check_dicom.c b/tests/check_dicom.c index 586359d..59b758e 100644 --- a/tests/check_dicom.c +++ b/tests/check_dicom.c @@ -652,7 +652,7 @@ START_TEST(test_file_sm_image_file_meta) 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 @@ -667,7 +667,6 @@ START_TEST(test_file_sm_image_file_meta) dcm_dataset_print(meta, 0); - dcm_dataset_destroy(meta); dcm_filehandle_destroy(filehandle); } END_TEST @@ -682,7 +681,7 @@ START_TEST(test_file_sm_image_metadata) free(file_path); ck_assert_ptr_nonnull(filehandle); - DcmDataSet *metadata = dcm_filehandle_read_metadata(NULL, filehandle, NULL); + const DcmDataSet *metadata = dcm_filehandle_get_metadata(NULL, filehandle); ck_assert_ptr_nonnull(metadata); // SOP Class UID @@ -693,7 +692,6 @@ START_TEST(test_file_sm_image_metadata) dcm_dataset_print(metadata, 0); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); } END_TEST @@ -709,7 +707,7 @@ START_TEST(test_file_sm_image_frame) free(file_path); ck_assert_ptr_nonnull(filehandle); - DcmDataSet *metadata = dcm_filehandle_read_metadata(NULL, filehandle, NULL); + const DcmDataSet *metadata = dcm_filehandle_get_metadata(NULL, filehandle); ck_assert_ptr_nonnull(metadata); ck_assert_int_ne(dcm_filehandle_read_pixeldata(NULL, filehandle), 0); @@ -731,7 +729,6 @@ START_TEST(test_file_sm_image_frame) "1.2.840.10008.1.2.1"); dcm_frame_destroy(frame); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); } END_TEST @@ -750,7 +747,7 @@ START_TEST(test_file_sm_image_file_meta_memory) 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); @@ -764,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); } diff --git a/tools/dcm-getframe.c b/tools/dcm-getframe.c index 4f59e5a..d43628b 100644 --- a/tools/dcm-getframe.c +++ b/tools/dcm-getframe.c @@ -65,48 +65,6 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - dcm_log_info("Read metadata"); - DcmDataSet *metadata = dcm_filehandle_read_metadata(&error, - filehandle, - NULL); - if (metadata == NULL) { - dcm_error_print(error); - dcm_error_clear(&error); - dcm_filehandle_destroy(filehandle); - return EXIT_FAILURE; - } - - if (!dcm_filehandle_read_pixeldata(&error, filehandle)) { - dcm_error_print(error); - dcm_error_clear(&error); - dcm_filehandle_destroy(filehandle); - return EXIT_FAILURE; - } - - uint32_t tag = dcm_dict_tag_from_keyword("NumberOfFrames"); - DcmElement *element; - const char *value; - if (!(element = dcm_dataset_get(&error, metadata, tag)) || - !dcm_element_get_value_string(&error, element, 0, &value)) { - dcm_error_print(error); - dcm_error_clear(&error); - dcm_filehandle_destroy(filehandle); - return EXIT_FAILURE; - } - int num_frames = atoi(value); - - 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_print(error); - dcm_error_clear(&error); - 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, @@ -114,7 +72,6 @@ int main(int argc, char *argv[]) if (frame == NULL) { dcm_error_print(error); dcm_error_clear(&error); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; } @@ -150,7 +107,6 @@ int main(int argc, char *argv[]) dcm_error_print(error); dcm_error_clear(&error); dcm_frame_destroy(frame); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); return EXIT_FAILURE; } @@ -166,7 +122,6 @@ int main(int argc, char *argv[]) } dcm_frame_destroy(frame); - dcm_dataset_destroy(metadata); dcm_filehandle_destroy(filehandle); return EXIT_SUCCESS; From a6c5b3ea703f5e3fc764fa3b5401be03d130204d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 25 Jun 2023 14:29:07 +0100 Subject: [PATCH 55/82] polish docs, bump API to 1.0 --- doc/source/api.rst | 2 +- doc/source/conf.py | 3 +- doc/source/contributing.rst | 81 +++++++++++++++++++++------- doc/source/installation.rst | 12 +++-- doc/source/introduction.rst | 20 +++++-- doc/source/tools.rst | 22 +++++++- doc/source/usage.rst | 103 +++++++++++++++++++++--------------- include/dicom/dicom.h | 27 +++++----- meson.build | 2 +- 9 files changed, 181 insertions(+), 91 deletions(-) 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..27b40a9 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -22,7 +22,6 @@ copyright = '2021, Markus D. Herrmann' author = 'Markus D. Herrmann' - # -- General configuration --------------------------------------------------- primary_domain = 'c' @@ -57,7 +56,7 @@ # -- Hawkmoth extension ------------------------------------------------------ -cautodoc_root = os.path.abspath('../../include') +hawkmoth_root = os.path.abspath('../../include') readthedocs.clang_setup() if sys.platform == 'darwin': lib_search_dirs = [ 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..e619252 100644 --- a/doc/source/introduction.rst +++ b/doc/source/introduction.rst @@ -7,11 +7,15 @@ 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 ++++++++++++ @@ -19,10 +23,18 @@ Design goals The library inspires 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, 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 dca734e..0656d52 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()`. 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 and Per Frame Functional Group) can be read via -:c:func:`dcm_filehandle_read_metadata()`. - -In case the Data Set contained in a Part10 file represents an Image -instance, the remaining parts of the image metadata may be loaded with -:c:func:`dcm_filehandle_read_pixeldata()`. After this, individual frames -may be read out with :c:func:`dcm_filehandle_read_frame()`. - Thread safety +++++++++++++ @@ -196,22 +201,22 @@ 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, + 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. @@ -219,7 +224,7 @@ 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 +242,7 @@ printing it to standard output: return 1; } - DcmDataSet *metadata = dcm_filehandle_read_metadata(&error, filehandle); + const DcmDataSet *metadata = dcm_filehandle_get_metadata(&error, filehandle); if (metadata == NULL) { dcm_error_log(error); dcm_error_clear(&error); @@ -245,10 +250,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 76569b2..8d06edb 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -206,7 +206,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. @@ -713,7 +713,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 @@ -1557,8 +1557,8 @@ int64_t dcm_io_read(DcmError **error, /** * 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 + * 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. @@ -1634,8 +1634,8 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle); * implicit mode. * * The resturn result must not be destroyed. Make a clone of it with - * dcm_dataset_clone() if you need it to remain valid after closing the File - * handle. + * :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. @@ -1668,7 +1668,7 @@ const char *dcm_filehandle_get_transfer_syntax_uid(const DcmFilehandle *filehand * 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 dcm_dataset_destroy(). + * 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 @@ -1693,8 +1693,8 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, * to set various internal fields. * * The return result must not be destroyed. Make a clone of it with - * dcm_dataset_clone() if you need it to remain valid after closing the File - * handle. + * :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. @@ -1717,12 +1717,13 @@ const DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, * if present. * * This function will be called automatically on the first call to - * dcm_filehandle_read_frame_position() or dcm_filehandle_read_frame(). It can - * take some time to execute, so it is available as a separate function call - * in case this delay need to be managed. + * :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. + * positioned at the PixelData tag. * * It is safe to call this function many times. * diff --git a/meson.build b/meson.build index 7d1773c..4237e46 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,7 @@ project( ], license : 'MIT', meson_version : '>=0.50', - version : '0.5.0' + version : '1.0.0' ) if not meson.is_subproject() meson.add_dist_script( From 47d8cd91ffa06734b6a349be49110bbb0e5b3606 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 25 Jun 2023 14:34:40 +0100 Subject: [PATCH 56/82] TODO all done --- TODO | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 TODO diff --git a/TODO b/TODO deleted file mode 100644 index a3deaf6..0000000 --- a/TODO +++ /dev/null @@ -1,24 +0,0 @@ -# TODO - -- read metadata needs to define callbacks for pixeldata - -- note tag positions in the stop function - -- read metadata needs a set of stop tags - - DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, - DcmFilehandle *filehandle, - uint32_t *stop_tags) - - where stop_tags == NULL means stop set as now - -# 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 - From e8ace31e227d3f650bce569db1f44c6fcfe7ce4c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 25 Jun 2023 15:10:27 +0100 Subject: [PATCH 57/82] revise read_frame in case dcm_filehandle_read_pixeldata() has not been called yet --- include/dicom/dicom.h | 7 +++---- src/dicom-file.c | 9 ++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 8d06edb..a74106a 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1062,7 +1062,7 @@ DcmElement *dcm_dataset_get_clone(DcmError **error, DCM_EXTERN bool dcm_dataset_foreach(const DcmDataSet *dataset, bool (*fn)(const DcmElement *element, void *client), - void *client); + void *client); /** * Fetch a Data Element from a Data Set, or NULL if not present. @@ -1208,9 +1208,8 @@ DcmDataSet *dcm_sequence_get(DcmError **error, DCM_EXTERN bool dcm_sequence_foreach(const DcmSequence *seq, bool (*fn)(const DcmDataSet *dataset, - uint32_t index, - void *client), - void *client); + uint32_t index, void *client), + void *client); /** * Remove a Data Set item from a Sequence. diff --git a/src/dicom-file.c b/src/dicom-file.c index b56b47b..a814275 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -1026,6 +1026,10 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, { dcm_log_debug("Read frame number #%u.", frame_number); + if (!dcm_filehandle_read_pixeldata(error, filehandle)) { + return NULL; + } + if (frame_number == 0) { dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading Frame Item failed", @@ -1040,10 +1044,6 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, return NULL; } - if (!dcm_filehandle_read_pixeldata(error, filehandle)) { - return NULL; - } - // we are zero-based from here on uint32_t i = frame_number - 1; @@ -1087,7 +1087,6 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, { dcm_log_debug("Read frame position (%u, %u)", column, row); - // load metadata around pixeldata, if we've not loaded it already if (!dcm_filehandle_read_pixeldata(error, filehandle)) { return NULL; } From f100c8fd0b286938ede1c40155974e4a0970f69c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 25 Jun 2023 16:15:05 +0100 Subject: [PATCH 58/82] missing file_meta destroy --- src/dicom-file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dicom-file.c b/src/dicom-file.c index a814275..24beb67 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -181,6 +181,10 @@ void dcm_filehandle_destroy(DcmFilehandle *filehandle) dcm_dataset_destroy(filehandle->meta); } + if (filehandle->file_meta) { + dcm_dataset_destroy(filehandle->file_meta); + } + free(filehandle); } } From df5fee4070e4c393750ef5f58448c1c45b6826f9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 25 Jun 2023 20:06:26 +0100 Subject: [PATCH 59/82] bump ABI version to 1.x --- meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 4237e46..41be004 100644 --- a/meson.build +++ b/meson.build @@ -33,8 +33,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 = 5 +abi_version_major = 1 +abi_version_minor = 0 abi_version_patch = 0 abi_version = '@0@.@1@.@2@'.format( From e0e130f27a594ab0c0baa467fd622d899e5c9646 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 26 Jun 2023 05:11:48 +0100 Subject: [PATCH 60/82] small doc fixes --- README.md | 28 +++++++++++++++++++---- doc/source/usage.rst | 20 ++++++++-------- include/dicom/dicom.h | 53 +++++++++++++++---------------------------- tests/check_dicom.c | 6 ++--- 4 files changed, 53 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index ea61e0e..3da9132 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,36 @@ 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 +``` + ## 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 and under Contract +No. HHSN261201500003l. diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 0656d52..49e9678 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -146,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. @@ -164,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; } @@ -218,7 +218,7 @@ other programs, for example, arrays of numeric values. 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 +++++++++++++++ @@ -252,9 +252,9 @@ printing an element to standard output: const char *num_frames; uint32_t tag = dcm_dict_tag_from_keyword("NumberOfFrames"); - DcmElement *element = dcm_dataset_get(error, metadata, tag); + DcmElement *element = dcm_dataset_get(&error, metadata, tag); if (element == NULL || - !dcm_element_get_value_string(error, element, 0, &num_frames)) { + !dcm_element_get_value_string(&error, element, 0, &num_frames)) { dcm_error_log(error); dcm_error_clear(&error); dcm_filehandle_destroy(filehandle); diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index a74106a..8fba26e 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -915,7 +915,6 @@ bool dcm_element_get_value_sequence(DcmError **error, const DcmElement *element, DcmSequence **value); - /** * Set the value of a Data Element to a Sequence. * @@ -1102,7 +1101,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); /** @@ -1474,16 +1474,10 @@ struct _DcmIOMethods { 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); + 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); + int64_t (*seek)(DcmError **error, DcmIO *io, int64_t offset, int whence); }; /** @@ -1509,8 +1503,7 @@ DcmIO *dcm_io_create(DcmError **error, * :return: IO object */ DCM_EXTERN -DcmIO *dcm_io_create_from_file(DcmError **error, - const char *filename); +DcmIO *dcm_io_create_from_file(DcmError **error, const char *filename); /** * Open an area of memory for IO. @@ -1522,8 +1515,7 @@ DcmIO *dcm_io_create_from_file(DcmError **error, * :return: IO object */ DCM_EXTERN -DcmIO *dcm_io_create_from_memory(DcmError **error, - const char *buffer, +DcmIO *dcm_io_create_from_memory(DcmError **error, const char *buffer, int64_t length); /** @@ -1548,10 +1540,7 @@ void dcm_io_close(DcmIO *io); * :return: Number of bytes read */ DCM_EXTERN -int64_t dcm_io_read(DcmError **error, - DcmIO *io, - char *buffer, - int64_t length); +int64_t dcm_io_read(DcmError **error, DcmIO *io, char *buffer, int64_t length); /** * Seek an IO object. @@ -1570,13 +1559,10 @@ int64_t dcm_io_read(DcmError **error, * :return: New read position */ DCM_EXTERN -int64_t dcm_io_seek(DcmError **error, - DcmIO *io, - int64_t offset, - int whence); +int64_t dcm_io_seek(DcmError **error, DcmIO *io, int64_t offset, int whence); /** - * Create a representatiopn of a DICOM File using an IO object. + * Create a representation of a DICOM File using an IO object. * * The File object tracks information like the transfer syntax and the byte * ordering. @@ -1614,7 +1600,7 @@ DcmFilehandle *dcm_filehandle_create_from_file(DcmError **error, DCM_EXTERN DcmFilehandle *dcm_filehandle_create_from_memory(DcmError **error, const char *buffer, - int64_t length); + int64_t length); /** * Destroy a Filehandle. @@ -1627,12 +1613,10 @@ 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. A pointer - * to libdicom's copy of the File Meta Information is returned. The File Meta - * Information is used to set the Transfer Syntax and enable or disable - * implicit mode. + * Reads the File Meta Information and saves it in the File handle. Returns a + * reference to this internal copy of the File Meta Information. * - * The resturn result must not be destroyed. Make a clone of it with + * 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. * @@ -1671,7 +1655,7 @@ const char *dcm_filehandle_get_transfer_syntax_uid(const DcmFilehandle *filehand * * After calling this function, the filehandle read point is always * positioned at the tag that stopped the read. You can call this function - * with a different stop set to read more of the metadata. + * again with a different stop set to read more of the metadata. * * :param error: Pointer to error object * :param filehandle: File @@ -1687,9 +1671,8 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, /** * Get metadata from a File. * - * Gets the File's metadata and saves it in the File handle. A pointer - * to libdicom's copy of the File metadata is returned. The metadata is used - * to set various internal fields. + * Gets the File's metadata and saves it in the File handle. Returns a + * reference to this internal copy of the 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 @@ -1754,9 +1737,9 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, /** * Read the frame at a position in a File. * - * Read a tile from a File at a specified (column, row), numbered from zero. + * Read a frame from a File at a specified (column, row), numbered from zero. * This takes account of any frame positioning given in - * PerFrameFunctionalGroupSequence, if necessary. + * PerFrameFunctionalGroupSequence. * * :param error: Pointer to error object * :param filehandle: File diff --git a/tests/check_dicom.c b/tests/check_dicom.c index 59b758e..07e2627 100644 --- a/tests/check_dicom.c +++ b/tests/check_dicom.c @@ -505,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); @@ -533,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); From 5a506b0c121931b4299ddc7bd4f837eb1b526246 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 26 Jun 2023 05:41:43 +0100 Subject: [PATCH 61/82] fix sphinx build it was not finding bool --- doc/source/conf.py | 3 +++ doc/source/usage.rst | 8 ++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 27b40a9..707da65 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 ----------------------------------------------------- @@ -58,6 +59,8 @@ hawkmoth_root = os.path.abspath('../../include') readthedocs.clang_setup() +hawkmoth_clang = compiler.get_include_args() +hawkmoth_clang.append(f"-I{os.path.abspath('../../build')}") if sys.platform == 'darwin': lib_search_dirs = [ '/usr/lib', diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 49e9678..40e8fc1 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -207,15 +207,11 @@ other programs, for example, arrays of numeric values. 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 +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, libdicom will make a copy of the array. From 057c4ec98e0f3b4e4654d878ca6625a56f0e39da Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 26 Jun 2023 13:48:52 +0100 Subject: [PATCH 62/82] add dcm_calloc / dcm_free to public API The python binding needs them. --- doc/source/conf.py | 1 + include/dicom/dicom.h | 28 ++++++++++++++++++++++++++++ src/dicom.c | 16 ++++++++-------- src/pdicom.h | 3 +-- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 707da65..50fcdc2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -60,6 +60,7 @@ 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 = [ diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 8fba26e..8e961b1 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -258,6 +258,34 @@ 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 */ diff --git a/src/dicom.c b/src/dicom.c index 0ab1678..30a0d9b 100644 --- a/src/dicom.c +++ b/src/dicom.c @@ -24,9 +24,9 @@ #include "pdicom.h" -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 +34,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,7 +48,7 @@ void *dcm_calloc(DcmError **error, size_t n, size_t size) } -void *dcm_realloc(DcmError **error, void *ptr, size_t size) +void *dcm_realloc(DcmError **error, void *ptr, uint64_t size) { void *result = realloc(ptr, size); if (!result) { @@ -99,7 +99,7 @@ char *dcm_printf_append(char *str, const char *format, ...) } } 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) { @@ -189,7 +189,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; @@ -208,7 +208,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) { @@ -231,7 +231,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); diff --git a/src/pdicom.h b/src/pdicom.h index 46f3c76..01014fe 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -55,8 +55,7 @@ typedef SSIZE_T ssize_t; #define TAG_ITEM_DELIM 0xFFFEE00D #define TAG_SQ_DELIM 0xFFFEE0DD -void *dcm_calloc(DcmError **error, size_t n, size_t size); -void *dcm_realloc(DcmError **error, void *ptr, size_t size); +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, ...); From 035ce756c16fc7432b8943739fbe28fc61e9ee76 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 26 Jun 2023 13:52:30 +0100 Subject: [PATCH 63/82] implement dcm_free() --- src/dicom.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dicom.c b/src/dicom.c index 30a0d9b..bc8d945 100644 --- a/src/dicom.c +++ b/src/dicom.c @@ -23,6 +23,11 @@ #include #include "pdicom.h" +// we need a namedspaced free for language bindings +void dcm_free(void *pointer) +{ + free(pointer); +} void *dcm_calloc(DcmError **error, uint64_t n, uint64_t size) { From 2cead3330e0fe7b9d4e970781525f03ecf2867ff Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 26 Jun 2023 22:43:55 +0100 Subject: [PATCH 64/82] better printing of binary values --- src/dicom-data.c | 24 +++++++++++++++++++----- src/dicom-dict.c | 2 +- src/dicom-file.c | 41 +++++++++-------------------------------- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index 64ba09c..4e82f51 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -925,8 +925,7 @@ bool dcm_element_set_value(DcmError **error, { size_t size; - switch (dcm_dict_vr_class(element->vr)) - { + switch (dcm_dict_vr_class(element->vr)) { case DCM_CLASS_STRING_SINGLE: case DCM_CLASS_STRING_MULTI: if (!dcm_element_set_value_string(error, element, value, steal)) { @@ -1187,12 +1186,14 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) char *dcm_element_value_to_string(const DcmElement *element) { DcmVRClass klass = 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; + uint32_t n; if (element->vm > 1) { result = dcm_printf_append(result, "["); @@ -1233,12 +1234,25 @@ char *dcm_element_value_to_string(const DcmElement *element) break; case DCM_CLASS_BINARY: - result = dcm_printf_append(result, - "", - dcm_element_get_length(element)); + (void) dcm_element_get_value_binary(NULL, element, &str); + n = MIN(16, dcm_element_get_length(element)); + + for (i = 0; i < n; i++) { + result = dcm_printf_append(result, "%02x", str[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_CLASS_SEQUENCE: + result = dcm_printf_append(result, ""); + break; + default: dcm_log_warning("Unexpected Value Representation."); } diff --git a/src/dicom-dict.c b/src/dicom-dict.c index 7f73484..b548ade 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -68,7 +68,7 @@ static const struct _DcmVRTable vr_table[] = { {DCM_VR_LT, "LT", DCM_CLASS_STRING_SINGLE, 0, DCM_CAPACITY_LT, 2}, - {DCM_VR_OB, "OB", DCM_CLASS_BINARY, 0, 0, 4}, + {DCM_VR_OB, "OB", DCM_CLASS_BINARY, 1, 0, 4}, {DCM_VR_OD, "OD", DCM_CLASS_BINARY, 8, 0, 4}, {DCM_VR_OF, "OF", DCM_CLASS_BINARY, 4, 0, 4}, diff --git a/src/dicom-file.c b/src/dicom-file.c index 24beb67..64a4f11 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -1322,40 +1322,17 @@ static bool print_pixeldata_create(DcmError **error, printf("| %u | ", length); - switch (size) { - default: - case 1: - for (uint32_t i = 0; i < MIN(length, 17); i++) { - printf("%02x ", ((uint8_t *)value)[i]); - } - - if (length > 17) { - printf("..."); - } - - break; - - case 2: - for (uint32_t i = 0; i < MIN(length, 10); i++) { - printf("%04x ", ((uint16_t *)value)[i]); - } - - if (length > 10) { - printf("..."); - } - - break; - - case 4: - for (uint32_t i = 0; i < MIN(length, 6); i++) { - printf("%08x ", ((uint32_t *)value)[i]); - } + uint32_t n = MIN(16, length); + for (uint32_t i = 0; i < n; i++) { + printf("%02x", value[i]); - if (length > 6) { - printf("..."); - } + if (i % size == size - 1) { + printf(" "); + } + } - break; + if (length > 16) { + printf("..."); } printf("\n"); From 45a6c0e0a0549c691b712d6d129bcea842156dff Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 28 Jun 2023 12:37:59 +0100 Subject: [PATCH 65/82] print vm as well as length We were just printing length. --- src/dicom-data.c | 2 +- src/dicom-file.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index 4e82f51..e3c4a68 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -1322,7 +1322,7 @@ void dcm_element_print(const DcmElement *element, int indentation) num_indent, " "); } else { - printf(" | %u | ", element->length); + printf(" | %u | %u | ", element->length, element->vm); char *str = dcm_element_value_to_string(element); if (str != NULL) { printf("%s\n", str); diff --git a/src/dicom-file.c b/src/dicom-file.c index 64a4f11..1838ebb 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -1289,7 +1289,7 @@ static bool print_element_create(DcmError **error, char *str; if (dcm_element_set_value(NULL, element, value, length, false) && (str = dcm_element_value_to_string(element))) { - printf("| %s\n", str); + printf("| %u | %s\n", dcm_element_get_vm(element), str); free(str); } From ac968b8a156dbde205191a147ec0ff851913ba04 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 28 Jun 2023 14:51:55 +0100 Subject: [PATCH 66/82] add link to py binding --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3da9132..8583e4b 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,22 @@ dcm-dump data/test_files/sm_image.dcm dcm-getframe -o tile.raw data/test_files/sm_image.dcm 12 ``` +### From Python + +There's a sample Python binding using cffi here: + +https://github.com/jcupitt/pywsidicom + ## Documentation User and developer guides as well as API documentation can be found at [libdicom.readthedocs.io](https://libdicom.readthedocs.io/en/latest/). -## Thanks +# 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. -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 and under Contract -No. HHSN261201500003l. From b12d43c9f4c98ef03c868ed5483e795f4c1c059c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 28 Jun 2023 17:51:43 +0100 Subject: [PATCH 67/82] update pywsidicom link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8583e4b..51ba947 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ dcm-getframe -o tile.raw data/test_files/sm_image.dcm 12 ### From Python -There's a sample Python binding using cffi here: +There's a sample Python binding here: -https://github.com/jcupitt/pywsidicom +https://github.com/ImagingDataCommons/pywsidicom ## Documentation From f4355f4a51147eee81827ff2355c73742322a1f4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 8 Jul 2023 10:57:29 +0100 Subject: [PATCH 68/82] make binary values void* We were using char* --- include/dicom/dicom.h | 9 ++++----- src/dicom-data.c | 12 +++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index 8e961b1..a6f650a 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -884,14 +884,13 @@ bool dcm_element_set_value_decimal(DcmError **error, 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 @@ -908,7 +907,7 @@ 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); diff --git a/src/dicom-data.c b/src/dicom-data.c index e3c4a68..124253b 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -861,7 +861,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)) { @@ -876,7 +876,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) { @@ -888,7 +888,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; } @@ -1193,6 +1193,7 @@ char *dcm_element_value_to_string(const DcmElement *element) double d; int64_t i; const char *str; + const void *val; uint32_t n; if (element->vm > 1) { @@ -1234,11 +1235,12 @@ char *dcm_element_value_to_string(const DcmElement *element) break; case DCM_CLASS_BINARY: - (void) dcm_element_get_value_binary(NULL, element, &str); + (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", str[i]); + result = dcm_printf_append(result, "%02x", + ((unsigned char *) val)[i]); if (i % size == size - 1) { result = dcm_printf_append(result, " "); } From 25da0e342758538685bf018185929a0055bc57e4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 9 Jul 2023 12:35:20 +0100 Subject: [PATCH 69/82] fix UC parsing --- include/dicom/dicom.h | 5 +++++ src/dicom-dict.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index a6f650a..e367ba1 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -95,6 +95,11 @@ */ #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. */ diff --git a/src/dicom-dict.c b/src/dicom-dict.c index b548ade..f8305ab 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -99,7 +99,7 @@ static const struct _DcmVRTable vr_table[] = { {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_UC, "UC", DCM_CLASS_BINARY, 0, 0, 4}, + {DCM_VR_UC, "UC", DCM_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}, From 02796530cccef52f5043c0642e9af919b82b2e0b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 9 Jul 2023 14:25:07 +0100 Subject: [PATCH 70/82] rename klass as vr_class --- src/dicom-data.c | 44 +++++++++++++++++++++++--------------------- src/dicom-dict.c | 6 +++--- src/dicom-parse.c | 12 ++++++------ 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index 124253b..82b2d42 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -271,8 +271,9 @@ 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_CLASS_STRING_MULTI && + vr_class != DCM_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", @@ -372,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; @@ -387,8 +388,8 @@ static bool dcm_element_validate(DcmError **error, DcmElement *element) return false; } - if (klass == DCM_CLASS_NUMERIC_DECIMAL || - klass == DCM_CLASS_NUMERIC_INTEGER) { + if (vr_class == DCM_CLASS_NUMERIC_DECIMAL || + vr_class == DCM_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", @@ -398,7 +399,8 @@ static bool dcm_element_validate(DcmError **error, DcmElement *element) } } - if (klass == DCM_CLASS_STRING_MULTI || klass == DCM_CLASS_STRING_SINGLE) { + if (vr_class == DCM_CLASS_STRING_MULTI || + vr_class == DCM_CLASS_STRING_SINGLE) { uint32_t capacity = dcm_dict_vr_capacity(element->vr); if (!element_check_capacity(error, element, capacity)) { return false; @@ -445,8 +447,8 @@ 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_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", @@ -547,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_CLASS_STRING_MULTI) { uint32_t vm; char **values = dcm_parse_character_string(error, value, &vm); if (values == NULL) { @@ -626,9 +628,9 @@ 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_DECIMAL && - klass != DCM_CLASS_NUMERIC_INTEGER) { + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + if (vr_class != DCM_CLASS_NUMERIC_DECIMAL && + vr_class != DCM_CLASS_NUMERIC_INTEGER) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not numeric", "Element tag %08x is not numeric", @@ -846,8 +848,8 @@ bool dcm_element_set_value_decimal(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_CLASS_BINARY) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not binary", "Element tag %08x does not have a binary value", @@ -979,8 +981,8 @@ bool dcm_element_set_value(DcmError **error, 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_CLASS_SEQUENCE) { dcm_error_set(error, DCM_ERROR_CODE_INVALID, "Data Element is not seeuence", "Element tag %08x does not have a seeuence value", @@ -1058,8 +1060,8 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) } clone->length = element->length; - DcmVRClass klass = dcm_dict_vr_class(element->vr); - switch (klass) { + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); + switch (vr_class) { case DCM_CLASS_SEQUENCE: if (!dcm_element_get_value_sequence(error, element, &from_seq)) { dcm_element_destroy(clone); @@ -1185,7 +1187,7 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) char *dcm_element_value_to_string(const DcmElement *element) { - DcmVRClass klass = dcm_dict_vr_class(element->vr); + DcmVRClass vr_class = dcm_dict_vr_class(element->vr); size_t size = dcm_dict_vr_size(element->vr); char *result = NULL; @@ -1201,7 +1203,7 @@ char *dcm_element_value_to_string(const DcmElement *element) } for (uint32_t index = 0; index < element->vm; index++) { - switch (klass) { + switch (vr_class) { case DCM_CLASS_NUMERIC_DECIMAL: (void) dcm_element_get_value_decimal(NULL, element, diff --git a/src/dicom-dict.c b/src/dicom-dict.c index f8305ab..27eecd7 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; @@ -5209,7 +5209,7 @@ 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; diff --git a/src/dicom-parse.c b/src/dicom-parse.c index a1ae858..d5b8ccf 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -562,7 +562,7 @@ static bool parse_element_body(DcmParseState *state, uint32_t length, int64_t *position) { - DcmVRClass klass = dcm_dict_vr_class(vr); + DcmVRClass vr_class = dcm_dict_vr_class(vr); size_t size = dcm_dict_vr_size(vr); char *value; @@ -580,14 +580,14 @@ static bool parse_element_body(DcmParseState *state, dcm_log_debug("Read Data Element body '%08x'", tag); - switch (klass) { + switch (vr_class) { case DCM_CLASS_STRING_SINGLE: case DCM_CLASS_STRING_MULTI: case DCM_CLASS_NUMERIC_DECIMAL: case DCM_CLASS_NUMERIC_INTEGER: case DCM_CLASS_BINARY: - if (klass == DCM_CLASS_NUMERIC_DECIMAL || - klass == DCM_CLASS_NUMERIC_INTEGER) { + if (vr_class == DCM_CLASS_NUMERIC_DECIMAL || + vr_class == DCM_CLASS_NUMERIC_INTEGER) { // all numeric classes have a size if (length % size != 0) { dcm_error_set(state->error, DCM_ERROR_CODE_PARSE, @@ -625,8 +625,8 @@ static bool parse_element_body(DcmParseState *state, } } - if (klass == DCM_CLASS_NUMERIC_DECIMAL || - klass == DCM_CLASS_NUMERIC_INTEGER) { + if (vr_class == DCM_CLASS_NUMERIC_DECIMAL || + vr_class == DCM_CLASS_NUMERIC_INTEGER) { if (state->big_endian) { byteswap(value, length, size); } From 3a0617809e19530ed258aa8032a8060c11bef964 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 9 Jul 2023 14:45:05 +0100 Subject: [PATCH 71/82] make element_check_integer also check numeric --- src/dicom-data.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index 82b2d42..5cb7bbe 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -645,6 +645,9 @@ 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", @@ -663,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; @@ -688,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; } From dfd6b07ad170cc70b8fb472f5bc66fbea9fca55f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 15 Jul 2023 13:37:35 +0100 Subject: [PATCH 72/82] fix byteswapping ooops, two silly errors --- src/dicom-parse.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/dicom-parse.c b/src/dicom-parse.c index d5b8ccf..d4f03ee 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -134,28 +134,28 @@ static bool is_big_endian(void) #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) \ + (((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) \ + (((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) { + if (length >= size && length % size == 0) { size_t n_elements = length / size; switch (size) { From 2632133fd49a9ca6df7141b1b09b3f1094ff21a9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 15 Jul 2023 14:26:47 +0100 Subject: [PATCH 73/82] grammar fix --- doc/source/introduction.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/introduction.rst b/doc/source/introduction.rst index e619252..eeb2262 100644 --- a/doc/source/introduction.rst +++ b/doc/source/introduction.rst @@ -20,12 +20,12 @@ 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 From f51a0c817acc60e38417519ea60283bb65ff3391 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 16 Jul 2023 12:37:34 +0100 Subject: [PATCH 74/82] revise codespell workflow --- .github/workflows/codespell.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) 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: From 8fefded9050b2323edf0904daab172ec3936dbee Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 23 Jul 2023 08:58:25 -0400 Subject: [PATCH 75/82] meson: apply `bin` tag to tools Meson >= 0.60 lets users filter the installed files with `meson install --tags`. Tell Meson that the tools are "executables bundled with a library meant to be used by end users". Older Meson versions complain about the install_tags argument but don't fail: WARNING: Passed invalid keyword argument "install_tag" --- meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meson.build b/meson.build index 41be004..1d0289d 100644 --- a/meson.build +++ b/meson.build @@ -190,12 +190,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( From 679ab2a871c5f0db2d4ad8efd32b733edcb5f591 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 23 Jul 2023 12:27:14 -0400 Subject: [PATCH 76/82] Fix typos --- include/dicom/dicom.h | 2 +- src/dicom-parse.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index e367ba1..dfbb19a 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -461,7 +461,7 @@ typedef enum _DcmVR { * DCM_CLASS_BINARY -- an uninterpreted array of bytes, length in the * element header * - * DCM_CLASS_SEQUENCE -- Value Representation is a seqeunce + * DCM_CLASS_SEQUENCE -- Value Representation is a sequence */ typedef enum _DcmVRClass { DCM_CLASS_ERROR, diff --git a/src/dicom-parse.c b/src/dicom-parse.c index d4f03ee..5c35038 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -263,7 +263,7 @@ static bool parse_element_header(DcmParseState *state, } if (state->implicit) { - // this can be an ambiguious VR, eg. pixeldata is allowed in 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) { From cb97fa958b0b38cb3d13e31dbb4b46854e7fee92 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 28 Jul 2023 10:18:35 +0100 Subject: [PATCH 77/82] Update doc/source/usage.rst Co-authored-by: Markus D. Herrmann --- doc/source/usage.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 548fd70..8a11f8b 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -36,9 +36,9 @@ A `Data Element 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 +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()`. From 414dec5624bc56063ce49d1bc84d18ef1cf3d14b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 28 Jul 2023 10:30:03 +0100 Subject: [PATCH 78/82] rename DCM_CLASS_ as DCM_VR_CLASS_ --- README.md | 2 +- include/dicom/dicom.h | 26 ++++++++-------- src/dicom-data.c | 60 ++++++++++++++++++------------------- src/dicom-dict.c | 70 +++++++++++++++++++++---------------------- src/dicom-parse.c | 20 ++++++------- 5 files changed, 89 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 51ba947..bdb227c 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ dcm-getframe -o tile.raw data/test_files/sm_image.dcm 12 There's a sample Python binding here: -https://github.com/ImagingDataCommons/pywsidicom +https://github.com/ImagingDataCommons/pylibdicom ## Documentation diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index dfbb19a..bafd194 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -446,31 +446,31 @@ typedef enum _DcmVR { /** * The general class of the value associated with a Value Representation. * - * DCM_CLASS_STRING_MULTI -- one or more null-terminated strings, cannot + * DCM_VR_CLASS_STRING_MULTI -- one or more null-terminated strings, cannot * contain backslash * - * DCM_CLASS_STRING_SINGLE -- a single null-terminated string, backslash + * DCM_VR_CLASS_STRING_SINGLE -- a single null-terminated string, backslash * allowed * - * DCM_CLASS_NUMERIC_DECIMAL -- one or more binary floating point numeric + * DCM_VR_CLASS_NUMERIC_DECIMAL -- one or more binary floating point numeric * values, other fields give sizeof(type) * - * DCM_CLASS_NUMERIC_INTEGER -- one or more binary integer numeric + * DCM_VR_CLASS_NUMERIC_INTEGER -- one or more binary integer numeric * values, other fields give sizeof(type) * - * DCM_CLASS_BINARY -- an uninterpreted array of bytes, length in the + * DCM_VR_CLASS_BINARY -- an uninterpreted array of bytes, length in the * element header * - * DCM_CLASS_SEQUENCE -- Value Representation is a sequence + * DCM_VR_CLASS_SEQUENCE -- Value Representation is a sequence */ typedef enum _DcmVRClass { - DCM_CLASS_ERROR, - DCM_CLASS_STRING_MULTI, - DCM_CLASS_STRING_SINGLE, - DCM_CLASS_NUMERIC_DECIMAL, - DCM_CLASS_NUMERIC_INTEGER, - DCM_CLASS_BINARY, - DCM_CLASS_SEQUENCE + 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; /** diff --git a/src/dicom-data.c b/src/dicom-data.c index 5cb7bbe..d18ffc9 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -272,8 +272,8 @@ static bool element_check_string(DcmError **error, const DcmElement *element) { DcmVRClass vr_class = dcm_dict_vr_class(element->vr); - if (vr_class != DCM_CLASS_STRING_MULTI && - vr_class != DCM_CLASS_STRING_SINGLE) { + 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", @@ -388,8 +388,8 @@ static bool dcm_element_validate(DcmError **error, DcmElement *element) return false; } - if (vr_class == DCM_CLASS_NUMERIC_DECIMAL || - vr_class == DCM_CLASS_NUMERIC_INTEGER) { + 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", @@ -399,8 +399,8 @@ static bool dcm_element_validate(DcmError **error, DcmElement *element) } } - if (vr_class == DCM_CLASS_STRING_MULTI || - vr_class == 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; @@ -448,7 +448,7 @@ bool dcm_element_set_value_string_multi(DcmError **error, } } else { DcmVRClass vr_class = dcm_dict_vr_class(element->vr); - if (vr_class != DCM_CLASS_STRING_MULTI) { + 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", @@ -550,7 +550,7 @@ bool dcm_element_set_value_string(DcmError **error, } DcmVRClass vr_class = dcm_dict_vr_class(element->vr); - if (vr_class == DCM_CLASS_STRING_MULTI) { + if (vr_class == DCM_VR_CLASS_STRING_MULTI) { uint32_t vm; char **values = dcm_parse_character_string(error, value, &vm); if (values == NULL) { @@ -629,8 +629,8 @@ static bool element_check_numeric(DcmError **error, const DcmElement *element) { DcmVRClass vr_class = dcm_dict_vr_class(element->vr); - if (vr_class != DCM_CLASS_NUMERIC_DECIMAL && - vr_class != DCM_CLASS_NUMERIC_INTEGER) { + 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", @@ -850,7 +850,7 @@ static bool element_check_binary(DcmError **error, const DcmElement *element) { DcmVRClass vr_class = dcm_dict_vr_class(element->vr); - if (vr_class != DCM_CLASS_BINARY) { + 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", @@ -929,15 +929,15 @@ bool dcm_element_set_value(DcmError **error, size_t size; switch (dcm_dict_vr_class(element->vr)) { - case DCM_CLASS_STRING_SINGLE: - case DCM_CLASS_STRING_MULTI: + 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_CLASS_NUMERIC_DECIMAL: - case DCM_CLASS_NUMERIC_INTEGER: + 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, @@ -954,7 +954,7 @@ bool dcm_element_set_value(DcmError **error, } break; - case DCM_CLASS_BINARY: + case DCM_VR_CLASS_BINARY: if (!dcm_element_set_value_binary(error, element, value, @@ -965,7 +965,7 @@ bool dcm_element_set_value(DcmError **error, break; - case DCM_CLASS_SEQUENCE: + case DCM_VR_CLASS_SEQUENCE: default: dcm_error_set(error, DCM_ERROR_CODE_PARSE, "Reading of Data Element failed", @@ -983,7 +983,7 @@ static bool element_check_sequence(DcmError **error, const DcmElement *element) { DcmVRClass vr_class = dcm_dict_vr_class(element->vr); - if (vr_class != DCM_CLASS_SEQUENCE) { + 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", @@ -1063,7 +1063,7 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) DcmVRClass vr_class = dcm_dict_vr_class(element->vr); switch (vr_class) { - case DCM_CLASS_SEQUENCE: + case DCM_VR_CLASS_SEQUENCE: if (!dcm_element_get_value_sequence(error, element, &from_seq)) { dcm_element_destroy(clone); return NULL; @@ -1099,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, @@ -1134,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) { @@ -1149,8 +1149,8 @@ DcmElement *dcm_element_clone(DcmError **error, const DcmElement *element) } break; - case DCM_CLASS_NUMERIC_DECIMAL: - case DCM_CLASS_NUMERIC_INTEGER: + case DCM_VR_CLASS_NUMERIC_DECIMAL: + case DCM_VR_CLASS_NUMERIC_INTEGER: if (element->vm == 1) { clone->value = element->value; clone->vm = 1; @@ -1205,7 +1205,7 @@ char *dcm_element_value_to_string(const DcmElement *element) for (uint32_t index = 0; index < element->vm; index++) { switch (vr_class) { - case DCM_CLASS_NUMERIC_DECIMAL: + case DCM_VR_CLASS_NUMERIC_DECIMAL: (void) dcm_element_get_value_decimal(NULL, element, index, @@ -1213,7 +1213,7 @@ char *dcm_element_value_to_string(const DcmElement *element) result = dcm_printf_append(result, "%g", d); break; - case DCM_CLASS_NUMERIC_INTEGER: + case DCM_VR_CLASS_NUMERIC_INTEGER: (void) dcm_element_get_value_integer(NULL, element, index, @@ -1228,8 +1228,8 @@ char *dcm_element_value_to_string(const DcmElement *element) } break; - case DCM_CLASS_STRING_SINGLE: - case DCM_CLASS_STRING_MULTI: + case DCM_VR_CLASS_STRING_SINGLE: + case DCM_VR_CLASS_STRING_MULTI: (void) dcm_element_get_value_string(NULL, element, index, @@ -1237,7 +1237,7 @@ char *dcm_element_value_to_string(const DcmElement *element) result = dcm_printf_append(result, "%s", str); break; - case DCM_CLASS_BINARY: + case DCM_VR_CLASS_BINARY: (void) dcm_element_get_value_binary(NULL, element, &val); n = MIN(16, dcm_element_get_length(element)); @@ -1254,7 +1254,7 @@ char *dcm_element_value_to_string(const DcmElement *element) } break; - case DCM_CLASS_SEQUENCE: + case DCM_VR_CLASS_SEQUENCE: result = dcm_printf_append(result, ""); break; diff --git a/src/dicom-dict.c b/src/dicom-dict.c index 27eecd7..05d7fad 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -52,60 +52,60 @@ struct _DcmVRTable_hash_entry { * 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_SINGLE, 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_SINGLE, 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_STRING_MULTI, 0, 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_DECIMAL, 4, 0, 2}, - {DCM_VR_FD, "FD", DCM_CLASS_NUMERIC_DECIMAL, 8, 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, 1, 0, 4}, + {DCM_VR_OB, "OB", DCM_VR_CLASS_BINARY, 1, 0, 4}, - {DCM_VR_OD, "OD", DCM_CLASS_BINARY, 8, 0, 4}, - {DCM_VR_OF, "OF", DCM_CLASS_BINARY, 4, 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, 2, 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_INTEGER, 4, 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_INTEGER, 2, 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_INTEGER, 4, 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_INTEGER, 2, 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_STRING_MULTI, 0, DCM_CAPACITY_UC, 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_INTEGER, 8, 0, 4}, - {DCM_VR_UV, "UV", DCM_CLASS_NUMERIC_INTEGER, 8, 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); @@ -5212,7 +5212,7 @@ DcmVRClass dcm_dict_vr_class(DcmVR vr) return vr_table[(int)vr].vr_class; } - return DCM_CLASS_ERROR; + return DCM_VR_CLASS_ERROR; } diff --git a/src/dicom-parse.c b/src/dicom-parse.c index 5c35038..c90bd68 100644 --- a/src/dicom-parse.c +++ b/src/dicom-parse.c @@ -581,13 +581,13 @@ static bool parse_element_body(DcmParseState *state, dcm_log_debug("Read Data Element body '%08x'", tag); switch (vr_class) { - case DCM_CLASS_STRING_SINGLE: - case DCM_CLASS_STRING_MULTI: - case DCM_CLASS_NUMERIC_DECIMAL: - case DCM_CLASS_NUMERIC_INTEGER: - case DCM_CLASS_BINARY: - if (vr_class == DCM_CLASS_NUMERIC_DECIMAL || - vr_class == DCM_CLASS_NUMERIC_INTEGER) { + 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, @@ -625,8 +625,8 @@ static bool parse_element_body(DcmParseState *state, } } - if (vr_class == DCM_CLASS_NUMERIC_DECIMAL || - vr_class == DCM_CLASS_NUMERIC_INTEGER) { + if (vr_class == DCM_VR_CLASS_NUMERIC_DECIMAL || + vr_class == DCM_VR_CLASS_NUMERIC_INTEGER) { if (state->big_endian) { byteswap(value, length, size); } @@ -651,7 +651,7 @@ static bool parse_element_body(DcmParseState *state, break; - case DCM_CLASS_SEQUENCE: + case DCM_VR_CLASS_SEQUENCE: if (length == 0xFFFFFFFF) { dcm_log_debug("Sequence of Data Element '%08x' " "has undefined length.", From 38fce1e120bf88e9ea067fccf97c0f75bf26006f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 1 Aug 2023 15:39:55 +0100 Subject: [PATCH 79/82] renames following review rename dcm_filehandle_read_pixeldata() as dcm_filehandle_prepare_read_frame() rename dcm_filehandle_get_metadata() as dcm_filehandle_get_metadata_subset() revise docs and tests --- doc/source/usage.rst | 7 ++++--- include/dicom/dicom.h | 18 +++++++++++------- src/dicom-file.c | 14 +++++++------- tests/check_dicom.c | 8 +++++--- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 8a11f8b..fbc56c7 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -19,8 +19,8 @@ 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()`. This function will stop read on tags -which are likely to take a long time to process. +: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()`. @@ -238,7 +238,8 @@ printing an element to standard output: return 1; } - const DcmDataSet *metadata = dcm_filehandle_get_metadata(&error, filehandle); + const DcmDataSet *metadata = + dcm_filehandle_get_metadata_subset(&error, filehandle); if (metadata == NULL) { dcm_error_log(error); dcm_error_clear(&error); diff --git a/include/dicom/dicom.h b/include/dicom/dicom.h index bafd194..842cf94 100644 --- a/include/dicom/dicom.h +++ b/include/dicom/dicom.h @@ -1701,10 +1701,14 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, const uint32_t *stop_tags); /** - * Get metadata from a File. + * Get a fast subset of metadata from a File. * - * Gets the File's metadata and saves it in the File handle. Returns a - * reference to this internal copy of the File metadata. + * 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 @@ -1721,8 +1725,8 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, * :return: metadata */ DCM_EXTERN -const DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, - DcmFilehandle *filehandle); +const DcmDataSet *dcm_filehandle_get_metadata_subset(DcmError **error, + DcmFilehandle *filehandle); /** * Read everything necessary to fetch frames from the file. @@ -1747,8 +1751,8 @@ const DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, * :return: true on success */ DCM_EXTERN -bool dcm_filehandle_read_pixeldata(DcmError **error, - DcmFilehandle *filehandle); +bool dcm_filehandle_prepare_read_frame(DcmError **error, + DcmFilehandle *filehandle); /** * Read an individual Frame from a File. diff --git a/src/dicom-file.c b/src/dicom-file.c index 1838ebb..ce88d7d 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -757,8 +757,8 @@ DcmDataSet *dcm_filehandle_read_metadata(DcmError **error, } -const DcmDataSet *dcm_filehandle_get_metadata(DcmError **error, - DcmFilehandle *filehandle) +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 @@ -939,12 +939,12 @@ static bool read_skip_to_index(DcmError **error, } -bool dcm_filehandle_read_pixeldata(DcmError **error, - DcmFilehandle *filehandle) +bool dcm_filehandle_prepare_read_frame(DcmError **error, + DcmFilehandle *filehandle) { if (filehandle->offset_table == NULL) { // move to the first of our stop tags - if (dcm_filehandle_get_metadata(error, filehandle) == NULL) { + if (dcm_filehandle_get_metadata_subset(error, filehandle) == NULL) { return false; } @@ -1030,7 +1030,7 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error, { dcm_log_debug("Read frame number #%u.", frame_number); - if (!dcm_filehandle_read_pixeldata(error, filehandle)) { + if (!dcm_filehandle_prepare_read_frame(error, filehandle)) { return NULL; } @@ -1091,7 +1091,7 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error, { dcm_log_debug("Read frame position (%u, %u)", column, row); - if (!dcm_filehandle_read_pixeldata(error, filehandle)) { + if (!dcm_filehandle_prepare_read_frame(error, filehandle)) { return NULL; } diff --git a/tests/check_dicom.c b/tests/check_dicom.c index 07e2627..87cd22b 100644 --- a/tests/check_dicom.c +++ b/tests/check_dicom.c @@ -679,7 +679,8 @@ START_TEST(test_file_sm_image_metadata) free(file_path); ck_assert_ptr_nonnull(filehandle); - const DcmDataSet *metadata = dcm_filehandle_get_metadata(NULL, filehandle); + const DcmDataSet *metadata = + dcm_filehandle_get_metadata_subset(NULL, filehandle); ck_assert_ptr_nonnull(metadata); // SOP Class UID @@ -705,10 +706,11 @@ START_TEST(test_file_sm_image_frame) free(file_path); ck_assert_ptr_nonnull(filehandle); - const DcmDataSet *metadata = dcm_filehandle_get_metadata(NULL, filehandle); + const DcmDataSet *metadata = + dcm_filehandle_get_metadata_subset(NULL, filehandle); ck_assert_ptr_nonnull(metadata); - ck_assert_int_ne(dcm_filehandle_read_pixeldata(NULL, filehandle), 0); + ck_assert_int_ne(dcm_filehandle_prepare_read_frame(NULL, filehandle), 0); DcmFrame *frame = dcm_filehandle_read_frame(NULL, filehandle, From 0b6836a27672bf75dac278cdff029a4fa7e43c3c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 8 Sep 2023 16:35:34 +0100 Subject: [PATCH 80/82] fix handling of AT We were treating AT as a string rather than a fixed length numeric array. Improve display with dcm-dump as well. --- src/dicom-data.c | 22 ++++++++++++++++++++++ src/dicom-dict.c | 2 +- src/dicom-file.c | 2 +- src/pdicom.h | 1 + 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/dicom-data.c b/src/dicom-data.c index d18ffc9..8de4b04 100644 --- a/src/dicom-data.c +++ b/src/dicom-data.c @@ -1223,6 +1223,11 @@ char *dcm_element_value_to_string(const DcmElement *element) 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); } @@ -1271,6 +1276,23 @@ char *dcm_element_value_to_string(const DcmElement *element) } } + // 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); + + uint32_t tag = grp << 16 | ele; + + const char *keyword = dcm_dict_keyword_from_tag(tag); + + if (keyword) { + result = dcm_printf_append(result, " (%s)", keyword); + } + } + return result; } diff --git a/src/dicom-dict.c b/src/dicom-dict.c index 05d7fad..3da9e28 100644 --- a/src/dicom-dict.c +++ b/src/dicom-dict.c @@ -54,7 +54,7 @@ struct _DcmVRTable_hash_entry { static const struct _DcmVRTable vr_table[] = { {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_STRING_MULTI, 0, DCM_CAPACITY_AT, 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}, diff --git a/src/dicom-file.c b/src/dicom-file.c index ce88d7d..7f313e5 100644 --- a/src/dicom-file.c +++ b/src/dicom-file.c @@ -1275,7 +1275,7 @@ static bool print_element_create(DcmError **error, filehandle->indent * 2, " ", (tag & 0xffff0000) >> 16, - tag >> 16); + tag & 0xffff); if (dcm_is_public_tag(tag)) { printf("%s ", dcm_dict_keyword_from_tag(tag)); diff --git a/src/pdicom.h b/src/pdicom.h index 01014fe..e282631 100644 --- a/src/pdicom.h +++ b/src/pdicom.h @@ -67,6 +67,7 @@ int dcm_dict_vr_header_length(DcmVR vr); #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; \ From ec925f319136d74eadb8e4c2284511b4c4acda3f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 23 Sep 2023 14:06:07 +0100 Subject: [PATCH 81/82] bump min meson version since "check" needs 0.54.1 --- meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 1d0289d..a59f8fe 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,8 @@ project( 'warning_level=2', ], license : 'MIT', - meson_version : '>=0.50', + # "check" needs 0.54.1 + meson_version : '>=0.54.1', version : '1.0.0' ) if not meson.is_subproject() From 34d8e9658e76ba47f147b32b11649019db16cf1f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 25 Sep 2023 12:11:11 +0100 Subject: [PATCH 82/82] install ninja for win build this was installed transitively before --- .github/workflows/run_unit_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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