Skip to content

Commit 71218ee

Browse files
authored
Use frame pixel positions (#62)
* fix a compiler warning * use frame pixel positions rather than index When parsing PerFrameFunctionalGroupsSequence, use ColumnPositionInTotalImagePixelMatrix and RowPositionInTotalImagePixelMatrix to position frames. Add a new error code, DCM_ERROR_CODE_MISSING_FRAME, which is set by dcm_filehandle_read_frame_position() if a missing frame is requested.
1 parent 030b427 commit 71218ee

File tree

5 files changed

+155
-28
lines changed

5 files changed

+155
-28
lines changed

doc/source/usage.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ You can read all metadata and control read stop using a sequence of calls to
2727

2828
In case the Data Set contained in a Part10 file represents an Image instance,
2929
individual frames may be read out with :c:func:`dcm_filehandle_read_frame()`.
30+
3031
Use :c:func:`dcm_filehandle_read_frame_position()` to read the frame at a
31-
certain (column, row) position.
32+
certain (column, row) position. This will return NULL and set the error code
33+
`DCM_ERROR_CODE_MISSING_FRAME` if there is no frame at that position.
3234

3335
A `Data Element
3436
<http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_3.html#glossentry_DataElement>`_

include/dicom/dicom.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ typedef enum _DcmErrorCode {
162162
DCM_ERROR_CODE_PARSE = 3,
163163
/** IO error */
164164
DCM_ERROR_CODE_IO = 4,
165+
/** Missing frame */
166+
DCM_ERROR_CODE_MISSING_FRAME = 5,
165167
} DcmErrorCode;
166168

167169
/**
@@ -1777,6 +1779,11 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error,
17771779
* This takes account of any frame positioning given in
17781780
* PerFrameFunctionalGroupSequence.
17791781
*
1782+
* If the frame is missing, perhaps because this is a sparse file, this
1783+
* function returns NULL and sets the error
1784+
* :c:enum:`DCM_ERROR_CODE_MISSING_FRAME`. Applications can detect
1785+
* this and render a background image.
1786+
*
17801787
* :param error: Pointer to error object
17811788
* :param filehandle: File
17821789
* :param column: Column number, from 0

src/dicom-file.c

Lines changed: 136 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ struct _DcmFilehandle {
4949
DcmDataSet *meta;
5050

5151
// image properties we need to track
52-
uint32_t tiles_across;
52+
uint32_t frame_width;
53+
uint32_t frame_height;
54+
uint32_t frames_across;
5355
uint32_t num_frames;
5456
struct PixelDescription desc;
5557
DcmLayout layout;
@@ -63,6 +65,8 @@ struct _DcmFilehandle {
6365

6466
// used to count frames as we scan perframefunctionalgroup
6567
uint32_t frame_number;
68+
int32_t column_position;
69+
int32_t row_position;
6670

6771
// indent for file print
6872
int indent;
@@ -296,23 +300,44 @@ static bool get_num_frames(DcmError **error,
296300
}
297301

298302

299-
static bool get_tiles_across(DcmError **error,
300-
const DcmDataSet *metadata,
301-
uint32_t *tiles_across)
303+
static bool get_frame_size(DcmError **error,
304+
const DcmDataSet *metadata,
305+
uint32_t *frame_width,
306+
uint32_t *frame_height)
302307
{
303308
int64_t width;
304-
int64_t tile_width;
309+
int64_t height;
305310

306-
if (!get_tag_int(error, metadata, "Columns", &tile_width)) {
311+
if (!get_tag_int(error, metadata, "Columns", &width) ||
312+
!get_tag_int(error, metadata, "Rows", &height)) {
313+
return false;
314+
}
315+
316+
*frame_width = width;
317+
*frame_height = height;
318+
319+
return true;
320+
}
321+
322+
323+
static bool get_frames_across(DcmError **error,
324+
const DcmDataSet *metadata,
325+
uint32_t *frames_across)
326+
{
327+
int64_t width;
328+
uint32_t frame_width;
329+
uint32_t frame_height;
330+
331+
if (!get_frame_size(error, metadata, &frame_width, &frame_height)) {
307332
return false;
308333
}
309334

310335
// TotalPixelMatrixColumns is optional and defaults to Columns, ie. one
311-
// tile across
312-
width = tile_width;
336+
// frame across
337+
width = frame_width;
313338
(void) get_tag_int(NULL, metadata, "TotalPixelMatrixColumns", &width);
314339

315-
*tiles_across = width / tile_width + !!(width % tile_width);
340+
*frames_across = width / frame_width + !!(width % frame_width);
316341

317342
return true;
318343
}
@@ -790,15 +815,22 @@ const DcmDataSet *dcm_filehandle_get_metadata_subset(DcmError **error,
790815
}
791816

792817
// useful values for later
793-
if (!get_tiles_across(error, meta, &filehandle->tiles_across) ||
818+
if (!get_frame_size(error,
819+
meta,
820+
&filehandle->frame_width,
821+
&filehandle->frame_height) ||
822+
!get_frames_across(error, meta, &filehandle->frames_across) ||
794823
!get_num_frames(error, meta, &filehandle->num_frames) ||
795824
!set_pixel_description(error, meta, &filehandle->desc)) {
796825
dcm_dataset_destroy(meta);
797826
return false;
798827
}
799828

800-
// we support sparse and full tile layout, defaulting to full if no type
801-
// is specified
829+
// we support sparse and full frame layout, defaulting to full if
830+
// no type is specified
831+
//
832+
// we flip to SPARSE if there's a PerFrameFunctionalGroupsSequence
833+
// containing frame positions, see below
802834
const char *type;
803835
if (get_tag_str(NULL, meta, "DimensionOrganizationType", &type)) {
804836
if (strcmp(type, "TILED_SPARSE") == 0 || strcmp(type, "3D") == 0) {
@@ -822,28 +854,103 @@ const DcmDataSet *dcm_filehandle_get_metadata_subset(DcmError **error,
822854
}
823855

824856

825-
static bool parse_frame_index_element_create(DcmError **error,
857+
static bool parse_frame_index_sequence_begin(DcmError **error,
826858
void *client,
827859
uint32_t tag,
828860
DcmVR vr,
829-
char *value,
830861
uint32_t length)
831862
{
832863
USED(error);
864+
USED(vr);
865+
USED(length);
833866

834867
DcmFilehandle *filehandle = (DcmFilehandle *) client;
835868

836-
if (vr == DCM_VR_UL && length == 8 && tag == TAG_DIMENSION_INDEX_VALUES) {
837-
// it will have already been byteswapped for us, if necessary
838-
uint32_t *ul = (uint32_t *) value;
839-
uint32_t col = ul[0];
840-
uint32_t row = ul[1];
841-
uint32_t index = (col - 1) + (row - 1) * filehandle->tiles_across;
869+
if (tag == TAG_PLANE_POSITION_SLIDE_SEQUENCE) {
870+
filehandle->column_position = -1;
871+
filehandle->row_position = -1;
872+
}
873+
874+
return true;
875+
}
876+
877+
878+
static bool parse_frame_index_sequence_end(DcmError **error,
879+
void *client,
880+
uint32_t tag,
881+
DcmVR vr,
882+
uint32_t length)
883+
{
884+
USED(vr);
885+
USED(length);
842886

887+
DcmFilehandle *filehandle = (DcmFilehandle *) client;
888+
889+
// have we seen a valid pair of frame positions
890+
if (tag == TAG_PLANE_POSITION_SLIDE_SEQUENCE &&
891+
filehandle->column_position != -1 &&
892+
filehandle->row_position != -1) {
893+
// we don't support fractional frame positioning ... they must be
894+
// exactly aligned on frame boundaries
895+
if ((filehandle->column_position - 1) % filehandle->frame_width != 0 ||
896+
(filehandle->row_position - 1) % filehandle->frame_height != 0) {
897+
dcm_error_set(error, DCM_ERROR_CODE_PARSE,
898+
"Reading PerFrameFunctionalGroupsSequence failed",
899+
"Unsupported frame alignment.");
900+
return false;
901+
}
902+
903+
// map the position of the frame to the frame number
904+
int col = (filehandle->column_position - 1) / filehandle->frame_width;
905+
int row = (filehandle->row_position - 1) / filehandle->frame_height;
906+
uint32_t index = col + row * filehandle->frames_across;
843907
if (index < filehandle->num_frames) {
844908
filehandle->frame_index[index] = filehandle->frame_number;
845-
filehandle->frame_number += 1;
909+
910+
// we have something meaningful in PerFrameFunctionalGroupsSequence,
911+
// so we must display in SPARSE mode
912+
filehandle->layout = DCM_LAYOUT_SPARSE;
913+
}
914+
915+
// end of TAG_PLANE_POSITION_SLIDE_SEQUENCE, so we're on to the next
916+
// frame
917+
filehandle->frame_number += 1;
918+
}
919+
920+
return true;
921+
}
922+
923+
924+
static bool parse_frame_index_element_create(DcmError **error,
925+
void *client,
926+
uint32_t tag,
927+
DcmVR vr,
928+
char *value,
929+
uint32_t length)
930+
{
931+
USED(error);
932+
933+
DcmFilehandle *filehandle = (DcmFilehandle *) client;
934+
935+
switch (tag) {
936+
case TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX:
937+
if (vr == DCM_VR_SL && length == 4) {
938+
int32_t *sl = (int32_t *) value;
939+
940+
filehandle->column_position = sl[0];
846941
}
942+
break;
943+
944+
case TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX:
945+
if (vr == DCM_VR_SL && length == 4) {
946+
int32_t *sl = (int32_t *) value;
947+
948+
filehandle->row_position = sl[0];
949+
}
950+
break;
951+
952+
default:
953+
break;
847954
}
848955

849956
return true;
@@ -870,6 +977,8 @@ static bool read_frame_index(DcmError **error,
870977
DcmFilehandle *filehandle)
871978
{
872979
static DcmParse parse = {
980+
.sequence_begin = parse_frame_index_sequence_begin,
981+
.sequence_end = parse_frame_index_sequence_end,
873982
.element_create = parse_frame_index_element_create,
874983
.stop = parse_frame_index_stop,
875984
};
@@ -1095,29 +1204,29 @@ DcmFrame *dcm_filehandle_read_frame_position(DcmError **error,
10951204
return NULL;
10961205
}
10971206

1098-
if (column >= filehandle->tiles_across) {
1207+
if (column >= filehandle->frames_across) {
10991208
dcm_error_set(error, DCM_ERROR_CODE_PARSE,
11001209
"Reading Frame position failed",
11011210
"Column must be less than %u",
1102-
filehandle->tiles_across);
1211+
filehandle->frames_across);
11031212
return NULL;
11041213
}
11051214

1106-
uint32_t index = column + row * filehandle->tiles_across;
1215+
uint32_t index = column + row * filehandle->frames_across;
11071216

11081217
if (index >= filehandle->num_frames) {
11091218
dcm_error_set(error, DCM_ERROR_CODE_PARSE,
11101219
"Reading Frame position failed",
11111220
"Row must be less than %u",
1112-
filehandle->num_frames / filehandle->tiles_across);
1221+
filehandle->num_frames / filehandle->frames_across);
11131222
return NULL;
11141223
}
11151224

11161225
if (filehandle->layout == DCM_LAYOUT_SPARSE) {
11171226
index = filehandle->frame_index[index];
11181227
if (index == 0xffffffff) {
1119-
dcm_error_set(error, DCM_ERROR_CODE_PARSE,
1120-
"Reading Frame position failed",
1228+
dcm_error_set(error, DCM_ERROR_CODE_MISSING_FRAME,
1229+
"No frame",
11211230
"No Frame at position (%u, %u)", column, row);
11221231
return NULL;
11231232
}

src/dicom.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ const char *dcm_error_code_str(DcmErrorCode code)
149149
case DCM_ERROR_CODE_IO:
150150
return "IO error";
151151

152+
case DCM_ERROR_CODE_MISSING_FRAME:
153+
return "Missing frame";
154+
152155
default:
153156
return "Unknown error code";
154157
}
@@ -170,6 +173,9 @@ const char *dcm_error_code_name(DcmErrorCode code)
170173
case DCM_ERROR_CODE_IO:
171174
return "IO";
172175

176+
case DCM_ERROR_CODE_MISSING_FRAME:
177+
return "MISSING_FRAME";
178+
173179
default:
174180
return "UNKNOWN";
175181
}

src/pdicom.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ typedef SSIZE_T ssize_t;
4545

4646
#define TAG_DIMENSION_INDEX_VALUES 0x00209157
4747
#define TAG_REFERENCED_IMAGE_NAVIGATION_SEQUENCE 0x00480200
48+
#define TAG_PLANE_POSITION_SLIDE_SEQUENCE 0x0048021a
49+
#define TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX 0x0048021e
50+
#define TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX 0x0048021f
4851
#define TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE 0x52009230
4952
#define TAG_EXTENDED_OFFSET_TABLE 0x7FE00001
5053
#define TAG_FLOAT_PIXEL_DATA 0x7FE00008

0 commit comments

Comments
 (0)