Skip to content

Commit a28eba1

Browse files
committed
ENH: return sorted file list when reading DICOM series
The functions readImageDICOMFileSeries and readImageDICOMArrayBufferSeries have an additional side effect that it sorts / orders the files (particularly DICOM files) based on a pre-defined logic in gdcm. However, this information is not related to the caller in any form. Add Array<string> as the ordered list of filenames as additional output.
1 parent e0c97a3 commit a28eba1

File tree

4 files changed

+49
-9
lines changed

4 files changed

+49
-9
lines changed

src/core/Image.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Image {
1313

1414
size: number[]
1515

16-
metadata: Record<string, string | number | number[] | number[][]>
16+
metadata: Record<string, string | string[] | number | number[] | number[][]>
1717

1818
data: null | TypedArray
1919

src/io/internal/pipelines/image/ReadDICOM/ReadImageDICOMFileSeries.cxx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
#include "itkPipeline.h"
3232
#include "itkOutputImage.h"
33+
#include "itkOutputTextStream.h"
3334

3435
class CustomSerieHelper: public gdcm::SerieHelper
3536
{
@@ -194,14 +195,17 @@ int runPipeline(itk::wasm::Pipeline & pipeline, std::vector<std::string> & input
194195
OutputImageType outputImage;
195196
pipeline.add_option("-o,--output-image", outputImage, "Output image volume")->required();
196197

198+
itk::wasm::OutputTextStream outputFilenames;
199+
auto outputFilenamesOption = pipeline.add_option("--output-filenames", outputFilenames, "Output sorted filenames.");
200+
197201
ITK_WASM_PARSE(pipeline);
198202

199203
typedef itk::QuickDICOMImageSeriesReader< ImageType > ReaderType;
200204
typename ReaderType::Pointer reader = ReaderType::New();
201205
reader->SetMetaDataDictionaryArrayUpdate(false);
202206

203207
if (!singleSortedSeries)
204-
{
208+
{
205209
std::unique_ptr<CustomSerieHelper> serieHelper(new CustomSerieHelper());
206210
for (const std::string & fileName: inputFileNames)
207211
{
@@ -257,11 +261,21 @@ int runPipeline(itk::wasm::Pipeline & pipeline, std::vector<std::string> & input
257261
}
258262

259263
reader->SetFileNames(fileNames);
260-
}
264+
}
261265
else
262-
{
266+
{
263267
reader->SetFileNames(inputFileNames);
268+
}
269+
270+
// copy sorted filenames as additional output
271+
if(!outputFilenamesOption->empty())
272+
{
273+
auto finalFileList = reader->GetFileNames();
274+
for (auto f = finalFileList.begin(); f != finalFileList.end(); ++f)
275+
{
276+
outputFilenames.Get() << *f << ":";
264277
}
278+
}
265279

266280
auto gdcmImageIO = itk::GDCMImageIO::New();
267281
reader->SetImageIO(gdcmImageIO);
@@ -279,16 +293,25 @@ int main (int argc, char * argv[])
279293
std::vector<std::string> inputFileNames;
280294
pipeline.add_option("-i,--input-images", inputFileNames, "File names in the series")->required()->check(CLI::ExistingFile)->expected(1,-1);
281295

296+
// We are interested in reading --input-images beforehand.
297+
// We need to add and then remove other options in order to do ITK_WASM_PARSE twice (once here in main, and then again in runPipeline)
282298
bool singleSortedSeries = false;
283299
auto sortedOption = pipeline.add_flag("-s,--single-sorted-series", singleSortedSeries, "There is a single sorted series in the files");
284300

301+
// Type is not important here, its just a dummy placeholder to be added and then removed.
285302
std::string outputImage;
286303
auto outputImageOption = pipeline.add_option("-o,--output-image", outputImage, "Output image volume")->required();
287304

305+
// Type is not important here, its just a dummy placeholder to be added and then removed.
306+
std::string outputFilenames;
307+
auto outputFilenamesOption = pipeline.add_option("--output-filenames", outputFilenames, "Output sorted filenames");
308+
288309
ITK_WASM_PARSE(pipeline);
289310

311+
// Remove added dummy options. runPipeline will add the real options later.
290312
pipeline.remove_option(sortedOption);
291313
pipeline.remove_option(outputImageOption);
314+
pipeline.remove_option(outputFilenamesOption);
292315

293316
auto gdcmImageIO = itk::GDCMImageIO::New();
294317

src/io/readImageDICOMArrayBufferSeries.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@ const workerFunction = async (
2121
)
2222
worker = usedWorker
2323

24-
const args = ['--memory-io', '--output-image', '0', '--input-images']
24+
const args = ['--memory-io', '--output-image', '0', '--output-filenames', '1', '--input-images']
2525
fileDescriptions.forEach((desc) => {
2626
args.push(`./${desc.path}`)
2727
})
2828
if (singleSortedSeries) {
2929
args.push('--single-sorted-series')
3030
}
3131
const outputs = [
32-
{ type: InterfaceTypes.Image }
32+
{ type: InterfaceTypes.Image },
33+
{ type: InterfaceTypes.TextStream },
3334
]
3435
const inputs = fileDescriptions.map((fd) => {
3536
return { type: InterfaceTypes.BinaryFile, data: fd }
@@ -54,6 +55,19 @@ const workerFunction = async (
5455
inputs
5556
}
5657
const result: PipelineResult = await webworkerPromise.postMessage(message, transferables)
58+
const image: Image = result.outputs[0].data
59+
const filenames: string[] = result.outputs[1].data.data.split(':')
60+
61+
if(image.metadata === undefined) {
62+
const metadata:
63+
Record<string, string | string[] | number | number[] | number[][]> = {}
64+
metadata["orderedFileNames"] = filenames
65+
image.metadata = metadata
66+
}
67+
else {
68+
image.metadata["orderedFileNames"] = filenames
69+
}
70+
5771
return { image: result.outputs[0].data as Image, webWorker: worker }
5872
}
5973
const numberOfWorkers = typeof globalThis.navigator?.hardwareConcurrency === 'number' ? globalThis.navigator.hardwareConcurrency : 4
@@ -63,10 +77,12 @@ const seriesBlockSize = 8
6377

6478
const readImageDICOMArrayBufferSeries = async (
6579
arrayBuffers: ArrayBuffer[],
66-
singleSortedSeries = false
80+
singleSortedSeries = false,
81+
fileNames?: Array<string>
6782
): Promise<ReadImageFileSeriesResult> => {
83+
const validFileNames = fileNames && fileNames.length == arrayBuffers.length
6884
const fileDescriptions = arrayBuffers.map((ab, index) => {
69-
return { path: `${index}.dcm`, data: new Uint8Array(ab) }
85+
return { path: validFileNames? fileNames[index] : `${index}.dcm`, data: new Uint8Array(ab) }
7086
})
7187
if (singleSortedSeries) {
7288
const taskArgsArray = []

src/io/readImageDICOMFileSeries.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ const readImageDICOMFileSeries = async (
1212
})
1313
const fileContents: ArrayBuffer[] = await Promise.all(fetchFileContents)
1414

15-
return await readImageDICOMArrayBufferSeries(fileContents, singleSortedSeries)
15+
const fileNames = Array.from(fileList, (file) => file.name);
16+
return await readImageDICOMArrayBufferSeries(fileContents, singleSortedSeries, fileNames)
1617
}
1718

1819
export default readImageDICOMFileSeries

0 commit comments

Comments
 (0)