camfi.util module

class camfi.util.SubDirDict(mapping: Optional[Mapping[pathlib.Path, camfi.util.V]] = None)

Bases: Mapping[pathlib.Path, camfi.util.V]

A mapping from subdirectory Paths to V which returns self[‘foo/bar’] if ‘foo/bar/baz’ is missing from the available keys.

Examples

>>> d = SubDirDict()
>>> d["foo"] = "foo"
>>> d["foo"]
'foo'
>>> d["foo/bar"]
'foo'
>>> d["foo/bar/baz"]
'foo'
>>> d["bar"]
Traceback (most recent call last):
...
KeyError: "'bar' not in SubDirDict({Path('foo'): 'foo'})"

SubDirDict can be initialised from a dictionary

>>> SubDirDict({"foo": "bar", "foobar": "baz"})
SubDirDict({Path('foo'): 'bar', Path('foobar'): 'baz'})
__init__(mapping: Optional[Mapping[pathlib.Path, camfi.util.V]] = None)

Initialises SubDirDict.

Parameters

mapping (Optional[Mapping[Path, V]]) – E.g. an instance of type dictt[Path, V]

__repr__()

String representation of SubDirDict. Uses “Path” instead of platform dependant “PosixPath” or “WindowsPath”.

__weakref__

list of weak references to the object (if defined)

items() a set-like object providing a view on D’s items
keys() a set-like object providing a view on D’s keys
values() an object providing a view on D’s values
class camfi.util.Timezone(v)

Bases: datetime.tzinfo

Provides pydantic validation for timezones.

__init__(v)
__repr__()

Return repr(self).

__str__()

Return str(self).

__weakref__

list of weak references to the object (if defined)

dst(dt: Optional[datetime.datetime]) Optional[datetime.timedelta]

datetime -> DST offset as timedelta positive east of UTC.

fromutc(dt: datetime.datetime) datetime.datetime

datetime in UTC -> datetime in local time.

tzname(dt: Optional[datetime.datetime]) Optional[str]

datetime -> string name of time zone.

utcoffset(dt: Optional[datetime.datetime]) Optional[datetime.timedelta]

datetime -> timedelta showing offset from UTC, negative values indicating West of UTC

camfi.util.dilate_idx(rr: numpy.ndarray, cc: numpy.ndarray, d: int, img_shape: Optional[tuple] = None) tuple

Takes index arrays rr and cc and performs a morphological dilation of size d on them.

Parameters
  • rr (np.ndarray) – Row indices.

  • cc (np.ndarray) – Column indices (must have same shape as rr).

  • d (int) – Dilation factor, must be at least 1 (or a ValueError is raised).

  • img_shape (Optional[tuple[int, int]]) – Shape of image (rows, columns). Indices which lie outside this will be ommitted.

Returns

  • rr_dilated (np.ndarray) – Row indices after morphological dilation.

  • cc_dilated (np.ndarray) – Column indices after morphological dilation.

Examples

>>> a = np.array([[0, 0, 0, 0, 0],
...               [0, 0, 1, 0, 0],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0]])
>>> rr, cc = np.nonzero(a)
>>> rr_dilated, cc_dilated = dilate_idx(rr, cc, 1)
>>> a[rr_dilated, cc_dilated] = 1
>>> a
array([[0, 0, 1, 0, 0],
       [0, 1, 1, 1, 0],
       [0, 0, 1, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]])

If shape is given, omits indices larger than the dimensions given

>>> a = np.array([[0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 1],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0]])
>>> rr, cc = np.nonzero(a)
>>> rr_dilated, cc_dilated = dilate_idx(rr, cc, 1, a.shape)
>>> a[rr_dilated, cc_dilated] = 1
>>> a
array([[0, 0, 0, 0, 1],
       [0, 0, 0, 1, 1],
       [0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]])

If we didn’t give the shape argument in the above example, we get an IndexError

>>> a = np.array([[0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 1],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0]])
>>> rr, cc = np.nonzero(a)
>>> rr_dilated, cc_dilated = dilate_idx(rr, cc, 1)
>>> a[rr_dilated, cc_dilated] = 1
Traceback (most recent call last):
...
IndexError: index 5 is out of bounds for axis 1 with size 5

But we don’t need the shape parameter to filter out negative indices

>>> a = np.array([[1, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0]])
>>> rr, cc = np.nonzero(a)
>>> rr_dilated, cc_dilated = dilate_idx(rr, cc, 1)
>>> a[rr_dilated, cc_dilated] = 1
>>> a
array([[1, 1, 0, 0, 0],
       [1, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]])

Dilation is based on euclidean distance

>>> a = np.array([[0, 0, 0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0, 0, 0],
...               [0, 0, 0, 1, 0, 0, 0],
...               [0, 0, 0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0, 0, 0],
...               [0, 0, 0, 0, 0, 0, 0]])
>>> rr, cc = np.nonzero(a)
>>> rr_dilated, cc_dilated = dilate_idx(rr, cc, 3, a.shape)
>>> a[rr_dilated, cc_dilated] = 1
>>> a
array([[0, 0, 0, 1, 0, 0, 0],
       [0, 1, 1, 1, 1, 1, 0],
       [0, 1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 1, 1],
       [0, 1, 1, 1, 1, 1, 0],
       [0, 1, 1, 1, 1, 1, 0],
       [0, 0, 0, 1, 0, 0, 0]])

If input is sorted, then the ouptut will be too (with precedence rr, cc)

>>> rr, cc = np.array([50]), np.array([50])
>>> dilate_idx(rr, cc, 1, (100, 100))
(array([49, 50, 50, 50, 51]), array([50, 49, 50, 51, 50]))

If a non-positive dilation factor is given, a ValueError is raised

>>> dilate_idx(1, 2, 0)
Traceback (most recent call last):
...
ValueError: d=0. Should be positive.
camfi.util.endpoint_truncate(fit_mask_vals: numpy.ndarray, n: pydantic.types.NonNegativeInt) tuple

An implementation of an endpoint_method.

Parameters
  • fit_mask_vals (np.ndarray) – Array to find the endpoints of.

  • n (NonNegativeInt) – Number of values to truncate off start and end of fit_mask_vals.

Returns

  • start_index (NonNegativeInt) – Index of endpoint of polyline annotation.

  • end_index (NonNegativeInt) – Index of endpoint of polyline annotation.

camfi.util.smallest_enclosing_circle(points: Union[Iterable[tuple], numpy.ndarray]) tuple

Performs Welzl’s algorithm to find the smallest enclosing circle of a set of points in a cartesian plane.

Parameters

points (Union[Iterable[tuple[float, float]], np.ndarray]) – Iterable of 2-tuples or (N, 2)-array, with each tuple defining the coordinates of a point.

Returns

  • x (float) – x-coordinate of centre of circle.

  • y (float) – y-coordinate of centre of circle.

  • r (float) – Radius of circle.

Examples

If no points are given, values are still returned:

>>> smallest_enclosing_circle([])
(0.0, 0.0, 0.0)

If one point is given, r will be 0.0:

>>> smallest_enclosing_circle([(1.0, 2.0)])
(1.0, 2.0, 0.0)

Two points trivial case:

>>> smallest_enclosing_circle([(0.0, 0.0), (2.0, 0.0)])
(1.0, 0.0, 1.0)

Three points trivial case:

>>> np.allclose(
...     smallest_enclosing_circle([(0.0, 0.0), (2.0, 0.0), (1.0, sqrt(3))]),
...     (1.0, sqrt(3) / 3, 2 * sqrt(3) / 3)
... )
True

Extra points within the circle don’t affect the circle:

>>> np.allclose(
...     smallest_enclosing_circle([
...                                (0.0, 0.0),
...                                (2.0, 0.0),
...                                (1.0, sqrt(3)),
...                                (0.5, 0.5)]),
...     (1.0, sqrt(3) / 3, 2 * sqrt(3) / 3)
... )
True

If points are inscribed on a circle, the correct circle is also given:

>>> np.allclose(
...     smallest_enclosing_circle([(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0)]),
...     (1.0, 1.0, sqrt(2))
... )
True
camfi.util.weighted_intersection_over_minimum(mask0: torch.Tensor, mask1: torch.Tensor) float

Calculates the weighted intersection over minimum (IoM) between two segmentation masks.

Parameters
  • mask0 (torch.Tensor) – Instance segmentation mask to compare to mask1.

  • mask1 (torch.Tensor) – Instance segmentation mask to compare to mask0. Should have the same shape as mask0.

Returns

iom – Weighted intersection over union of mask0 and mask1.

Return type

float

Examples

>>> mask0 = torch.tensor([0.0, 0.5, 0.5, 0.0])
>>> mask1 = torch.tensor([1.0, 1.0, 0.0, 0.0])
>>> weighted_intersection_over_minimum(mask0, mask1)
0.5