Skip to content

Commit

Permalink
Merge pull request #151 from BrainLesion/coverage-increase
Browse files Browse the repository at this point in the history
added more coverage, removed deprecated code
  • Loading branch information
Hendrik-code authored Oct 9, 2024
2 parents cf28f50 + e230f5f commit e151b86
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 92 deletions.
26 changes: 0 additions & 26 deletions panoptica/panoptica_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,29 +563,3 @@ def sq_rvd_std(res: PanopticaResult):


# endregion


def _build_global_bin_metric_function(metric: Metric):

def function_template(res: PanopticaResult):
if metric not in res._global_metrics:
raise MetricCouldNotBeComputedException(f"Global Metric {metric} not set")
if res.tp == 0:
is_edgecase, result = res._edge_case_handler.handle_zero_tp(
metric, res.tp, res.num_pred_instances, res.num_ref_instances
)
if is_edgecase:
return result
pred_binary = res._prediction_arr.copy()
ref_binary = res._reference_arr.copy()
pred_binary[pred_binary != 0] = 1
ref_binary[ref_binary != 0] = 1
return metric(
reference_arr=res._reference_arr,
prediction_arr=res._prediction_arr,
)

return lambda x: function_template(x)


# endregion
65 changes: 2 additions & 63 deletions panoptica/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,67 +43,6 @@ def _save_yaml(data_dict: dict | object, out_file: str | Path, registered_class=
print(f"Saved config into {out_file}")


####################
# TODO Merge into SupportsConfig
class Configuration:
"""General Configuration class that handles yaml"""

_data_dict: dict
_registered_class = None

def __init__(self, data_dict: dict, registered_class=None) -> None:
assert isinstance(data_dict, dict)
self._data_dict = data_dict
if registered_class is not None:
self.register_to_class(registered_class)

def register_to_class(self, cls):
global supported_helper_classes
if cls not in supported_helper_classes:
supported_helper_classes.append(cls)
self._registered_class = cls
return self

@classmethod
def save_from_object(cls, obj: object, file: str | Path):
_save_yaml(obj, file, registered_class=type(obj))
# return Configuration.load(file, registered_class=type(obj))

@classmethod
def load(cls, file: str | Path, registered_class=None):
data = _load_yaml(file, registered_class)
assert isinstance(
data, dict
), f"The config at {file} is registered to a class. Use load_as_object() instead"
return Configuration(data, registered_class=registered_class)

@classmethod
def load_as_object(cls, file: str | Path, registered_class=None):
data = _load_yaml(file, registered_class)
assert not isinstance(
data, dict
), f"The config at {file} is not registered to a class. Use load() instead"
return data

def save(self, out_file: str | Path):
_save_yaml(self._data_dict, out_file)

def cls_object_from_this(self):
assert self._registered_class is not None
return self._registered_class(**self._data_dict)

@property
def data_dict(self):
return self._data_dict

@property
def cls(self):
return self._registered_class

def __str__(self) -> str:
return f"Config({self.cls.__name__ if self.cls is not None else 'NoClass'} = {self.data_dict})" # type: ignore


#########
# Universal Functions
#########
Expand All @@ -118,7 +57,7 @@ def _load_from_config(cls, path: str | Path):
if isinstance(path, str):
path = Path(path)
assert path.exists(), f"load_from_config: {path} does not exist"
obj = Configuration.load_as_object(path, registered_class=cls)
obj = _load_yaml(path, registered_class=cls)
assert isinstance(obj, cls), f"Loaded config was not for class {cls.__name__}"
return obj

Expand All @@ -132,7 +71,7 @@ def _load_from_config_name(cls, name: str):
def _save_to_config(obj, path: str | Path):
if isinstance(path, str):
path = Path(path)
Configuration.save_from_object(obj, path)
_save_yaml(obj, path, registered_class=type(obj))


def _save_to_config_by_name(obj, name: str):
Expand Down
4 changes: 2 additions & 2 deletions panoptica/utils/numpy_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def _unique_without_zeros(arr: np.ndarray) -> np.ndarray:
Issues a warning if negative values are present.
"""
if np.any(arr < 0):
warnings.warn("Negative values are present in the input array.")
warnings.warn("Negative values are present in the input array.", UserWarning)

return np.unique(arr[arr != 0])

Expand All @@ -33,7 +33,7 @@ def _count_unique_without_zeros(arr: np.ndarray) -> int:
int: Number of unique elements excluding zeros.
"""
if np.any(arr < 0):
warnings.warn("Negative values are present in the input array.")
warnings.warn("Negative values are present in the input array.", UserWarning)

return len(_unique_without_zeros(arr))

Expand Down
42 changes: 42 additions & 0 deletions unit_tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ def setUp(self) -> None:

def test_rvd_case_simple_identical(self):

pred_arr, ref_arr = case_simple_identical()
rvd = Metric.RVD(
reference_arr=ref_arr,
prediction_arr=pred_arr,
ref_instance_idx=1,
pred_instance_idx=1,
)
self.assertEqual(rvd, 0.0)

def test_rvd_case_simple_identical_idx(self):

pred_arr, ref_arr = case_simple_identical()
rvd = Metric.RVD(reference_arr=ref_arr, prediction_arr=pred_arr)
self.assertEqual(rvd, 0.0)
Expand Down Expand Up @@ -130,6 +141,17 @@ def test_dsc_case_simple_identical(self):
dsc = Metric.DSC(reference_arr=ref_arr, prediction_arr=pred_arr)
self.assertEqual(dsc, 1.0)

def test_dsc_case_simple_identical_idx(self):

pred_arr, ref_arr = case_simple_identical()
dsc = Metric.DSC(
reference_arr=ref_arr,
prediction_arr=pred_arr,
ref_instance_idx=1,
pred_instance_idx=1,
)
self.assertEqual(dsc, 1.0)

def test_dsc_case_simple_nooverlap(self):

pred_arr, ref_arr = case_simple_nooverlap()
Expand Down Expand Up @@ -159,6 +181,16 @@ def test_st_case_simple_identical(self):
st = Metric.ASSD(reference_arr=ref_arr, prediction_arr=pred_arr)
self.assertEqual(st, 0.0)

def test_st_case_simple_identical_idx(self):
pred_arr, ref_arr = case_simple_identical()
st = Metric.ASSD(
reference_arr=ref_arr,
prediction_arr=pred_arr,
ref_instance_idx=1,
pred_instance_idx=1,
)
self.assertEqual(st, 0.0)

def test_st_case_simple_nooverlap(self):
pred_arr, ref_arr = case_simple_nooverlap()
st = Metric.ASSD(reference_arr=ref_arr, prediction_arr=pred_arr)
Expand All @@ -185,6 +217,16 @@ def test_st_case_simple_identical(self):
st = Metric.clDSC(reference_arr=ref_arr, prediction_arr=pred_arr)
self.assertEqual(st, 1.0)

def test_st_case_simple_identical_idx(self):
pred_arr, ref_arr = case_simple_identical()
st = Metric.clDSC(
reference_arr=ref_arr,
prediction_arr=pred_arr,
ref_instance_idx=1,
pred_instance_idx=1,
)
self.assertEqual(st, 1.0)

def test_st_case_simple_nooverlap(self):
pred_arr, ref_arr = case_simple_nooverlap()
st = Metric.clDSC(reference_arr=ref_arr, prediction_arr=pred_arr)
Expand Down
26 changes: 25 additions & 1 deletion unit_tests/test_panoptic_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@

from panoptica import InputType
from panoptica.instance_approximator import ConnectedComponentsInstanceApproximator
from panoptica.instance_matcher import MaximizeMergeMatching, NaiveThresholdMatching
from panoptica.instance_matcher import (
MaximizeMergeMatching,
NaiveThresholdMatching,
InstanceLabelMap,
)
from panoptica.metrics import Metric
from panoptica.instance_evaluator import (
evaluate_matched_instance,
Expand All @@ -33,6 +37,26 @@ def setUp(self) -> None:
os.environ["PANOPTICA_CITATION_REMINDER"] = "False"
return super().setUp()

def test_labelmap(self):
labelmap = InstanceLabelMap()

labelmap.add_labelmap_entry(1, 1)
labelmap.add_labelmap_entry([2, 3], 2)

with self.assertRaises(Exception):
labelmap.add_labelmap_entry(1, 1)
labelmap.add_labelmap_entry(1, 2)

self.assertTrue(labelmap.contains_and(None, None))
self.assertTrue(labelmap.contains_and(1, 1))
self.assertTrue(not labelmap.contains_and(1, 3))
self.assertTrue(not labelmap.contains_and(4, 1))

print(labelmap)

with self.assertRaises(Exception):
labelmap.labelmap = {}


class Test_Panoptica_Instance_Evaluation(unittest.TestCase):
def setUp(self) -> None:
Expand Down
56 changes: 56 additions & 0 deletions unit_tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Call 'python -m unittest' on this folder
# coverage run -m unittest
# coverage report
# coverage html
import os
import unittest
import numpy as np
from panoptica.utils.numpy_utils import (
_unique_without_zeros,
_count_unique_without_zeros,
_get_smallest_fitting_uint,
)
from panoptica.utils.citation_reminder import citation_reminder


class Test_Citation_Reminder(unittest.TestCase):
def setUp(self) -> None:
os.environ["PANOPTICA_CITATION_REMINDER"] = "True"
return super().setUp()

def test_citation_code(self):

@citation_reminder
def foo():
return "bar"

foo()


class Test_Numpy_Utils(unittest.TestCase):
def setUp(self) -> None:
os.environ["PANOPTICA_CITATION_REMINDER"] = "False"
return super().setUp()

def test_np_unique(self):
a = np.array([0, 1, 2, 3, 6])
b = _unique_without_zeros(a)

self.assertTrue(b[0] == 1)
self.assertTrue(b[1] == 2)
self.assertTrue(b[2] == 3)
self.assertTrue(b[3] == 6)
self.assertEqual(b.shape[0], 4)

with self.assertWarns(UserWarning):
a = np.array([0, 1, -2, 3, -6])
b = _unique_without_zeros(a)

def test_np_count_unique(self):
a = np.array([0, 1, 2, 3, 6])
b = _count_unique_without_zeros(a)
self.assertEqual(b, 4)
#
with self.assertWarns(UserWarning):
a = np.array([0, 1, -2, 3, -6])
b = _count_unique_without_zeros(a)

0 comments on commit e151b86

Please sign in to comment.