Skip to content

Commit 84c68e2

Browse files
committed
ENH: Add the IMPACT ITK v4 similarity metric (IMPACT-Reg)
itk::ImpactImageToImageMetricv4 plugs the anatomy comparison into ITK's standard registration framework: a drop-in semantic similarity metric for itk::ImageRegistrationMethodv4 with any itk::*Optimizerv4. The registration engine stays standard; only the way images are compared changes. - Static mode: features are precomputed once and interpolated as multi-channel images. - Jacobian mode: features are recomputed per iteration and the gradient is backpropagated through the model (more faithful, more costly). The metric emits the descent direction (-d(value)/dp), matching the ITKv4 convention, and reduces the per-work-unit loss accumulators for multi-threaded evaluation. Adds the Python wrapping and a lung-CT vector-registration test.
1 parent 304d8c1 commit 84c68e2

6 files changed

Lines changed: 1362 additions & 0 deletions
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
#ifndef itkImpactImageToImageMetricv4_h
19+
#define itkImpactImageToImageMetricv4_h
20+
21+
// Intentionally free of any LibTorch dependency so it can be parsed by castxml and
22+
// exposed to Python (WrapITK). The feature maps, interpolators, PCA bases and the
23+
// inference/loss machinery live behind an opaque Internals struct defined in the .hxx;
24+
// the threader and feature-extraction headers (which pull in torch) are included only
25+
// from the .hxx.
26+
27+
#include <itkImageToImageMetricv4.h>
28+
#include <itkDefaultImageToImageMetricTraitsv4.h>
29+
#include <itkBSplineInterpolateImageFunction.h>
30+
#include <itkVectorImage.h>
31+
#include <itkModelConfiguration.h>
32+
#include <functional>
33+
#include <memory>
34+
35+
namespace itk
36+
{
37+
38+
// Forward declaration so the metric can befriend the threader without pulling in its
39+
// (torch-dependent) header here.
40+
template <typename TDomainPartitioner, typename TImageToImageMetric, typename TImpactMetric>
41+
class ImpactImageToImageMetricv4GetValueAndDerivativeThreader;
42+
43+
/** \class ImpactImageToImageMetricv4
44+
*
45+
* \brief Semantic similarity metric comparing internal features of pretrained
46+
* TorchScript models (IMPACT) for multimodal image registration.
47+
*
48+
* This class supports vector images of type VectorImage
49+
* and Image< VectorType, imageDimension >.
50+
*
51+
* See
52+
* ImpactImageToImageMetricv4GetValueAndDerivativeThreader::ProcessPoint for algorithm implementation.
53+
*
54+
* \ingroup Impact
55+
*/
56+
template <typename TFixedImage,
57+
typename TMovingImage,
58+
typename TVirtualImage = TFixedImage,
59+
typename TInternalComputationValueType = double,
60+
typename TMetricTraits =
61+
DefaultImageToImageMetricTraitsv4<TFixedImage, TMovingImage, TVirtualImage, TInternalComputationValueType>>
62+
class ITK_TEMPLATE_EXPORT ImpactImageToImageMetricv4
63+
: public ImageToImageMetricv4<TFixedImage, TMovingImage, TVirtualImage, TInternalComputationValueType, TMetricTraits>
64+
{
65+
public:
66+
ITK_DISALLOW_COPY_AND_MOVE(ImpactImageToImageMetricv4);
67+
68+
/** Standard class type aliases. */
69+
using Self = ImpactImageToImageMetricv4;
70+
using Superclass =
71+
ImageToImageMetricv4<TFixedImage, TMovingImage, TVirtualImage, TInternalComputationValueType, TMetricTraits>;
72+
using Pointer = SmartPointer<Self>;
73+
using ConstPointer = SmartPointer<const Self>;
74+
75+
/** Method for creation through the object factory. */
76+
itkNewMacro(Self);
77+
78+
/** \see LightObject::GetNameOfClass() */
79+
itkOverrideGetNameOfClassMacro(ImpactImageToImageMetricv4);
80+
81+
using typename Superclass::DerivativeType;
82+
83+
using typename Superclass::FixedImagePointType;
84+
using typename Superclass::FixedImagePixelType;
85+
using typename Superclass::FixedImageGradientType;
86+
87+
using typename Superclass::MovingImagePointType;
88+
using typename Superclass::MovingImagePixelType;
89+
using typename Superclass::MovingImageGradientType;
90+
91+
using typename Superclass::MovingTransformType;
92+
using typename Superclass::JacobianType;
93+
using VirtualImageType = typename Superclass::VirtualImageType;
94+
using typename Superclass::VirtualIndexType;
95+
using typename Superclass::VirtualPointType;
96+
using typename Superclass::VirtualPointSetType;
97+
98+
/* Image dimension accessors */
99+
static constexpr typename TVirtualImage::ImageDimensionType VirtualImageDimension = TVirtualImage::ImageDimension;
100+
static constexpr typename TFixedImage::ImageDimensionType FixedImageDimension = TFixedImage::ImageDimension;
101+
static constexpr typename TMovingImage::ImageDimensionType MovingImageDimension = TMovingImage::ImageDimension;
102+
103+
/** Set/Get the TorchScript model configurations used to extract features from the fixed
104+
* image. Each model may target a different resolution, architecture or semantic level.
105+
*/
106+
itkSetMacro(FixedModelsConfiguration, std::vector<ModelConfiguration>);
107+
itkGetConstReferenceMacro(FixedModelsConfiguration, std::vector<ModelConfiguration>);
108+
109+
/** Set/Get the TorchScript model configurations used to extract features from the moving
110+
* image. Distinct fixed/moving models support asymmetric or multimodal setups.
111+
*/
112+
itkSetMacro(MovingModelsConfiguration, std::vector<ModelConfiguration>);
113+
itkGetConstReferenceMacro(MovingModelsConfiguration, std::vector<ModelConfiguration>);
114+
115+
void
116+
SetModelsConfiguration(std::vector<ModelConfiguration> & modelsConfiguration)
117+
{
118+
SetFixedModelsConfiguration(modelsConfiguration);
119+
SetMovingModelsConfiguration(modelsConfiguration);
120+
}
121+
122+
/** Append a single model configuration. Convenience for callers (e.g. Python) that add
123+
* configurations one at a time instead of passing a std::vector. */
124+
void
125+
AddFixedModelConfiguration(const ModelConfiguration & configuration)
126+
{
127+
m_FixedModelsConfiguration.push_back(configuration);
128+
this->Modified();
129+
}
130+
void
131+
AddMovingModelConfiguration(const ModelConfiguration & configuration)
132+
{
133+
m_MovingModelsConfiguration.push_back(configuration);
134+
this->Modified();
135+
}
136+
/** Append the same configuration to both the fixed and moving lists. */
137+
void
138+
AddModelConfiguration(const ModelConfiguration & configuration)
139+
{
140+
AddFixedModelConfiguration(configuration);
141+
AddMovingModelConfiguration(configuration);
142+
}
143+
144+
/** Set/Get the subset of feature channels used in the loss (per layer), for
145+
* dimensionality reduction or focusing on the most informative channels.
146+
*/
147+
itkSetMacro(SubsetFeatures, std::vector<unsigned int>);
148+
itkGetConstMacro(SubsetFeatures, std::vector<unsigned int>);
149+
150+
/** Set/Get the weight applied to each layer's loss contribution, to balance layers of
151+
* different semantic granularity.
152+
*/
153+
itkSetMacro(LayersWeight, std::vector<float>);
154+
itkGetConstMacro(LayersWeight, std::vector<float>);
155+
156+
/** Set/Get the loss function per layer (e.g. "l1", "cosine", "ncc"); heterogeneous
157+
* losses adapt to the nature of each feature representation.
158+
*/
159+
itkSetMacro(Distance, std::vector<std::string>);
160+
itkGetConstMacro(Distance, std::vector<std::string>);
161+
162+
/** Set/Get the number of principal components to keep per layer (PCA on the feature
163+
* maps). 0 disables PCA.
164+
*/
165+
itkSetMacro(PCA, std::vector<unsigned int>);
166+
itkGetConstMacro(PCA, std::vector<unsigned int>);
167+
168+
/** Set/Get the device for all model inference and tensor operations, as a string
169+
* ("cpu", "cuda", "cuda:0", ...).
170+
*/
171+
itkSetMacro(Device, std::string);
172+
itkGetConstMacro(Device, std::string);
173+
174+
/** Set/Get the directory where feature maps are written (empty disables the dump).
175+
* Used for debugging/inspection of the extracted features.
176+
*/
177+
itkSetMacro(FeatureMapsPath, std::string);
178+
itkGetConstMacro(FeatureMapsPath, std::string);
179+
180+
/** Set/Get the mode of operation:
181+
* - "Static": features are precomputed as full maps and interpolated per point.
182+
* - "Jacobian": online per-point patch extraction with backpropagation through the model.
183+
*/
184+
itkSetMacro(Mode, std::string);
185+
itkGetConstMacro(Mode, std::string);
186+
187+
/** Set/Get the RNG seed for feature-subset sampling (0 seeds from the clock). */
188+
itkSetMacro(Seed, unsigned int);
189+
itkGetConstMacro(Seed, unsigned int);
190+
191+
/** Set/Get how often (in optimizer iterations) the feature maps are refreshed.
192+
* 0 disables refreshes; positive values enable periodic updates.
193+
*/
194+
itkSetMacro(FeaturesMapUpdateInterval, int);
195+
itkGetConstMacro(FeaturesMapUpdateInterval, int);
196+
197+
void
198+
Initialize() override;
199+
200+
protected:
201+
ImpactImageToImageMetricv4();
202+
~ImpactImageToImageMetricv4() override = default;
203+
204+
friend class ImpactImageToImageMetricv4GetValueAndDerivativeThreader<
205+
ThreadedImageRegionPartitioner<Superclass::VirtualImageDimension>,
206+
Superclass,
207+
Self>;
208+
friend class ImpactImageToImageMetricv4GetValueAndDerivativeThreader<ThreadedIndexedContainerPartitioner,
209+
Superclass,
210+
Self>;
211+
212+
/** Vector-valued feature-map image type; the per-layer maps and their interpolators
213+
* live in the torch-dependent Internals. */
214+
using FeaturesImageType = VectorImage<float, FixedImageDimension>;
215+
216+
void
217+
PrintSelf(std::ostream & os, Indent indent) const override;
218+
219+
/** Opaque, torch-dependent state (feature maps, interpolators, PCA bases), defined in
220+
* the .hxx. The threader reaches the feature maps through it. */
221+
struct Internals;
222+
std::shared_ptr<Internals> m_Internals;
223+
224+
/** Build the per-layer feature maps for an image (fct maps points through the moving
225+
* transform). Templated on the concrete FeaturesMap type (Internals::FeaturesMap) so
226+
* this declaration carries no torch types. Defined in the .hxx. */
227+
template <typename TFeaturesMap, typename TImage>
228+
std::vector<TFeaturesMap>
229+
GetFeaturesMaps(typename TImage::ConstPointer image,
230+
const std::vector<ModelConfiguration> & modelsConfiguration,
231+
std::function<typename TImage::PointType(const typename TImage::PointType &)> fct = nullptr);
232+
233+
private:
234+
std::vector<ModelConfiguration> m_FixedModelsConfiguration;
235+
std::vector<ModelConfiguration> m_MovingModelsConfiguration;
236+
237+
std::vector<unsigned int> m_SubsetFeatures;
238+
std::vector<unsigned int> m_PCA;
239+
std::vector<float> m_LayersWeight;
240+
std::vector<std::string> m_Distance;
241+
int m_FeaturesMapUpdateInterval;
242+
std::string m_Mode;
243+
std::string m_FeatureMapsPath;
244+
std::string m_Device = "cpu";
245+
unsigned int m_Seed;
246+
247+
std::vector<std::vector<unsigned int>> m_features_indexes;
248+
};
249+
250+
} // end namespace itk
251+
252+
#ifndef ITK_MANUAL_INSTANTIATION
253+
# include "itkImpactImageToImageMetricv4.hxx"
254+
#endif
255+
256+
257+
#endif

0 commit comments

Comments
 (0)