diff --git a/src/io/resample/CMakeLists.txt b/src/io/resample/CMakeLists.txt index 257ca9632..cadac1625 100644 --- a/src/io/resample/CMakeLists.txt +++ b/src/io/resample/CMakeLists.txt @@ -8,6 +8,7 @@ find_package(ITK REQUIRED WebAssemblyInterface ITKImageGrid ITKImageFunction + GenericLabelInterpolator ) include(${ITK_USE_FILE}) diff --git a/src/io/resample/emscripten-build/resample.wasm b/src/io/resample/emscripten-build/resample.wasm index 6a47271ad..1e71629d1 100755 Binary files a/src/io/resample/emscripten-build/resample.wasm and b/src/io/resample/emscripten-build/resample.wasm differ diff --git a/src/io/resample/emscripten-build/resample.wasm.zst b/src/io/resample/emscripten-build/resample.wasm.zst index a30a93ee9..d273785ae 100755 Binary files a/src/io/resample/emscripten-build/resample.wasm.zst and b/src/io/resample/emscripten-build/resample.wasm.zst differ diff --git a/src/io/resample/itkWasmUtils.js b/src/io/resample/itkWasmUtils.js index 35c1a217f..ae99ecc49 100644 --- a/src/io/resample/itkWasmUtils.js +++ b/src/io/resample/itkWasmUtils.js @@ -8,6 +8,7 @@ import { import itkConfig from '@/src/io/itk/itkConfig'; +// Splits the input image into multiple slices and processes them in parallel export async function runWasm( pipeline, args, diff --git a/src/io/resample/resample.cxx b/src/io/resample/resample.cxx index 0d754fbcd..a8a13e398 100644 --- a/src/io/resample/resample.cxx +++ b/src/io/resample/resample.cxx @@ -19,6 +19,7 @@ #include "itkVectorImage.h" #include "itkResampleImageFilter.h" #include "itkLinearInterpolateImageFunction.h" +#include "itkLabelImageGenericInterpolateImageFunction.h" #include "itkImageRegionSplitterSlowDimension.h" #include "itkExtractImageFilter.h" #include "itkRGBPixel.h" @@ -53,6 +54,9 @@ int Resample(itk::wasm::Pipeline &pipeline, itk::wasm::InputImage &input OutputImageType outputImage; pipeline.add_option("OutputImage", outputImage, "Output image")->required(); + bool label = false; + pipeline.add_flag("-l,--label", label, "Interpolate for label image")->default_val(false); + std::vector outSize; pipeline.add_option("-z,--size", outSize, "New image size for each direction")->expected(2, 3)->delimiter(','); @@ -81,6 +85,12 @@ int Resample(itk::wasm::Pipeline &pipeline, itk::wasm::InputImage &input using ResampleFilterType = itk::ResampleImageFilter; auto resampleFilter = ResampleFilterType::New(); + if (label) + { + using InterpolatorType = itk::LabelImageGenericInterpolateImageFunction; + resampleFilter->SetInterpolator(InterpolatorType::New()); + } + resampleFilter->SetInput(inImage); typename ImageType::SizeType outputSize; diff --git a/src/io/resample/resample.ts b/src/io/resample/resample.ts index d7842b471..711bcc734 100644 --- a/src/io/resample/resample.ts +++ b/src/io/resample/resample.ts @@ -1,10 +1,15 @@ import { Image } from 'itk-wasm'; +import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData'; +import vtkITKHelper from '@kitware/vtk.js/Common/DataModel/ITKHelper'; +import { compareImageSpaces } from '@/src/utils/imageSpace'; import { runWasm } from './itkWasmUtils'; -export async function resample(fixed: Image, moving: Image) { +export async function resample(fixed: Image, moving: Image, label = false) { + const labelFlag = label ? ['--label'] : []; const { size, spacing, origin, direction } = fixed; const args = [ + ...labelFlag, '--size', size.join(','), '--spacing', @@ -17,3 +22,15 @@ export async function resample(fixed: Image, moving: Image) { return runWasm('resample', args, [moving]); } + +export async function ensureSameSpace(target: vtkImageData, resampleCandidate: vtkImageData, label = false) { + if (compareImageSpaces(target, resampleCandidate)) { + return resampleCandidate; // could still be different pixel dimensions + } + const itkImage = await resample( + vtkITKHelper.convertVtkToItkImage(target), + vtkITKHelper.convertVtkToItkImage(resampleCandidate), + label + ); + return vtkITKHelper.convertItkToVtkImage(itkImage); +} \ No newline at end of file diff --git a/src/store/segmentGroups.ts b/src/store/segmentGroups.ts index 393327416..4b89b47b5 100644 --- a/src/store/segmentGroups.ts +++ b/src/store/segmentGroups.ts @@ -1,13 +1,13 @@ import { computed, reactive, ref, toRaw, watch } from 'vue'; import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray'; import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData'; +import vtkBoundingBox from '@kitware/vtk.js/Common/DataModel/BoundingBox'; import { defineStore } from 'pinia'; import { useImageStore } from '@/src/store/datasets-images'; import { join, normalize } from '@/src/utils/path'; import { useIdStore } from '@/src/store/id'; import { onImageDeleted } from '@/src/composables/onImageDeleted'; import { normalizeForStore, removeFromArray } from '@/src/utils'; -import { compareImageSpaces } from '@/src/utils/imageSpace'; import { SegmentMask } from '@/src/types/segment'; import { DEFAULT_SEGMENT_MASKS } from '@/src/config'; import { readImage, writeImage } from '@/src/io/readWriteImage'; @@ -27,6 +27,7 @@ import { getImageID, selectionEquals, } from './datasets'; +import { ensureSameSpace } from '../io/resample/resample'; const LabelmapArrayType = Uint8Array; export type LabelmapArrayType = Uint8Array; @@ -215,8 +216,9 @@ export const useSegmentGroupStore = defineStore('segmentGroup', () => { throw new Error('Cannot convert an image to be a labelmap of itself'); // Build vtkImageData for DICOMs - await getImage(image); - await getImage(parent); + const [childImage, parentImage] = await Promise.all( + [image, parent].map(getImage) + ); const imageID = getImageID(image); const parentID = getImageID(parent); @@ -225,18 +227,25 @@ export const useSegmentGroupStore = defineStore('segmentGroup', () => { throw new Error('Image and/or parent datasets do not exist'); const imageStore = useImageStore(); - const parentImage = imageStore.dataIndex[parentID]; - const childImage = imageStore.dataIndex[imageID]; - if (!compareImageSpaces(childImage, parentImage)) - throw new Error('Image does not match parent image space'); + const intersects = vtkBoundingBox.intersects( + parentImage.getBounds(), + childImage.getBounds() + ); + if (!intersects) { + throw new Error( + 'Segment group and parent image bounds do not intersect. So no overlap in physical space.' + ); + } + + const resampled = await ensureSameSpace(parentImage, childImage, true); + const labelmapImage = toLabelMap(resampled); - const segmentGroupStore = useSegmentGroupStore(); - const labelmapImage = toLabelMap(childImage); const { order, byKey } = normalizeForStore( structuredClone(DEFAULT_SEGMENT_MASKS), 'value' ); + const segmentGroupStore = useSegmentGroupStore(); segmentGroupStore.addLabelmap(labelmapImage, { name: imageStore.metadata[imageID].name, parentImage: parentID,