Skip to content

Commit

Permalink
Merge pull request #12 from schencej/add-image-modality
Browse files Browse the repository at this point in the history
Add interface to handle image matrices directly
  • Loading branch information
Purg authored Nov 29, 2021
2 parents 7d99e4a + 3f4c7a2 commit 901d98e
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 4 deletions.
5 changes: 5 additions & 0 deletions docs/detection_interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ ImageMatrixObjectDetector
+++++++++++++++++++++++++
.. autoclass:: smqtk_detection.interfaces.object_detector.ImageMatrixObjectDetector
:members:

DetectImageObjects
++++++++++++++++++
.. autoclass:: smqtk_detection.interfaces.detect_image_objects.DetectImageObjects
:members:
5 changes: 5 additions & 0 deletions docs/release_notes/pending_release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@ Pending Release Notes
Updates / New Features
----------------------

Features

* Created `DetectImageObjects` interface and accompanying example
implementation, `RandomDetector` to handle image matrices directly.

Fixes
-----
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pytest-cov = "^2.8.1"
[tool.poetry.plugins."smqtk_plugins"]
# Detection Element
"smqtk_detection.impls.detection_element.memory" = "smqtk_detection.impls.detection_element.memory"
"smqtk_detection.impls.detect_image_objects.random_detector" = "smqtk_detection.impls.detect_image_objects.random_detector"

[tool.pytest.ini_options]
addopts = [
Expand Down
Empty file.
68 changes: 68 additions & 0 deletions smqtk_detection/impls/detect_image_objects/random_detector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from random import randrange
import numpy as np
from typing import Iterable, Tuple, Dict, Hashable, Sequence

from smqtk_detection.interfaces.detect_image_objects import DetectImageObjects
from smqtk_detection.utils.bbox import AxisAlignedBoundingBox


class RandomDetector(DetectImageObjects):
"""
Example implementation of the `DetectImageObjects` interface. An instance
of this class acts as a functor to generate paired bounding boxes and
classification maps for objects detected in a set of images.
"""

def detect_objects(
self,
img_iter: Iterable[np.ndarray]
) -> Iterable[Iterable[Tuple[AxisAlignedBoundingBox, Dict[Hashable, float]]]]:
"""
Return random set of detections for each image in the input set.
"""

dets = []

for img in img_iter:
img_h = img.shape[0]
img_w = img.shape[1]

num_dets = randrange(10)

dets.append(
[(
self._gen_random_bbox(img_w, img_h),
self._gen_random_class_map([0, 1, 2])
) for _ in range(num_dets)]
)

return dets

def _gen_random_bbox(self, img_w: int, img_h: int) -> AxisAlignedBoundingBox:
"""
Creates `AxisAlignedBoundingBox` object with random vertices within
passed image size.
"""

min_vertex = [randrange(int(img_w/2)), randrange(int(img_h/2))]
max_vertex = [randrange(int(img_w/2), img_w), randrange(int(img_h/2), img_h)]

return AxisAlignedBoundingBox(min_vertex, max_vertex)

def _gen_random_class_map(self, classes: Sequence) -> Dict[Hashable, float]:
"""
Creates dictionary of random classification scores for the list of
input classes.
"""

scores = np.random.rand(len(classes))
scores = scores / scores.sum()

d = {}
for i, c in enumerate(classes):
d[c] = scores[i]

return d

def get_config(self) -> dict:
return {}
39 changes: 39 additions & 0 deletions smqtk_detection/interfaces/detect_image_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import abc
from typing import Iterable, Hashable, Dict, Tuple
import numpy as np

from smqtk_core import Configurable, Pluggable
from smqtk_detection.utils.bbox import AxisAlignedBoundingBox


class DetectImageObjects (Configurable, Pluggable):
"""
Algorithm that generates object bounding boxes and classification maps for
a set of input image matricies as ``numpy.ndarray`` type arrays.
"""

@abc.abstractmethod
def detect_objects(
self,
img_iter: Iterable[np.ndarray]
) -> Iterable[Iterable[Tuple[AxisAlignedBoundingBox, Dict[Hashable, float]]]]:
"""
Generate paired bounding boxes and classification maps for detected
objects in the given set of images.
:param img_iter: Iterable of input images as numpy arrays.
:return: Iterable of sets of paired bounding boxes and classification
maps. Each set is the collection of detections for the
corresponding input image.
"""

def __call__(
self,
img_iter: Iterable[np.ndarray]
) -> Iterable[Iterable[Tuple[AxisAlignedBoundingBox, Dict[Hashable, float]]]]:
"""
Calls `detect_objects() with the given iterable set of images.`
"""

return self.detect_objects(img_iter)
8 changes: 4 additions & 4 deletions smqtk_detection/utils/bbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from smqtk_core import Plugfigurable

from typing import Type, Union, Optional, List, Any
from typing import Type, Union, Optional, Sequence


class AxisAlignedBoundingBox (Plugfigurable):
Expand Down Expand Up @@ -36,7 +36,7 @@ class AxisAlignedBoundingBox (Plugfigurable):
EQUALITY_ATOL = 1.e-8
EQUALITY_RTOL = 1.e-5

def __init__(self, min_vertex: List[Union[int, float]], max_vertex: List[Union[int, float]]) -> None:
def __init__(self, min_vertex: Sequence[Union[int, float]], max_vertex: Sequence[Union[int, float]]) -> None:
"""
Create a new AxisAlignedBoundingBox from the given minimum and maximum
euclidean-space vertex.
Expand Down Expand Up @@ -116,7 +116,7 @@ def __getstate__(self) -> tuple:
def __setstate__(self, state: Union[list, tuple]) -> None:
self._set_vertices(*state)

def _set_vertices(self, min_v: List[Any], max_v: List[Any]) -> None:
def _set_vertices(self, min_v: Sequence[Union[int, float]], max_v: Sequence[Union[int, float]]) -> None:
self.min_vertex = numpy.asarray(min_v)
self.min_vertex.flags.writeable = False
self.max_vertex = numpy.asarray(max_v)
Expand Down Expand Up @@ -148,7 +148,7 @@ def intersection(self, other: Type["AxisAlignedBoundingBox"]) -> Optional["AxisA
# the intersection's maximum and minimum vertices is <= 0.
if (inter_max_v - inter_min_v).min() <= 0:
return None
return AxisAlignedBoundingBox(inter_min_v, inter_max_v)
return AxisAlignedBoundingBox(inter_min_v.tolist(), inter_max_v.tolist())

@property
def ndim(self) -> int:
Expand Down

0 comments on commit 901d98e

Please sign in to comment.