deepvisiontools 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepvisiontools/__init__.py +16 -0
- deepvisiontools/config/__init__.py +3 -0
- deepvisiontools/config/config.py +176 -0
- deepvisiontools/config/errors.py +12 -0
- deepvisiontools/data/__init__.py +24 -0
- deepvisiontools/data/additional_augmentations.py +201 -0
- deepvisiontools/data/augmentation_class.py +37 -0
- deepvisiontools/data/batch_augmentations.py +211 -0
- deepvisiontools/data/data_reader/__init__.py +3 -0
- deepvisiontools/data/data_reader/readers.py +490 -0
- deepvisiontools/data/dataset.py +357 -0
- deepvisiontools/data/errors.py +23 -0
- deepvisiontools/formats/__init__.py +46 -0
- deepvisiontools/formats/base_data.py +802 -0
- deepvisiontools/formats/base_formats.py +321 -0
- deepvisiontools/formats/errors.py +167 -0
- deepvisiontools/formats/formats.py +282 -0
- deepvisiontools/formats/utils.py +174 -0
- deepvisiontools/inference/__init__.py +4 -0
- deepvisiontools/inference/patchifier.py +343 -0
- deepvisiontools/inference/predictor.py +208 -0
- deepvisiontools/metrics/__init__.py +50 -0
- deepvisiontools/metrics/available_metrics.py +215 -0
- deepvisiontools/metrics/base_metric.py +436 -0
- deepvisiontools/metrics/func.py +34 -0
- deepvisiontools/metrics/matcher.py +99 -0
- deepvisiontools/metrics/utils.py +39 -0
- deepvisiontools/models/__init__.py +14 -0
- deepvisiontools/models/basemodel.py +100 -0
- deepvisiontools/models/mask2former/__init__.py +3 -0
- deepvisiontools/models/mask2former/mask2former.py +285 -0
- deepvisiontools/models/smp/__init__.py +0 -0
- deepvisiontools/models/smp/errors.py +4 -0
- deepvisiontools/models/smp/smp.py +149 -0
- deepvisiontools/models/yolov8/__init__.py +15 -0
- deepvisiontools/models/yolov8/errors.py +25 -0
- deepvisiontools/models/yolov8/utils.py +92 -0
- deepvisiontools/models/yolov8/yolov8.py +302 -0
- deepvisiontools/models/yolov8seg/__init__.py +4 -0
- deepvisiontools/models/yolov8seg/errors.py +14 -0
- deepvisiontools/models/yolov8seg/utils.py +135 -0
- deepvisiontools/models/yolov8seg/yolov8seg.py +370 -0
- deepvisiontools/preprocessing/__init__.py +19 -0
- deepvisiontools/preprocessing/image.py +79 -0
- deepvisiontools/preprocessing/preprocessing.py +65 -0
- deepvisiontools/train/__init__.py +3 -0
- deepvisiontools/train/trainer.py +335 -0
- deepvisiontools/utils/__init__.py +3 -0
- deepvisiontools/utils/errors.py +0 -0
- deepvisiontools/utils/visualization.py +318 -0
- deepvisiontools-0.0.1.dist-info/LICENSE.txt +661 -0
- deepvisiontools-0.0.1.dist-info/METADATA +725 -0
- deepvisiontools-0.0.1.dist-info/RECORD +55 -0
- deepvisiontools-0.0.1.dist-info/WHEEL +5 -0
- deepvisiontools-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from deepvisiontools.config.config import Configuration
|
|
2
|
+
from deepvisiontools.train.trainer import Trainer
|
|
3
|
+
from deepvisiontools.data.dataset import DeepVisionDataset, DeepVisionLoader
|
|
4
|
+
from deepvisiontools.utils import visualization
|
|
5
|
+
from deepvisiontools.inference.predictor import Predictor
|
|
6
|
+
|
|
7
|
+
__version__ = "0.0.1"
|
|
8
|
+
|
|
9
|
+
__all__ = (
|
|
10
|
+
Configuration,
|
|
11
|
+
Trainer,
|
|
12
|
+
DeepVisionDataset,
|
|
13
|
+
DeepVisionLoader,
|
|
14
|
+
visualization,
|
|
15
|
+
Predictor,
|
|
16
|
+
)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from typing import Literal, Union
|
|
2
|
+
import deepvisiontools.config.errors as er
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
import random as rd
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _SingletonConfig(type):
|
|
9
|
+
"""Metaclass for Config class Singleton design pattern"""
|
|
10
|
+
|
|
11
|
+
_instance = None
|
|
12
|
+
|
|
13
|
+
def __call__(cls, *args, **kwargs):
|
|
14
|
+
if not isinstance(cls._instance, cls):
|
|
15
|
+
cls._instance = super().__call__(*args, **kwargs)
|
|
16
|
+
return cls._instance
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Configuration(metaclass=_SingletonConfig):
|
|
20
|
+
"""Configuration class for deepvisiontools (Singleton -> can be instancied once and then point to same object). Store all configuration information about the library configuration.
|
|
21
|
+
If you wish to changes parameters later on, you can simply modify the corresponding attributes.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
device (``Literal["cpu", "cuda"]``, **optional**): Device to be used by default when creating objects, running models etc. Defaults to "cpu".
|
|
26
|
+
data_type (``Literal["instance_mask", "bbox", "keypoint", "semantic_mask"]``, **optional**): Default format to use in dataset, models, prediction etc. Defaults to "bbox".
|
|
27
|
+
num_classes (``int``, **optional**): Number of classes in model. Defaults to 1.
|
|
28
|
+
mask_min_size (``int``, **optional**): Minimal size of mask to be considered : below this threshold annotation will be ignored. Defaults to 15.
|
|
29
|
+
semantic_mask_logits_combination (``Literal["avg", "min", "max"]``): How to combine logits in patchification or adding semantic masks. avg takes the mean, min takes the minimum and max takes the maximum.
|
|
30
|
+
splitted_mask_handling (``bool``, **optional**): If set to True redefine masks that are splitted (after cropping for example) so they belong to independant objects. Defaults to False.
|
|
31
|
+
model_nms_threshold (``float``, **optional**): Default nms iou threshold used in models. Defaults to 0.45.
|
|
32
|
+
model_confidence_threshold (``float``, **optional**): Default model confidence threshold to consider it a valid object prediction. Defaults to 0.5.
|
|
33
|
+
model_max_detection (``int``, **optional**): Maximum number of objects outputed by a model (useful for some models such as yolo type models). Defaults to 300.
|
|
34
|
+
metrics_matcher_type (``Literal["bbox", "instance_mask"]``, **optional**): Object matcher data type used in metrics (note that instance_mask is slower because needs transition to cpu to save gpu memory). If data_type is instance mask and matcher type is bbox will convert it to bbox for matching in metrics. Defaults to "bbox".
|
|
35
|
+
metrics_match_iou_threshold (``float``, **optional**): Metrics matcher iou threshold. Defaults to 0.45.
|
|
36
|
+
patchifier_mode (``Literal["bbox", "instance_mask"]``, **optional**): Patchifier data type used for nms and duplicate supresser. If data_type is mask and patchifier to bbox will convert for according operations. Defaults to "bbox".
|
|
37
|
+
seed (``Union[False, int]``, **optional**): use a manual seed to enforce reproducibility (you probably want to also switch deterministic to True in that case). If False it ends reproducibility. Defaults to False.
|
|
38
|
+
deterministic (``bool``, **optional**): Use deterministic algorithms. Helps further reproducibility (see also seeds). Be careful : some models can't be deterministic so sometimes you need to switch it to False even if you are manually seeding. Defaults to False.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
----------
|
|
43
|
+
|
|
44
|
+
.. highlight:: python
|
|
45
|
+
.. code-block:: python
|
|
46
|
+
|
|
47
|
+
>>> from deepvisiontools import Configuration()
|
|
48
|
+
>>> config = Configuration(data_type = "instance_mask") # can instantiate with given parameter
|
|
49
|
+
>>> config.device = "cuda" # can modify parameters by modifying attributes / properties
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Attributes
|
|
53
|
+
----------
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
- data_type (``Literal["instance_mask", "bbox", "keypoint", "semantic_mask"]``, **optional**): Default format to use in dataset, models, prediction etc. Defaults to "bbox".
|
|
57
|
+
- num_classes (``int``): Number of classes in model. Defaults to 1.
|
|
58
|
+
- mask_min_size (``int``): Minimal size of mask to be considered : below this threshold annotation will be ignored. Defaults to 15.
|
|
59
|
+
- semantic_mask_logits_combination (``Literal["avg", "min", "max"]``): How to combine logits in patchification or adding semantic masks. avg takes the mean, min takes the minimum and max takes the maximum.
|
|
60
|
+
- splitted_mask_handling (``bool``): If set to True redefine masks that are splitted (after cropping for example) so they belong to independant objects. Defaults to False.
|
|
61
|
+
- model_nms_threshold (``float``): Default nms iou threshold used in models. Defaults to 0.45.
|
|
62
|
+
- model_confidence_threshold (``float``): Default model confidence threshold to consider it a valid object prediction. Defaults to 0.5.
|
|
63
|
+
- model_max_detection (``int``): Maximum number of objects outputed by a model (useful for some models such as yolo type models). Defaults to 300.
|
|
64
|
+
- metrics_matcher_type (``Literal["bbox", "instance_mask"]``): Object matcher data type used in metrics. If data_type is instance mask and matcher type is bbox will convert it to bbox for matching in metrics. Defaults to "bbox".
|
|
65
|
+
- metrics_match_iou_threshold (``float``): Metrics matcher iou threshold. Defaults to 0.45.
|
|
66
|
+
- patchifier_mode (``Literal["bbox", "instance_mask"]``): Patchifier data type used for nms and duplicate supresser. If data_type is mask and patchifier to bbox will convert for according operations. Defaults to "bbox".
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
Attributes
|
|
70
|
+
----------
|
|
71
|
+
|
|
72
|
+
Properties:
|
|
73
|
+
- device (``Literal["cpu", "cuda"]``): Device to be used by default when creating objects, running models etc. Defaults to "cpu".
|
|
74
|
+
- seed (``Union[False, int]``, **optional**): use a manual seed to enforce reproducibility (you probably want to also switch deterministic to True in that case). If False it ends reproducibility. Defaults to False.
|
|
75
|
+
- deterministic (``bool``, **optional**): Use deterministic algorithms. Helps further reproducibility (see also seeds). Be careful : some models can't be deterministic so sometimes you need to switch it to False even if you are manually seeding. Defaults to False.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
Notes:
|
|
80
|
+
1) If you use instance_mask, bbox will included when needed from the masks.
|
|
81
|
+
2) you can change model_nms_threshold and model_confidence_threshold for the entire lib by modifying the attributes
|
|
82
|
+
3) In instance mode, by default the target ``Format`` remove small objects in case their masks contains less than min_mask_threshold (default 5 pixels).
|
|
83
|
+
Change the attribute to modify this behaviour.
|
|
84
|
+
4) The option splitted_mask_handling is by default False.
|
|
85
|
+
If you set to True, when performing transformation on object mask that split it into discontinuous sub-masks the library creates new objects for every sub-masks.
|
|
86
|
+
Otherwise they will still be describing the same unique object
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
device: Literal["cpu", "cuda"] = "cpu",
|
|
93
|
+
data_type: Literal[
|
|
94
|
+
"instance_mask", "bbox", "keypoint", "semantic_mask"
|
|
95
|
+
] = "bbox",
|
|
96
|
+
num_classes: int = 1,
|
|
97
|
+
mask_min_size: int = 20,
|
|
98
|
+
semantic_mask_logits_combination: Literal["avg", "min", "max"] = "avg",
|
|
99
|
+
splitted_mask_handling: bool = False,
|
|
100
|
+
model_nms_threshold: float = 0.45,
|
|
101
|
+
model_confidence_threshold: float = 0.5,
|
|
102
|
+
model_max_detection: int = 300,
|
|
103
|
+
metrics_matcher_type: Literal["bbox", "instance_mask"] = "bbox",
|
|
104
|
+
metrics_match_iou_threshold: float = 0.45,
|
|
105
|
+
patchifier_mode: Literal["bbox", "instance_mask"] = "bbox",
|
|
106
|
+
seed: Union[False, int] = False,
|
|
107
|
+
deterministic: bool = False,
|
|
108
|
+
):
|
|
109
|
+
|
|
110
|
+
# generic
|
|
111
|
+
self.data_type = data_type
|
|
112
|
+
self.device: str = device
|
|
113
|
+
self.num_classes: int = num_classes
|
|
114
|
+
# Format specific parameters
|
|
115
|
+
self.mask_min_size = mask_min_size
|
|
116
|
+
self.splitted_mask_handling = splitted_mask_handling
|
|
117
|
+
self.semantic_mask_logits_combination = semantic_mask_logits_combination
|
|
118
|
+
# Model build_results parameters
|
|
119
|
+
self.model_nms_threshold = model_nms_threshold
|
|
120
|
+
self.model_confidence_threshold = model_confidence_threshold
|
|
121
|
+
self.model_max_detection = model_max_detection
|
|
122
|
+
# metrics params
|
|
123
|
+
self.metrics_matcher_type = metrics_matcher_type
|
|
124
|
+
self.metrics_match_iou_threshold = metrics_match_iou_threshold
|
|
125
|
+
# patchifier setup
|
|
126
|
+
self.patchifier_mode = patchifier_mode
|
|
127
|
+
# reproducibility
|
|
128
|
+
self.seed = seed
|
|
129
|
+
self.deterministic = deterministic
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def device(self):
|
|
133
|
+
return self._device
|
|
134
|
+
|
|
135
|
+
@device.setter
|
|
136
|
+
def device(self, val: Literal["cpu", "cuda"]):
|
|
137
|
+
er.check_device_availability(val)
|
|
138
|
+
self._device = val
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def seed(self):
|
|
142
|
+
return self._seed
|
|
143
|
+
|
|
144
|
+
@seed.setter
|
|
145
|
+
def seed(self, val: Union[False, int]):
|
|
146
|
+
assert val == False or isinstance(
|
|
147
|
+
val, int
|
|
148
|
+
), f"seed must be a int or False. Got {val}"
|
|
149
|
+
if val:
|
|
150
|
+
rd.seed(val)
|
|
151
|
+
np.random.seed(val)
|
|
152
|
+
torch.manual_seed(val)
|
|
153
|
+
else:
|
|
154
|
+
rd.seed()
|
|
155
|
+
np.random.seed()
|
|
156
|
+
torch.seed()
|
|
157
|
+
self._seed = val
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def deterministic(self):
|
|
161
|
+
return self.deterministic
|
|
162
|
+
|
|
163
|
+
@deterministic.setter
|
|
164
|
+
def deterministic(self, val: bool):
|
|
165
|
+
assert isinstance(val, bool), f"deterministic must be a bool, got {val}"
|
|
166
|
+
if val:
|
|
167
|
+
torch.use_deterministic_algorithms(True)
|
|
168
|
+
# disable convolutions benchmark
|
|
169
|
+
if torch.cuda.is_available():
|
|
170
|
+
torch.backends.cudnn.benchmark = False
|
|
171
|
+
else:
|
|
172
|
+
torch.use_deterministic_algorithms(False)
|
|
173
|
+
# disable convolutions benchmark
|
|
174
|
+
if torch.cuda.is_available():
|
|
175
|
+
torch.backends.cudnn.benchmark = True
|
|
176
|
+
self._deterministic = val
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def check_device_availability(device):
|
|
5
|
+
assert (
|
|
6
|
+
device == "cpu" or device == "cuda"
|
|
7
|
+
), f"{device} is not a valid device, must be: cuda or: cpu"
|
|
8
|
+
if device != "cpu":
|
|
9
|
+
assert (
|
|
10
|
+
torch.cuda.is_available()
|
|
11
|
+
), "CUDA seems unavailable. Please run torch.cuda.is_available() to verify"
|
|
12
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from deepvisiontools.data.dataset import DeepVisionDataset, DeepVisionLoader
|
|
2
|
+
from deepvisiontools.data.augmentation_class import Augmentation
|
|
3
|
+
from deepvisiontools.data.additional_augmentations import (
|
|
4
|
+
RandomCropAndResize,
|
|
5
|
+
RandomCenterCropAndResize,
|
|
6
|
+
RandomChangeBackground,
|
|
7
|
+
RandomPadAndResize,
|
|
8
|
+
)
|
|
9
|
+
from deepvisiontools.data.batch_augmentations import (
|
|
10
|
+
AbstractBatchAugmenter,
|
|
11
|
+
MosaicBatchAugmenter,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = (
|
|
15
|
+
"DeepVisionDataset",
|
|
16
|
+
"DeepVisionLoader",
|
|
17
|
+
"Augmentation",
|
|
18
|
+
"RandomCropAndResize",
|
|
19
|
+
"RandomPadAndResize",
|
|
20
|
+
"RandomChangeBackground",
|
|
21
|
+
"RandomCenterCropAndResize",
|
|
22
|
+
"AbstractBatchAugmenter",
|
|
23
|
+
"MosaicBatchAugmenter",
|
|
24
|
+
)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torchvision.transforms.v2 as T
|
|
3
|
+
from torchvision.transforms.v2 import Transform
|
|
4
|
+
from typing import Sequence, Union, Tuple
|
|
5
|
+
import random as rd
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from deepvisiontools.preprocessing.image import load_image
|
|
8
|
+
from torchvision.tv_tensors import BoundingBoxes, Mask
|
|
9
|
+
from torch import Tensor
|
|
10
|
+
import copy
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RandomCropAndResize(Transform):
|
|
14
|
+
"""
|
|
15
|
+
With a given probability, apply RandomCrop and Resize from torchvision.transforms.v2.
|
|
16
|
+
NB : here we resize only and systematically if cropped.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
crop (``Union[int, Sequence[int]]``): Size to crop
|
|
20
|
+
resize (``Union[int, Sequence[int]]``): Size to resize
|
|
21
|
+
p (``float``, **optional**): probability. Defaults to 0.5.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
**Methods**:
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
crop: Union[int, Sequence[int]],
|
|
30
|
+
resize: Union[int, Sequence[int]],
|
|
31
|
+
p=0.5,
|
|
32
|
+
**kwargs,
|
|
33
|
+
):
|
|
34
|
+
|
|
35
|
+
super().__init__(**kwargs)
|
|
36
|
+
self.p = p
|
|
37
|
+
self.crop = T.RandomCrop(crop)
|
|
38
|
+
self.resize = T.Resize(resize)
|
|
39
|
+
|
|
40
|
+
def forward(self, *inputs):
|
|
41
|
+
if torch.rand(1) >= self.p:
|
|
42
|
+
pass
|
|
43
|
+
else:
|
|
44
|
+
inputs = self.crop.forward(inputs)
|
|
45
|
+
inputs = self.resize.forward(inputs)
|
|
46
|
+
return inputs
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RandomCenterCropAndResize(Transform):
|
|
50
|
+
"""
|
|
51
|
+
With a given probability, apply CenterCrop and Resize from torchvision.transforms.v2.
|
|
52
|
+
NB : here we resize only and systematically if cropped.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
crop (``Union[int, Sequence[int]]``): Size to crop
|
|
56
|
+
resize (``Union[int, Sequence[int]]``): Size to resize
|
|
57
|
+
p (``float``, **optional**): probability. Defaults to 0.5.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, crop: Sequence[int], resize: Sequence[int], p=0.5, **kwargs):
|
|
61
|
+
super().__init__(**kwargs)
|
|
62
|
+
self.p = p
|
|
63
|
+
self.crop = T.CenterCrop(crop)
|
|
64
|
+
self.resize = T.Resize(resize)
|
|
65
|
+
|
|
66
|
+
def forward(self, *inputs):
|
|
67
|
+
if torch.rand(1) >= self.p:
|
|
68
|
+
pass
|
|
69
|
+
else:
|
|
70
|
+
inputs = self.crop.forward(inputs)
|
|
71
|
+
inputs = self.resize.forward(inputs)
|
|
72
|
+
return inputs
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class RandomPadAndResize(Transform):
|
|
76
|
+
"""
|
|
77
|
+
With a given probability, apply Pad and Resize from torchvision.transforms.v2. This looks like a zoom out effect by decreasing spatial resolution.
|
|
78
|
+
NB : here we resize only and systematically if Padded.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
MaxPad (``Union[int, Sequence[int]]``): maximum padding bounds can be int for common padding bound for all borders or sequence of 4 ints for (t, l, b, r)
|
|
82
|
+
resize (``Tuple[int, int]``): Size to resize
|
|
83
|
+
p (``float``, **optional**): probability to apply transformation. Defaults to 0.5.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
maxpad: Sequence[int],
|
|
89
|
+
resize: Tuple[int, int],
|
|
90
|
+
p=0.5,
|
|
91
|
+
**kwargs,
|
|
92
|
+
):
|
|
93
|
+
|
|
94
|
+
super().__init__(**kwargs)
|
|
95
|
+
self.p = p
|
|
96
|
+
self.max_pad = (maxpad,) * 4 if isinstance(maxpad, int) else maxpad
|
|
97
|
+
self.resize = T.Resize(resize)
|
|
98
|
+
|
|
99
|
+
def forward(self, *inputs):
|
|
100
|
+
if torch.rand(1) >= self.p:
|
|
101
|
+
return inputs
|
|
102
|
+
else:
|
|
103
|
+
t = rd.randrange(0, self.max_pad[0])
|
|
104
|
+
l = rd.randrange(0, self.max_pad[1])
|
|
105
|
+
r = rd.randrange(0, self.max_pad[2])
|
|
106
|
+
b = rd.randrange(0, self.max_pad[3])
|
|
107
|
+
padder = T.Pad([t, l, b, r])
|
|
108
|
+
inputs = padder(inputs)
|
|
109
|
+
inputs = self.resize(inputs)
|
|
110
|
+
return inputs
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class RandomChangeBackground(Transform):
|
|
114
|
+
"""With a given probability p, swap image background. New background is taken from an image folder for which path is provided.
|
|
115
|
+
Note 1 : it is implemented only for instance_mask, semantic_mask and bbox data type
|
|
116
|
+
Note 2 : new background image type must be one of .jpg, .jpeg, .png, .tif, .tiff, .PNG, .JPG, .JPEG, .TIF, .TIFF
|
|
117
|
+
Args:
|
|
118
|
+
background_dir_path (``Union[str, Path]``): Path to background folder
|
|
119
|
+
p (``float``, **optional**): Probability. Defaults to 0.5.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
IMPLEMENTED_TVTENSOR = [
|
|
123
|
+
BoundingBoxes,
|
|
124
|
+
Mask,
|
|
125
|
+
] # list of available hooks, i.e implemented for these type of tv_tensor
|
|
126
|
+
|
|
127
|
+
IMG_TYPE = [
|
|
128
|
+
".jpg",
|
|
129
|
+
".jpeg",
|
|
130
|
+
".png",
|
|
131
|
+
".tif",
|
|
132
|
+
".tiff",
|
|
133
|
+
".PNG",
|
|
134
|
+
".JPG",
|
|
135
|
+
".JPEG",
|
|
136
|
+
".TIF",
|
|
137
|
+
".TIFF",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
def __init__(self, background_dir_path: Union[str, Path], p: float = 0.5, **kwargs):
|
|
141
|
+
|
|
142
|
+
super().__init__(**kwargs)
|
|
143
|
+
path = (
|
|
144
|
+
background_dir_path
|
|
145
|
+
if isinstance(background_dir_path, Path)
|
|
146
|
+
else Path(background_dir_path)
|
|
147
|
+
)
|
|
148
|
+
list_bckg_imgs = []
|
|
149
|
+
for stem in RandomChangeBackground.IMG_TYPE:
|
|
150
|
+
list_bckg_imgs += list(path.glob(f"*{stem}"))
|
|
151
|
+
self.bckg_imgs = list_bckg_imgs
|
|
152
|
+
self.p = p
|
|
153
|
+
self._truth_extracter = _ExtractTruthFromTargetImage()
|
|
154
|
+
|
|
155
|
+
def forward(self, *inputs):
|
|
156
|
+
mu = torch.rand(1)
|
|
157
|
+
if mu > self.p:
|
|
158
|
+
return inputs
|
|
159
|
+
assert (
|
|
160
|
+
len(inputs) == 2
|
|
161
|
+
), f"RandomChangeBackground is implemented only for exactly image and target input provided. You can't provide only image or additional item like scores. Got {inputs}"
|
|
162
|
+
image, target = inputs
|
|
163
|
+
assert (
|
|
164
|
+
type(target) in RandomChangeBackground.IMPLEMENTED_TVTENSOR
|
|
165
|
+
), f"RandomChangeBackground is implemented only for {RandomChangeBackground.IMPLEMENTED_TVTENSOR} torchvision tv_tensors. Got {type(target)}"
|
|
166
|
+
|
|
167
|
+
# randomly select background image
|
|
168
|
+
original_dtype = image.dtype
|
|
169
|
+
image = image.float()
|
|
170
|
+
idx = torch.randint(0, len(self.bckg_imgs), (1,)).item()
|
|
171
|
+
bck_img = load_image(self.bckg_imgs[idx]).to(image.device).float()
|
|
172
|
+
bck_img = T.Resize(image.shape[-2:])(bck_img)
|
|
173
|
+
removed_bckg = self._truth_extracter(image, target)
|
|
174
|
+
removed_bckg[removed_bckg == -1.0] = bck_img[removed_bckg == -1.0]
|
|
175
|
+
return removed_bckg.to(original_dtype), target
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class _ExtractTruthFromTargetImage:
|
|
179
|
+
"""Handle background removing from target. Background is asserted as -1 to avoid errors removing 0 entries from image."""
|
|
180
|
+
|
|
181
|
+
def __call__(self, image: Tensor, targ: Tensor) -> Tensor:
|
|
182
|
+
if isinstance(targ, Mask):
|
|
183
|
+
rm_bckg = self._call_mask(image, targ)
|
|
184
|
+
elif isinstance(targ, BoundingBoxes):
|
|
185
|
+
rm_bckg = self._call_bbox(image, targ)
|
|
186
|
+
return rm_bckg
|
|
187
|
+
|
|
188
|
+
def _call_mask(self, image: Tensor, targ: Mask) -> Tensor:
|
|
189
|
+
removed_bckg = copy.deepcopy(image)
|
|
190
|
+
removed_bckg[:, targ == 0] = -1.0
|
|
191
|
+
return removed_bckg
|
|
192
|
+
|
|
193
|
+
def _call_bbox(self, image: Tensor, targ: BoundingBoxes) -> Tensor:
|
|
194
|
+
mask = Mask(torch.zeros(image.shape[-2:]))
|
|
195
|
+
for box in targ:
|
|
196
|
+
x1, y1, x2, y2 = box.to(torch.uint64).split(1)
|
|
197
|
+
x1, y1, x2, y2 = tuple(
|
|
198
|
+
max(i.item(), 0) for i in (x1, y1, x2, y2)
|
|
199
|
+
) # ensure that bbox has coordinates positive
|
|
200
|
+
mask[y1:y2, x1:x2] = 1
|
|
201
|
+
return self._call_mask(image, mask)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from deepvisiontools.formats import (
|
|
3
|
+
BaseFormat,
|
|
4
|
+
BboxFormat,
|
|
5
|
+
BboxData,
|
|
6
|
+
InstanceMaskData,
|
|
7
|
+
InstanceMaskFormat,
|
|
8
|
+
)
|
|
9
|
+
import torchvision.transforms.v2 as T
|
|
10
|
+
from torch import Tensor
|
|
11
|
+
from torchvision.tv_tensors import Image
|
|
12
|
+
import torch
|
|
13
|
+
import deepvisiontools.data.errors as er
|
|
14
|
+
|
|
15
|
+
# Tested : Mask : Ok : RandomResize, RandomCrop, RandomZoomOut, ScaleJitter, RandomHorizontalFlip / Vertical, RandomRotation, RandomAffine, RandomPerspective
|
|
16
|
+
# Errors : RandomCropIou if you have masks only -> needs boxes
|
|
17
|
+
# boxes : Ok : same as mask but careful with crop / rotation : can lead to slightly off new boxes as per rotation / crops don't preserve structural information of boxes by nature
|
|
18
|
+
|
|
19
|
+
# TODO check augmentation rotation with boxes : got an error of different input shape / canvas size between image and boxes
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Augmentation:
|
|
23
|
+
"""Class that handles augmentation in dataset. Call on different Formats (data_type) specific methods
|
|
24
|
+
Args:
|
|
25
|
+
augmentations (List[T.Transform]): List of torchvision.transforms.v2 Transform classes (or from deepvisiontools.data.additional_augmentations)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, augmentations: List[T.Transform]) -> None:
|
|
29
|
+
self.transform = T.Compose(augmentations)
|
|
30
|
+
|
|
31
|
+
def __call__(self, image: Tensor, target: BaseFormat):
|
|
32
|
+
"""Augment depending on format type"""
|
|
33
|
+
image = Image(image)
|
|
34
|
+
transformed_target, _, transformed_img = target.apply_augmentation(
|
|
35
|
+
image, self.transform
|
|
36
|
+
)
|
|
37
|
+
return transformed_img, transformed_target
|