Skip to content

Commit 6266955

Browse files
authored
Reduce seeks on file io (#114)
* Reduce seeks on file io * add changelog entry
1 parent 2f013dc commit 6266955

4 files changed

Lines changed: 63 additions & 52 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* support single-frame DICOM images and allow BitsStored > 8 [tokyovigilante]
66
* fix error handling for string values over max length [arngaillard]
77
* add `-w` (show warnings) to dcm-dump and dcm-getframe
8+
* reduce seeks on file io [rvause]
89

910
## 1.2.0, 09/04/2025
1011

@@ -55,4 +56,3 @@
5556
## 1.0.0, 2/10/23
5657

5758
* first release!
58-

src/dicom-file.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1370,7 +1370,8 @@ DcmFrame *dcm_filehandle_read_frame(DcmError **error,
13701370
char* frame_data = NULL;
13711371
if (dcm_is_encapsulated_transfer_syntax(syntax)) {
13721372
int64_t frame_end_offset = frame_number < filehandle->num_frames ?
1373-
filehandle->offset_table[i + 1] : 0xFFFFFFFF;
1373+
(filehandle->offset_table[i + 1] -
1374+
filehandle->offset_table[i]) : 0xFFFFFFFF;
13741375
frame_data = dcm_parse_encapsulated_frame(error,
13751376
filehandle->io,
13761377
filehandle->implicit,

src/dicom-io.c

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ typedef struct _DcmIOFile {
4646
char input_buffer[BUFFER_SIZE];
4747
int64_t bytes_in_buffer;
4848
int64_t read_point;
49+
int64_t offset;
4950
} DcmIOFile;
5051

5152

@@ -154,6 +155,7 @@ static int64_t refill(DcmError **error, DcmIOFile *file)
154155

155156
file->read_point = 0;
156157
file->bytes_in_buffer = bytes_read;
158+
file->offset += bytes_read;
157159

158160
return bytes_read;
159161
}
@@ -202,42 +204,62 @@ static int64_t dcm_io_seek_file(DcmError **error, DcmIO *io,
202204
{
203205
DcmIOFile *file = (DcmIOFile *) io;
204206

205-
/* We've read ahead by some number of buffered bytes, so first undo that,
206-
* then do the seek from the true position.
207+
/* Translate the request to an absolute target. The logical position the
208+
* caller perceives is offset - bytes_in_buffer + read_point.
207209
*/
208-
int64_t new_offset;
210+
int64_t logical_pos = file->offset - file->bytes_in_buffer +
211+
file->read_point;
212+
int64_t target;
209213

210-
int64_t bytes_ahead = file->bytes_in_buffer - file->read_point;
211-
if (bytes_ahead > 0) {
212-
#ifdef _WIN32
213-
new_offset = _lseeki64(file->fd, -bytes_ahead, SEEK_CUR);
214-
#else
215-
new_offset = lseek(file->fd, -bytes_ahead, SEEK_CUR);
216-
#endif
214+
switch (whence) {
215+
case SEEK_SET:
216+
target = offset;
217+
break;
218+
case SEEK_CUR:
219+
target = logical_pos + offset;
220+
break;
221+
case SEEK_END:
222+
default:
223+
target = -1;
224+
break;
225+
}
217226

218-
if (new_offset < 0) {
219-
dcm_error_set(error, DCM_ERROR_CODE_IO,
220-
"unable to seek file",
221-
"unable to seek %s - %s", file->filename, strerror(errno));
227+
/* If we know the absolute target and it falls within the
228+
* currently-buffered window (offset - bytes_in_buffer, offset), we can
229+
* skip the seek entirely.
230+
*/
231+
if (target >= 0) {
232+
int64_t buffer_base = file->offset - file->bytes_in_buffer;
233+
if (target >= buffer_base && target <= file->offset) {
234+
file->read_point = target - buffer_base;
235+
return target;
222236
}
223237
}
224238

239+
/* For SEEK_SET / SEEK_CUR we always use SEEK_SET with the resulting
240+
* absolute target.
241+
*/
242+
int64_t new_offset;
243+
int seek_whence = (target >= 0) ? SEEK_SET : whence;
244+
int64_t seek_offset = (target >= 0) ? target : offset;
225245
#ifdef _WIN32
226-
new_offset = _lseeki64(file->fd, offset, whence);
246+
new_offset = _lseeki64(file->fd, seek_offset, seek_whence);
227247
#else
228-
new_offset = lseek(file->fd, offset, whence);
248+
new_offset = lseek(file->fd, seek_offset, seek_whence);
229249
#endif
230250

231251
if (new_offset < 0) {
232252
dcm_error_set(error, DCM_ERROR_CODE_IO,
233253
"unable to seek file",
234254
"unable to seek %s - %s", file->filename, strerror(errno));
255+
return new_offset;
235256
}
236257

237-
/* Empty the buffer, since we may now be at a different position.
258+
/* Empty the buffer; the next read will refill it from the new position.
238259
*/
239260
file->bytes_in_buffer = 0;
240261
file->read_point = 0;
262+
file->offset = new_offset;
241263

242264
return new_offset;
243265
}
@@ -399,4 +421,3 @@ int64_t dcm_io_seek(DcmError **error,
399421
{
400422
return io->methods->seek(error, io, offset, whence);
401423
}
402-

src/dicom-parse.c

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,10 +1060,11 @@ char *dcm_parse_encapsulated_frame(DcmError **error,
10601060
uint32_t tag;
10611061
uint32_t fragment_length = 0;
10621062
uint64_t frame_length = 0;
1063+
char *value = NULL;
10631064

1064-
// first determine the total length of bytes to be read
10651065
while (position < frame_end_offset) {
10661066
if (!read_tag(&state, &tag, &position)) {
1067+
free(value);
10671068
return NULL;
10681069
}
10691070
if (tag == TAG_SQ_DELIM) {
@@ -1073,49 +1074,37 @@ char *dcm_parse_encapsulated_frame(DcmError **error,
10731074
dcm_error_set(error, DCM_ERROR_CODE_PARSE,
10741075
"reading frame item failed",
10751076
"no item tag found for frame item");
1077+
free(value);
10761078
return NULL;
10771079
}
10781080
if (!read_uint32(&state, &fragment_length, &position)) {
1081+
free(value);
10791082
return NULL;
10801083
}
1081-
dcm_seekcur(&state, fragment_length, &position);
1082-
frame_length += fragment_length;
1083-
}
1084-
if (frame_length > 0xFFFFFFFF) {
1085-
dcm_error_set(error, DCM_ERROR_CODE_PARSE,
1086-
"invalid frame size",
1087-
"frame size exceeds 4GB" );
1088-
return NULL;
1089-
}
1090-
1091-
char *value = DCM_MALLOC(error, frame_length);
1092-
if (value == NULL) {
1093-
return NULL;
1094-
}
1095-
// if frame end is unknown/undefined then update it
1096-
if (frame_end_offset == 0xFFFFFFFF) {
1097-
frame_end_offset = position;
1098-
}
1099-
// reposition to the beginning of encapsulated pixel data
1100-
dcm_seekcur(&state, -position, &position);
1101-
1102-
fragment_length = 0;
1103-
char* fragment = value;
1104-
position = 0;
1105-
while (position < frame_end_offset) {
1106-
if (!read_tag(&state, &tag, &position)) {
1084+
if (frame_length + fragment_length > 0xFFFFFFFF) {
1085+
dcm_error_set(error, DCM_ERROR_CODE_PARSE,
1086+
"invalid frame size",
1087+
"frame size exceeds 4GB");
1088+
free(value);
11071089
return NULL;
11081090
}
1109-
if (tag == TAG_SQ_DELIM) {
1110-
break;
1091+
1092+
char *new_value = (char *) dcm_realloc(error, value,
1093+
frame_length + fragment_length);
1094+
if (new_value == NULL) {
1095+
free(value);
1096+
return NULL;
11111097
}
1112-
if (!read_uint32(&state, &fragment_length, &position) ||
1113-
!dcm_require(&state, fragment, fragment_length, &position)) {
1098+
value = new_value;
1099+
1100+
if (!dcm_require(&state, value + frame_length, fragment_length,
1101+
&position)) {
11141102
free(value);
11151103
return NULL;
11161104
}
1117-
fragment += fragment_length;
1105+
frame_length += fragment_length;
11181106
}
1107+
11191108
*length = (uint32_t) frame_length;
11201109

11211110
return value;

0 commit comments

Comments
 (0)