diff --git a/src/core/Image.ts b/src/core/Image.ts index 29ef7173f..7ebf7143a 100644 --- a/src/core/Image.ts +++ b/src/core/Image.ts @@ -13,7 +13,7 @@ class Image { size: number[] - metadata: Record + metadata: Record data: null | TypedArray diff --git a/src/io/internal/pipelines/image/ReadDICOM/ReadImageDICOMFileSeries.cxx b/src/io/internal/pipelines/image/ReadDICOM/ReadImageDICOMFileSeries.cxx index 18d3ac4e8..f5cc2fc64 100644 --- a/src/io/internal/pipelines/image/ReadDICOM/ReadImageDICOMFileSeries.cxx +++ b/src/io/internal/pipelines/image/ReadDICOM/ReadImageDICOMFileSeries.cxx @@ -30,6 +30,7 @@ #include "itkPipeline.h" #include "itkOutputImage.h" +#include "itkOutputTextStream.h" class CustomSerieHelper: public gdcm::SerieHelper { @@ -194,6 +195,9 @@ int runPipeline(itk::wasm::Pipeline & pipeline, std::vector & input OutputImageType outputImage; pipeline.add_option("-o,--output-image", outputImage, "Output image volume")->required(); + itk::wasm::OutputTextStream outputFilenames; + auto outputFilenamesOption = pipeline.add_option("--output-filenames", outputFilenames, "Output sorted filenames."); + ITK_WASM_PARSE(pipeline); typedef itk::QuickDICOMImageSeriesReader< ImageType > ReaderType; @@ -201,7 +205,7 @@ int runPipeline(itk::wasm::Pipeline & pipeline, std::vector & input reader->SetMetaDataDictionaryArrayUpdate(false); if (!singleSortedSeries) - { + { std::unique_ptr serieHelper(new CustomSerieHelper()); for (const std::string & fileName: inputFileNames) { @@ -257,11 +261,21 @@ int runPipeline(itk::wasm::Pipeline & pipeline, std::vector & input } reader->SetFileNames(fileNames); - } + } else - { + { reader->SetFileNames(inputFileNames); + } + + // copy sorted filenames as additional output + if(!outputFilenamesOption->empty()) + { + auto finalFileList = reader->GetFileNames(); + for (auto f = finalFileList.begin(); f != finalFileList.end(); ++f) + { + outputFilenames.Get() << *f << '\0'; } + } auto gdcmImageIO = itk::GDCMImageIO::New(); reader->SetImageIO(gdcmImageIO); @@ -279,16 +293,25 @@ int main (int argc, char * argv[]) std::vector inputFileNames; pipeline.add_option("-i,--input-images", inputFileNames, "File names in the series")->required()->check(CLI::ExistingFile)->expected(1,-1); + // We are interested in reading --input-images beforehand. + // 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) bool singleSortedSeries = false; auto sortedOption = pipeline.add_flag("-s,--single-sorted-series", singleSortedSeries, "There is a single sorted series in the files"); + // Type is not important here, its just a dummy placeholder to be added and then removed. std::string outputImage; auto outputImageOption = pipeline.add_option("-o,--output-image", outputImage, "Output image volume")->required(); + // Type is not important here, its just a dummy placeholder to be added and then removed. + std::string outputFilenames; + auto outputFilenamesOption = pipeline.add_option("--output-filenames", outputFilenames, "Output sorted filenames"); + ITK_WASM_PARSE(pipeline); + // Remove added dummy options. runPipeline will add the real options later. pipeline.remove_option(sortedOption); pipeline.remove_option(outputImageOption); + pipeline.remove_option(outputFilenamesOption); auto gdcmImageIO = itk::GDCMImageIO::New(); diff --git a/src/io/readImageDICOMArrayBufferSeries.ts b/src/io/readImageDICOMArrayBufferSeries.ts index b7347c0e3..941343291 100644 --- a/src/io/readImageDICOMArrayBufferSeries.ts +++ b/src/io/readImageDICOMArrayBufferSeries.ts @@ -21,7 +21,7 @@ const workerFunction = async ( ) worker = usedWorker - const args = ['--memory-io', '--output-image', '0', '--input-images'] + const args = ['--memory-io', '--output-image', '0', '--output-filenames', '1', '--input-images'] fileDescriptions.forEach((desc) => { args.push(`./${desc.path}`) }) @@ -29,7 +29,8 @@ const workerFunction = async ( args.push('--single-sorted-series') } const outputs = [ - { type: InterfaceTypes.Image } + { type: InterfaceTypes.Image }, + { type: InterfaceTypes.TextStream } ] const inputs = fileDescriptions.map((fd) => { return { type: InterfaceTypes.BinaryFile, data: fd } @@ -54,6 +55,20 @@ const workerFunction = async ( inputs } const result: PipelineResult = await webworkerPromise.postMessage(message, transferables) + const image: Image = result.outputs[0].data + const filenames: string[] = result.outputs[1].data.data.split('\0') + // remove the last element since we expect it to be empty + filenames?.pop() + + if (image.metadata === undefined) { + const metadata: + Record = {} + metadata.orderedFileNames = filenames + image.metadata = metadata + } else { + image.metadata.orderedFileNames = filenames + } + return { image: result.outputs[0].data as Image, webWorker: worker } } const numberOfWorkers = typeof globalThis.navigator?.hardwareConcurrency === 'number' ? globalThis.navigator.hardwareConcurrency : 4 @@ -63,10 +78,12 @@ const seriesBlockSize = 8 const readImageDICOMArrayBufferSeries = async ( arrayBuffers: ArrayBuffer[], - singleSortedSeries = false + singleSortedSeries = false, + fileNames?: string[] ): Promise => { + const validFileNames = (fileNames != null) && fileNames.length === arrayBuffers.length const fileDescriptions = arrayBuffers.map((ab, index) => { - return { path: `${index}.dcm`, data: new Uint8Array(ab) } + return { path: validFileNames ? fileNames[index] : `${index}.dcm`, data: new Uint8Array(ab) } }) if (singleSortedSeries) { const taskArgsArray = [] diff --git a/src/io/readImageDICOMFileSeries.ts b/src/io/readImageDICOMFileSeries.ts index a78d75132..f9d0228a0 100644 --- a/src/io/readImageDICOMFileSeries.ts +++ b/src/io/readImageDICOMFileSeries.ts @@ -12,7 +12,8 @@ const readImageDICOMFileSeries = async ( }) const fileContents: ArrayBuffer[] = await Promise.all(fetchFileContents) - return await readImageDICOMArrayBufferSeries(fileContents, singleSortedSeries) + const fileNames = Array.from(fileList, (file) => file.name) + return await readImageDICOMArrayBufferSeries(fileContents, singleSortedSeries, fileNames) } export default readImageDICOMFileSeries