Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*=========================================================================
*
* Copyright NumFOCUS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/
#ifndef itkLabelOverlapLabelSetMeasures_h
#define itkLabelOverlapLabelSetMeasures_h

#include "itkIntTypes.h"

namespace itk
{
/** \class LabelOverlapLabelSetMeasures
* \brief Metrics stored per label
* \ingroup ITKImageStatistics
*/
struct LabelOverlapLabelSetMeasures
{
SizeValueType m_Source{ 0 };
SizeValueType m_Target{ 0 };
SizeValueType m_Union{ 0 };
SizeValueType m_Intersection{ 0 };
SizeValueType m_SourceComplement{ 0 };
SizeValueType m_TargetComplement{ 0 };

// ITK's igenerator wrapping pipeline does not expose public data members of
// a struct to Python. Provide explicit getters so wrapped consumers (Python
// tests, downstream bindings) can read these fields. Also provide
// descriptively-named aliases for the m_-prefixed forms; the m_Foo names
// remain accessible from C++ for backward compatibility with consumers that
// construct or mutate these values directly.
SizeValueType
GetSource() const
{
return m_Source;
}
SizeValueType
GetTarget() const
{
return m_Target;
}
SizeValueType
GetUnion() const
{
return m_Union;
}
SizeValueType
GetIntersection() const
{
return m_Intersection;
}
SizeValueType
GetSourceComplement() const
{
return m_SourceComplement;
}
SizeValueType
GetTargetComplement() const
{
return m_TargetComplement;
}
};
} // namespace itk
#endif // itkLabelOverlapLabelSetMeasures_h
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@

#include "itkImageSink.h"
#include "itkNumericTraits.h"
#include "itkLabelOverlapLabelSetMeasures.h"
#include <mutex>
#include <unordered_map>

namespace itk
{

/** \class LabelOverlapMeasuresImageFilter
* \brief Computes overlap measures between the same set of labels of
* pixels of two images. Background is assumed to be 0.
Expand Down Expand Up @@ -71,25 +71,13 @@ class ITK_TEMPLATE_EXPORT LabelOverlapMeasuresImageFilter : public ImageSink<TLa
/** Type to use for computations. */
using RealType = typename NumericTraits<LabelType>::RealType;

/** \class LabelSetMeasures
* \brief Metrics stored per label
* \ingroup ITKImageStatistics
*/
class LabelSetMeasures
{
public:
// default constructor/copy/move etc...

SizeValueType m_Source{ 0 };
SizeValueType m_Target{ 0 };
SizeValueType m_Union{ 0 };
SizeValueType m_Intersection{ 0 };
SizeValueType m_SourceComplement{ 0 };
SizeValueType m_TargetComplement{ 0 };
};
#ifndef ITK_FUTURE_LEGACY_REMOVE
/** Deprecated backward-compatibility alias. Use LabelOverlapLabelSetMeasures directly. */
using LabelSetMeasures = LabelOverlapLabelSetMeasures;
#endif // !ITK_FUTURE_LEGACY_REMOVE

/** Type of the map used to store data per label */
Comment thread
hjmjohnson marked this conversation as resolved.
using MapType = std::unordered_map<LabelType, LabelSetMeasures>;
using MapType = std::unordered_map<LabelType, LabelOverlapLabelSetMeasures>;
using MapIterator = typename MapType::iterator;
using MapConstIterator = typename MapType::const_iterator;

Expand All @@ -111,6 +99,48 @@ class ITK_TEMPLATE_EXPORT LabelOverlapMeasuresImageFilter : public ImageSink<TLa
return this->m_LabelSetMeasures;
}

/** Get the labels for which set measures have been computed.
*
* Provided for Python ergonomics: SWIG cannot wrap
* `std::unordered_map<LabelType, LabelOverlapLabelSetMeasures>` as a
* Python dict across submodule boundaries (the value type is `%import`-ed
* rather than `%include`-d, so `%template` is silently dropped). Pair
* this accessor with `GetMeasureForLabel()` to iterate labels and look up
* their measures from Python:
*
* \code{.py}
* for label in filter.GetLabels():
* m = filter.GetMeasureForLabel(label)
* \endcode
*/
std::vector<LabelType>
GetLabels() const
{
std::vector<LabelType> labels;
labels.reserve(this->m_LabelSetMeasures.size());
for (const auto & kv : this->m_LabelSetMeasures)
{
labels.push_back(kv.first);
}
return labels;
}

/** Get the per-label measures struct for a single label. Throws
* itk::ExceptionObject if the label is not present in the map.
* See GetLabels() for the Python iteration idiom.
*/
LabelOverlapLabelSetMeasures
GetMeasureForLabel(LabelType label) const
{
const auto it = this->m_LabelSetMeasures.find(label);
if (it == this->m_LabelSetMeasures.end())
{
itkExceptionMacro("Label " << static_cast<typename NumericTraits<LabelType>::PrintType>(label)
<< " is not present in the label set measures map.");
}
return it->second;
}

// Overlap agreement metrics

/** Get the total overlap over all labels. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4106f7a97659761a7fd42594a4f19aa9dad312445a1c9ff63f397f0c32cb952ee5d6ba6c6582951d883e6f40587091437a872c5fce966670d57f4984125c7a5d
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eb93426d1ee5f00d722979fd9dbfd8701f27bbb4256522e3d25c4db0f88f33b6a2db1ce572613e38c1a91787ca911e2399d206a137f7e987421b357bd5925b5d
1 change: 1 addition & 0 deletions Modules/Filtering/ImageStatistics/wrapping/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
itk_wrap_module(ITKImageStatistics)
set(WRAPPER_SUBMODULE_ORDER itkLabelOverlapLabelSetMeasures)
itk_auto_load_and_end_wrap_submodules()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
itk_wrap_simple_class("itk::LabelOverlapLabelSetMeasures")
14 changes: 14 additions & 0 deletions Modules/Filtering/ImageStatistics/wrapping/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
set(test_input_dir ${itk-module_SOURCE_DIR}/test/Input)

# let's make sure 3D uchar images are wrapped
list(FIND ITK_WRAP_IMAGE_DIMS 3 wrap_3_index)
if(ITK_WRAP_PYTHON AND ITK_WRAP_unsigned_char AND wrap_3_index GREATER -1)
itk_python_add_test(
NAME LabelOverlapMeasuresImageFilterTest
TEST_DRIVER_ARGS
COMMAND
${CMAKE_CURRENT_SOURCE_DIR}/itkLabelOverlapMeasuresImageFilterTest.py
DATA{${test_input_dir}/DzZ_T1.seg.nrrd}
DATA{${test_input_dir}/DzZ_Seeds.seg.nrrd}
)
endif()
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# ==========================================================================
#
# Copyright NumFOCUS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ==========================================================================*/


import itk
from sys import argv

itk.auto_progress(2)

ref = itk.imread(argv[1], itk.UC)
seg = itk.imread(argv[2], itk.UC)

lom_filter = itk.LabelOverlapMeasuresImageFilter[itk.Image[itk.UC, 3]].New()
lom_filter.SetTargetImage(seg)
lom_filter.SetSourceImage(ref)
lom_filter.UpdateLargestPossibleRegion()

# GetLabelSetMeasures() returns std::unordered_map<LabelType, ...>, which SWIG
# cannot wrap as a Python dict across submodule boundaries. Use the paired
# accessors GetLabels() + GetMeasureForLabel() for Python iteration.
labels = list(lom_filter.GetLabels())
print(f"Found {len(labels)} labels")
assert len(labels) > 0, "GetLabels() returned no labels"
for label in sorted(labels):
measure = lom_filter.GetMeasureForLabel(label)
# Use the explicit Get* accessors (igenerator does not expose public data
# members of a struct to Python; the m_Foo fields are not visible).
intersection = measure.GetIntersection()
union = measure.GetUnion()
print(f"Label: {label}, i: {intersection}, u: {union}")
assert (
intersection >= 0
), f"Label {label}: intersection ({intersection}) must be non-negative"
assert (
union >= intersection
), f"Label {label}: union ({union}) must be >= intersection ({intersection})"
Loading