Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added MetricType #89

Merged
merged 29 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
73d83a7
added MetricType to the mix, sorting upon print the metrics into diff…
Hendrik-code Jan 25, 2024
dbccf1c
Autoformat with black
brainless-bot[bot] Jan 25, 2024
6b7223f
add F1 score description
neuronflow Feb 1, 2024
dfda692
sort imports
neuronflow Feb 1, 2024
e1f7ba8
sorted imports
neuronflow Feb 1, 2024
5b2caf1
sorting imports
neuronflow Feb 1, 2024
dbf9d0b
Autoformat with black
brainless-bot[bot] Feb 1, 2024
a1951c4
clean unused imports
neuronflow Feb 1, 2024
2459144
cleaning unused imports
neuronflow Feb 1, 2024
abbfbbe
back to DSC
neuronflow Feb 1, 2024
2b22ce0
clean unused imports
neuronflow Feb 1, 2024
99a7811
fix: scipy import warnings
neuronflow Feb 1, 2024
21c7829
clean
neuronflow Feb 1, 2024
3dffd94
stop accessing protected variables
neuronflow Feb 1, 2024
495f729
clean
neuronflow Feb 1, 2024
1f8b7f1
add todo
neuronflow Feb 1, 2024
f926558
format
neuronflow Feb 1, 2024
9a90686
Autoformat with black
brainless-bot[bot] Feb 1, 2024
bb7d422
more todos
neuronflow Feb 1, 2024
ac55a58
clean
neuronflow Feb 1, 2024
3f55da0
add todo
neuronflow Feb 1, 2024
db229c2
add docstring
neuronflow Feb 1, 2024
fec5734
add todo
neuronflow Feb 1, 2024
bd0bd94
docs
neuronflow Feb 1, 2024
b7710cb
clean
neuronflow Feb 1, 2024
23c416f
docs
neuronflow Feb 1, 2024
0b7f27b
rename to iarray to avoid name collision
neuronflow Feb 1, 2024
3c7e2f9
fixed cyclic import, a lot of deprecated protected accesses
Hendrik-code Feb 9, 2024
2c072a2
Autoformat with black
brainless-bot[bot] Feb 9, 2024
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
2 changes: 1 addition & 1 deletion examples/example_spine_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from auxiliary.turbopath import turbopath

from panoptica import MatchedInstancePair, Panoptic_Evaluator
from panoptica.metrics import Metric, Metric, MetricMode
from panoptica.metrics import Metric

directory = turbopath(__file__).parent

Expand Down
1 change: 0 additions & 1 deletion examples/example_spine_semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
Panoptic_Evaluator,
SemanticPair,
)
from panoptica.metrics import Metric

directory = turbopath(__file__).parent

Expand Down
23 changes: 16 additions & 7 deletions panoptica/_functionals.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from typing import TYPE_CHECKING
from multiprocessing import Pool

import numpy as np

from panoptica.metrics import _compute_instance_iou, Metric
from panoptica.metrics.iou import _compute_instance_iou
from panoptica.utils.constants import CCABackend
from panoptica.utils.numpy_utils import _get_bbox_nd

if TYPE_CHECKING:
from panoptica.metrics import Metric


def _calc_overlapping_labels(
prediction_arr: np.ndarray,
Expand Down Expand Up @@ -41,7 +45,7 @@ def _calc_matching_metric_of_overlapping_labels(
prediction_arr: np.ndarray,
reference_arr: np.ndarray,
ref_labels: tuple[int, ...],
matching_metric: Metric,
matching_metric: "Metric",
) -> list[tuple[float, tuple[int, int]]]:
"""Calculates the MatchingMetric for all overlapping labels (fast!)

Expand All @@ -54,15 +58,15 @@ def _calc_matching_metric_of_overlapping_labels(
list[tuple[float, tuple[int, int]]]: List of pairs in style: (iou, (ref_label, pred_label))
"""
instance_pairs = [
(reference_arr == i[0], prediction_arr == i[1], i[0], i[1])
(reference_arr, prediction_arr, i[0], i[1])
for i in _calc_overlapping_labels(
prediction_arr=prediction_arr,
reference_arr=reference_arr,
ref_labels=ref_labels,
)
]
with Pool() as pool:
mm_values = pool.starmap(matching_metric.value._metric_function, instance_pairs)
mm_values = pool.starmap(matching_metric.value, instance_pairs)

mm_pairs = [
(i, (instance_pairs[idx][2], instance_pairs[idx][3]))
Expand All @@ -79,7 +83,7 @@ def _calc_iou_of_overlapping_labels(
prediction_arr: np.ndarray,
reference_arr: np.ndarray,
ref_labels: tuple[int, ...],
pred_labels: tuple[int, ...],
**kwargs,
) -> list[tuple[float, tuple[int, int]]]:
"""Calculates the IOU for all overlapping labels (fast!)

Expand Down Expand Up @@ -156,7 +160,10 @@ def _calc_iou_matrix(
return iou_matrix


def _map_labels(arr: np.ndarray, label_map: dict[np.integer, np.integer]) -> np.ndarray:
def _map_labels(
arr: np.ndarray,
label_map: dict[np.integer, np.integer],
) -> np.ndarray:
"""
Maps labels in the given array according to the label_map dictionary.

Expand Down Expand Up @@ -212,7 +219,9 @@ def _connected_components(


def _get_paired_crop(
prediction_arr: np.ndarray, reference_arr: np.ndarray, px_pad: int = 2
prediction_arr: np.ndarray,
reference_arr: np.ndarray,
px_pad: int = 2,
):
assert prediction_arr.shape == reference_arr.shape

Expand Down
14 changes: 8 additions & 6 deletions panoptica/instance_approximator.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from abc import abstractmethod, ABC
from abc import ABC, abstractmethod

import numpy as np

from panoptica.utils.constants import CCABackend
from panoptica._functionals import _connected_components
from panoptica.utils.numpy_utils import _get_smallest_fitting_uint
from panoptica.utils.processing_pair import (
MatchedInstancePair,
SemanticPair,
UnmatchedInstancePair,
MatchedInstancePair,
)
from panoptica._functionals import _connected_components, CCABackend
from panoptica.utils.numpy_utils import _get_smallest_fitting_uint
from panoptica.timing import measure_time
import numpy as np


class InstanceApproximator(ABC):
Expand Down
7 changes: 4 additions & 3 deletions panoptica/instance_evaluator.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from multiprocessing import Pool

import numpy as np

from panoptica.metrics import Metric
from panoptica.panoptic_result import PanopticaResult
from panoptica.utils import EdgeCaseHandler
from panoptica.utils.processing_pair import MatchedInstancePair
from panoptica.metrics import Metric


def evaluate_matched_instance(
Expand Down Expand Up @@ -42,8 +43,8 @@ def evaluate_matched_instance(
score_dict: dict[Metric, list[float]] = {m: [] for m in eval_metrics}

reference_arr, prediction_arr = (
matched_instance_pair._reference_arr,
matched_instance_pair._prediction_arr,
matched_instance_pair.reference_arr,
matched_instance_pair.prediction_arr,
)
ref_matched_labels = matched_instance_pair.matched_instances

Expand Down
25 changes: 14 additions & 11 deletions panoptica/instance_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
_calc_matching_metric_of_overlapping_labels,
_map_labels,
)
from panoptica.metrics import Metric, _Metric
from panoptica.metrics import Metric
from panoptica.utils.processing_pair import (
InstanceLabelMap,
MatchedInstancePair,
Expand Down Expand Up @@ -98,10 +98,10 @@ def map_instance_labels(
>>> labelmap = [([1, 2], [3, 4]), ([5], [6])]
>>> result = map_instance_labels(unmatched_instance_pair, labelmap)
"""
prediction_arr = processing_pair._prediction_arr
prediction_arr = processing_pair.prediction_arr

ref_labels = processing_pair._ref_labels
pred_labels = processing_pair._pred_labels
ref_labels = processing_pair.ref_labels
pred_labels = processing_pair.pred_labels

ref_matched_labels = []
label_counter = int(max(ref_labels) + 1)
Expand Down Expand Up @@ -185,14 +185,14 @@ def _match_instances(
Returns:
Instance_Label_Map: The result of the instance matching.
"""
ref_labels = unmatched_instance_pair._ref_labels
ref_labels = unmatched_instance_pair.ref_labels

# Initialize variables for True Positives (tp) and False Positives (fp)
labelmap = InstanceLabelMap()

pred_arr, ref_arr = (
unmatched_instance_pair._prediction_arr,
unmatched_instance_pair._reference_arr,
unmatched_instance_pair.prediction_arr,
unmatched_instance_pair.reference_arr,
)
mm_pairs = _calc_matching_metric_of_overlapping_labels(
pred_arr, ref_arr, ref_labels, matching_metric=self.matching_metric
Expand Down Expand Up @@ -259,19 +259,22 @@ def _match_instances(
Returns:
Instance_Label_Map: The result of the instance matching.
"""
ref_labels = unmatched_instance_pair._ref_labels
ref_labels = unmatched_instance_pair.ref_labels
# pred_labels = unmatched_instance_pair._pred_labels

# Initialize variables for True Positives (tp) and False Positives (fp)
labelmap = InstanceLabelMap()
score_ref: dict[int, float] = {}

pred_arr, ref_arr = (
unmatched_instance_pair._prediction_arr,
unmatched_instance_pair._reference_arr,
unmatched_instance_pair.prediction_arr,
unmatched_instance_pair.reference_arr,
)
mm_pairs = _calc_matching_metric_of_overlapping_labels(
pred_arr, ref_arr, ref_labels, matching_metric=self.matching_metric
prediction_arr=pred_arr,
reference_arr=ref_arr,
ref_labels=ref_labels,
matching_metric=self.matching_metric,
)

# Loop through matched instances to compute PQ components
Expand Down
21 changes: 13 additions & 8 deletions panoptica/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@
_average_surface_distance,
_average_symmetric_surface_distance,
)
from panoptica.metrics.cldice import (
_compute_centerline_dice,
_compute_centerline_dice_coefficient,
)
from panoptica.metrics.dice import (
_compute_dice_coefficient,
_compute_instance_volumetric_dice,
)
from panoptica.metrics.iou import (
_compute_instance_iou,
_compute_iou,
)
from panoptica.metrics.cldice import (
_compute_centerline_dice,
_compute_centerline_dice_coefficient,
from panoptica.metrics.iou import _compute_instance_iou, _compute_iou
from panoptica.metrics.metrics import (
Evaluation_List_Metric,
Evaluation_Metric,
Metric,
MetricCouldNotBeComputedException,
MetricMode,
MetricType,
_Metric,
)
from panoptica.metrics.metrics import Metric, _Metric, MetricMode
23 changes: 15 additions & 8 deletions panoptica/metrics/assd.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import numpy as np
from scipy.ndimage import _ni_support
from scipy.ndimage import _ni_support, binary_erosion, generate_binary_structure
from scipy.ndimage._nd_image import euclidean_feature_transform
from scipy.ndimage.morphology import binary_erosion, generate_binary_structure


def _average_symmetric_surface_distance(
Expand All @@ -11,13 +10,20 @@ def _average_symmetric_surface_distance(
connectivity=1,
*args,
) -> float:
"""ASSD is computed by computing the average of the bidrectionally computed ASD."""
assd = np.mean(
(
_average_surface_distance(
prediction, reference, voxelspacing, connectivity
reference=prediction,
prediction=reference,
voxelspacing=voxelspacing,
connectivity=connectivity,
),
_average_surface_distance(
reference, prediction, voxelspacing, connectivity
reference=reference,
prediction=prediction,
voxelspacing=voxelspacing,
connectivity=connectivity,
),
)
)
Expand All @@ -38,6 +44,7 @@ def __surface_distances(reference, prediction, voxelspacing=None, connectivity=1
prediction = np.atleast_1d(prediction.astype(bool))
reference = np.atleast_1d(reference.astype(bool))
if voxelspacing is not None:
# Protected access presented by Scipy
voxelspacing = _ni_support._normalize_sequence(voxelspacing, prediction.ndim)
voxelspacing = np.asarray(voxelspacing, dtype=np.float64)
if not voxelspacing.flags.contiguous:
Expand Down Expand Up @@ -70,7 +77,7 @@ def __surface_distances(reference, prediction, voxelspacing=None, connectivity=1


def _distance_transform_edt(
input: np.ndarray,
input_array: np.ndarray,
sampling=None,
return_distances=True,
return_indices=False,
Expand All @@ -83,12 +90,12 @@ def _distance_transform_edt(
# if not sampling.flags.contiguous:
# sampling = sampling.copy()

ft = np.zeros((input.ndim,) + input.shape, dtype=np.int32)
ft = np.zeros((input_array.ndim,) + input_array.shape, dtype=np.int32)

euclidean_feature_transform(input, sampling, ft)
euclidean_feature_transform(input_array, sampling, ft)
# if requested, calculate the distance transform
if return_distances:
dt = ft - np.indices(input.shape, dtype=ft.dtype)
dt = ft - np.indices(input_array.shape, dtype=ft.dtype)
dt = dt.astype(np.float64)
# if sampling is not None:
# for ii in range(len(sampling)):
Expand Down
2 changes: 1 addition & 1 deletion panoptica/metrics/cldice.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from skimage.morphology import skeletonize, skeletonize_3d
import numpy as np
from skimage.morphology import skeletonize, skeletonize_3d


def cl_score(volume: np.ndarray, skeleton: np.ndarray):
Expand Down
Loading
Loading