Source code for deepvisiontools.formats.formats
from __future__ import annotations
from typing import Tuple
from torch import Tensor
from deepvisiontools.formats.base_data import (
InstanceMaskData,
BboxData,
SemanticMaskData,
)
from deepvisiontools.formats.base_formats import BaseFormat, BaseSemanticFormat
import torch
import deepvisiontools.formats.errors as er
from typing import Union, List, Literal
from deepvisiontools import Configuration
import copy
[docs]
class SemanticMaskFormat(BaseSemanticFormat):
"""Class for Semantic Mask format (Child class of *BaseSemanticFormat*).
Args:
data (SemanticMaskData)
scores (Tensor | None, optional). Defaults to None.
Properties & attributes : cf *BaseSemanticFormat*
**Methods**
"""
[docs]
@classmethod
def from_instance_mask(
cls, mask: InstanceMaskFormat, scores: Tensor | None = None
) -> BboxFormat:
"""Create a SemanticMaskFormat from InstanceMaskFormat
Args:
mask (InstanceMaskFormat)
Returns:
SemanticMaskFormat
"""
semanticmask = SemanticMaskData(mask.export_semantic_mask())
return SemanticMaskFormat(semanticmask, scores=None)
[docs]
@classmethod
def empty(cls, canvas_size: Tuple[int], scores: Tensor | None = None) -> BboxFormat:
"""Create an empty SemanticMaskFormat of dimension canvas_size
Args:
canvas_size (Tuple[int, int])
Returns:
SemanticMaskFormat
"""
return SemanticMaskFormat(SemanticMaskData.empty(canvas_size), scores=scores)
def __init__(self, data: SemanticMaskData, scores: Tensor | None = None):
assert isinstance(
data, SemanticMaskData
), f"Expect to have SemanticMaskData data for SemanticMaskFormat, got {type(data)}."
super().__init__(data, scores)
def generate_scores_from_mask(self):
if Configuration().num_classes == 1:
scores = copy.deepcopy(self.data.value)[None, :]
else:
scores = copy.deepcopy(self.data.value)
scores = (
torch.nn.functional.one_hot(scores, Configuration().num_classes)
.float()
.permute(2, 0, 1)
) # These are perfects logits
return SemanticMaskFormat(self.data, scores=scores)
[docs]
class InstanceMaskFormat(BaseFormat):
"""Class for Instance Segmentation Format (Child class of *BaseFormat*). contains *InstanceMaskData* value, labels and scores.
Args:
data (InstanceMaskData)
labels (Tensor)
scores (Tensor | None, optional). Defaults to None.
Properties & attributes : cf *BaseFormat*
**Methods**
"""
[docs]
@classmethod
def empty(cls, canvas_size: Tuple[int, int]) -> InstanceMaskFormat:
"""Create an empty InstanceMaskFormat of dimension canvas_size
Args:
canvas_size (Tuple[int, int])
Returns:
InstanceMaskFormat
"""
return InstanceMaskFormat(
InstanceMaskData.empty(canvas_size), labels=torch.tensor([])
)
def __init__(
self, data: InstanceMaskData, labels: Tensor, scores: Tensor | None = None
):
assert isinstance(
data, InstanceMaskData
), f"Expect to have InstanceMaskData data for InstanceMaskFormat, got {type(data)}."
super().__init__(data, labels, scores)
[docs]
def export_semantic_mask(self) -> Tensor:
"""From self (data.value and labels) generate a semantic mask by replacing objects indexing by their corresponding labels.
Note that labels are shifted by 1 as 0 is preserved for background
Returns:
``Tensor``:
- Semantic mask
"""
inst_mask, _ = self.sanitize()
semantic_mask = torch.zeros(self.canvas_size).to(self.device)
semantic_mask = semantic_mask.long()
if self.nb_object == 0:
return semantic_mask
for i, lab in enumerate(self.labels):
semantic_mask[inst_mask.data.value == (i + 1)] = lab.item() + 1
return semantic_mask
[docs]
class BboxFormat(BaseFormat):
"""Class for Bounding box format (Child class of *BaseFormat*). contains *BBoxData* value, labels and scores.
Args:
data (BBoxData)
labels (Tensor)
scores (Tensor | None, optional). Defaults to None.
Properties & attributes : cf *BaseFormat*
**Methods**
"""
[docs]
@classmethod
def from_instance_mask(cls, mask: InstanceMaskFormat) -> BboxFormat:
"""Create a BboxFormat from InstanceMaskFormat
Args:
mask (InstanceMaskFormat)
Returns:
BboxFormat
"""
mask, _ = mask.sanitize()
boxes = BboxFormat(BboxData.from_mask(mask.data), mask.labels, mask.scores)
assert (
mask.nb_object == boxes.nb_object
), "Different number of objects when creating boxes from instance masks, you may need to increase the mask_min_size parameter in Configuration()"
return boxes
[docs]
@classmethod
def empty(cls, canvas_size: Tuple[int]) -> BboxFormat:
"""Create an empty BboxFormat of dimension canvas_size
Args:
canvas_size (Tuple[int, int])
Returns:
BboxFormat
"""
return BboxFormat(BboxData.empty(canvas_size), labels=torch.tensor([]))
def __init__(self, data: BboxData, labels: Tensor, scores: Tensor | None = None):
assert isinstance(
data, BboxData
), f"Expect to have BboxData data for BboxFormat, got {type(data)}."
super().__init__(data, labels, scores)
[docs]
class BatchedFormat:
"""A class that handles a list of Formats
Args:
formats (``List[BaseFormat]``)
Attributes
----------
Properties:
- device (``Literal["cpu", "cuda"]``): When changed, move all formats into same device.
- formats (``List[BaseFormat]``): contains all stored formats.
- size (``int``): number of formats
**Methods**
"""
[docs]
@classmethod
def cat(self, batches: List[BatchedFormat]):
"""batches need to be a list of BatchedFormat of same type !"""
new_list = []
for batch in batches:
new_list += batch.formats
return BatchedFormat(new_list)
def __init__(self, formats: List[BaseFormat]):
formats_check = [isinstance(form, BaseFormat) for form in formats]
assert all(
formats_check
), f"Some targets are not Format, got {[type(form) for form in formats]}"
self.formats: List[BaseFormat] = formats
self.sanitize()
[docs]
def sanitize(self):
"""Apply sanitize to all formats"""
self.formats = [form.sanitize()[0] for form in self.formats]
@property
def formats(self):
return self._formats
@formats.setter
def formats(self, val: List[BaseFormat]):
self._canvas_size = val[0].canvas_size if len(val) != 0 else None
self._formats = val
self._size = len(val)
self.device = Configuration().device
@property
def device(self):
return self._device
@device.setter
def device(self, val):
for form in self.formats:
form.device = val
self._device = val
@property
def size(self):
return self._size
@size.setter
def size(self, val):
raise er.ProtectedAttributeException()
def __getitem__(self, indexes: Union[int, slice, Tensor]) -> BatchedFormat:
if isinstance(indexes, Tensor):
if indexes.dtype == torch.bool:
indexes = indexes.cpu().tolist()
indexes = [i for i, val in enumerate(indexes) if val]
else:
indexes = indexes.cpu().tolist()
indexes = [i in indexes for i in range(len(indexes))]
if isinstance(indexes, list):
new_batch = [self.formats[i] for i in indexes]
else:
new_batch = self.formats[indexes]
if not isinstance(new_batch, list):
new_batch = [new_batch]
return BatchedFormat(
new_batch
) # TODO : this has been modified, originally it was only the list ... It must be checked intensivelly !!!
def set_bboxes_format(self, val: Literal["XYXY", "XYWH", "CXCYWH"]):
for form in self.formats:
assert isinstance(
form, BboxFormat
), f"In BatchedFormat: can't change bbox format of object {type(form)}"
form.data.format = val
def __next__(self):
_next: BaseFormat = next(self.__iter__())
return _next
def __iter__(self):
return iter(self.formats)
def __add__(self, form2: BatchedFormat):
return BatchedFormat(self.formats + form2.formats)