OTVision 0.6.4__py3-none-any.whl → 0.6.6__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.
- OTVision/abstraction/defaults.py +15 -0
- OTVision/application/config.py +475 -0
- OTVision/application/config_parser.py +280 -0
- OTVision/application/configure_logger.py +1 -1
- OTVision/application/detect/detected_frame_factory.py +1 -0
- OTVision/application/detect/factory.py +1 -1
- OTVision/application/detect/update_detect_config_with_cli_args.py +2 -1
- OTVision/application/get_config.py +2 -1
- OTVision/application/get_current_config.py +1 -1
- OTVision/application/track/__init__.py +0 -0
- OTVision/application/track/get_track_cli_args.py +20 -0
- OTVision/application/track/update_current_track_config.py +42 -0
- OTVision/application/track/update_track_config_with_cli_args.py +52 -0
- OTVision/application/update_current_config.py +1 -1
- OTVision/config.py +61 -668
- OTVision/convert/convert.py +3 -3
- OTVision/detect/builder.py +27 -20
- OTVision/detect/cli.py +3 -3
- OTVision/detect/detected_frame_buffer.py +3 -0
- OTVision/detect/file_based_detect_builder.py +19 -0
- OTVision/detect/otdet.py +54 -1
- OTVision/detect/otdet_file_writer.py +4 -3
- OTVision/detect/rtsp_based_detect_builder.py +37 -0
- OTVision/detect/rtsp_input_source.py +207 -0
- OTVision/detect/timestamper.py +2 -1
- OTVision/detect/video_input_source.py +9 -5
- OTVision/detect/yolo.py +17 -1
- OTVision/domain/cli.py +31 -1
- OTVision/domain/current_config.py +1 -1
- OTVision/domain/frame.py +6 -0
- OTVision/domain/object_detection.py +6 -1
- OTVision/domain/serialization.py +12 -0
- OTVision/domain/time.py +13 -0
- OTVision/helpers/files.py +14 -15
- OTVision/plugin/__init__.py +0 -0
- OTVision/plugin/yaml_serialization.py +20 -0
- OTVision/track/builder.py +132 -0
- OTVision/track/cli.py +128 -0
- OTVision/track/exporter/filebased_exporter.py +2 -1
- OTVision/track/id_generator.py +15 -0
- OTVision/track/model/track_exporter.py +2 -1
- OTVision/track/model/tracking_interfaces.py +6 -6
- OTVision/track/parser/chunk_parser_plugins.py +1 -0
- OTVision/track/parser/frame_group_parser_plugins.py +35 -5
- OTVision/track/track.py +54 -133
- OTVision/track/tracker/filebased_tracking.py +8 -7
- OTVision/track/tracker/tracker_plugin_iou.py +15 -9
- OTVision/transform/transform.py +2 -2
- OTVision/version.py +1 -1
- otvision-0.6.6.dist-info/METADATA +182 -0
- {otvision-0.6.4.dist-info → otvision-0.6.6.dist-info}/RECORD +53 -36
- otvision-0.6.4.dist-info/METADATA +0 -49
- {otvision-0.6.4.dist-info → otvision-0.6.6.dist-info}/WHEEL +0 -0
- {otvision-0.6.4.dist-info → otvision-0.6.6.dist-info}/licenses/LICENSE +0 -0
OTVision/domain/frame.py
CHANGED
|
@@ -20,6 +20,7 @@ class FrameKeys:
|
|
|
20
20
|
frame: Literal["frame"] = "frame"
|
|
21
21
|
source: Literal["source"] = "source"
|
|
22
22
|
occurrence: Literal["occurrence"] = "occurrence"
|
|
23
|
+
output: Literal["output"] = "output"
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class Frame(TypedDict):
|
|
@@ -35,6 +36,7 @@ class Frame(TypedDict):
|
|
|
35
36
|
data: Optional[ndarray]
|
|
36
37
|
frame: int
|
|
37
38
|
source: str
|
|
39
|
+
output: str
|
|
38
40
|
occurrence: datetime
|
|
39
41
|
|
|
40
42
|
|
|
@@ -49,6 +51,7 @@ class DetectedFrame:
|
|
|
49
51
|
no (FrameNo): Frame number.
|
|
50
52
|
occurrence (datetime): Time stamp, at which frame was recorded.
|
|
51
53
|
source (str): Source from where frame was obtained, e.g. video file path.
|
|
54
|
+
output (str): Output file name, e.g. video file name.
|
|
52
55
|
detections (Sequence[Detection]): A sequence of Detections occurring in frame.
|
|
53
56
|
image (Optional[ndarray]): Optional image data of frame.
|
|
54
57
|
"""
|
|
@@ -56,6 +59,7 @@ class DetectedFrame:
|
|
|
56
59
|
no: FrameNo
|
|
57
60
|
occurrence: datetime
|
|
58
61
|
source: str
|
|
62
|
+
output: str
|
|
59
63
|
detections: Sequence[Detection]
|
|
60
64
|
image: Optional[ndarray] = None
|
|
61
65
|
|
|
@@ -64,6 +68,7 @@ class DetectedFrame:
|
|
|
64
68
|
no=self.no,
|
|
65
69
|
occurrence=self.occurrence,
|
|
66
70
|
source=self.source,
|
|
71
|
+
output=self.output,
|
|
67
72
|
detections=self.detections,
|
|
68
73
|
image=None,
|
|
69
74
|
)
|
|
@@ -153,6 +158,7 @@ class TrackedFrame(DetectedFrame):
|
|
|
153
158
|
no=self.no,
|
|
154
159
|
occurrence=self.occurrence,
|
|
155
160
|
source=self.source,
|
|
161
|
+
output=self.output,
|
|
156
162
|
finished_tracks=self.finished_tracks,
|
|
157
163
|
detections=detections,
|
|
158
164
|
image=self.image,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import Generator
|
|
3
3
|
|
|
4
|
-
from OTVision.config import DetectConfig
|
|
4
|
+
from OTVision.application.config import DetectConfig
|
|
5
5
|
from OTVision.domain.frame import DetectedFrame, Frame
|
|
6
6
|
|
|
7
7
|
|
|
@@ -39,6 +39,11 @@ class ObjectDetector(ObjectDetectorMetadata):
|
|
|
39
39
|
"""
|
|
40
40
|
raise NotImplementedError
|
|
41
41
|
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def preload(self) -> None:
|
|
44
|
+
"""Preload the model if possible."""
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
|
|
42
47
|
|
|
43
48
|
class ObjectDetectorFactory(ABC):
|
|
44
49
|
@abstractmethod
|
OTVision/domain/time.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DatetimeProvider(ABC):
|
|
6
|
+
@abstractmethod
|
|
7
|
+
def provide(self) -> datetime:
|
|
8
|
+
raise NotImplementedError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CurrentDatetimeProvider(DatetimeProvider):
|
|
12
|
+
def provide(self) -> datetime:
|
|
13
|
+
return datetime.now(tz=timezone.utc)
|
OTVision/helpers/files.py
CHANGED
|
@@ -24,7 +24,7 @@ import logging
|
|
|
24
24
|
import shutil
|
|
25
25
|
import time
|
|
26
26
|
from pathlib import Path
|
|
27
|
-
from typing import Iterable, Union
|
|
27
|
+
from typing import Iterable, Sequence, Union
|
|
28
28
|
|
|
29
29
|
import ijson
|
|
30
30
|
import ujson
|
|
@@ -49,8 +49,8 @@ FULL_FILE_NAME_PATTERN = (
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def get_files(
|
|
52
|
-
paths:
|
|
53
|
-
filetypes:
|
|
52
|
+
paths: Sequence[Path | str],
|
|
53
|
+
filetypes: list[str] | None = None,
|
|
54
54
|
search_subdirs: bool = True,
|
|
55
55
|
) -> list[Path]:
|
|
56
56
|
"""
|
|
@@ -58,12 +58,11 @@ def get_files(
|
|
|
58
58
|
(recursive) content of folders.
|
|
59
59
|
|
|
60
60
|
Args:
|
|
61
|
-
paths (list[Path]): where to find the files.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
search_subdirs (bool): Wheter or not to search subdirs of dirs given as paths.
|
|
61
|
+
paths (list[Path | str]): where to find the files.
|
|
62
|
+
filetypes (list[str] | None): ending of files to find. Preceding "_" prevents
|
|
63
|
+
adding a '.'. If no filetype is given, filetypes of file paths given are
|
|
64
|
+
used and directories are ignored. Defaults to None.
|
|
65
|
+
search_subdirs (bool): Whether to search subdirectories of dirs given as paths.
|
|
67
66
|
Defaults to True.
|
|
68
67
|
|
|
69
68
|
Raises:
|
|
@@ -76,9 +75,11 @@ def get_files(
|
|
|
76
75
|
Returns:
|
|
77
76
|
list[Path]: List of files
|
|
78
77
|
"""
|
|
78
|
+
if type(paths) is str:
|
|
79
|
+
paths = [paths]
|
|
79
80
|
files = set()
|
|
80
|
-
if
|
|
81
|
-
raise
|
|
81
|
+
if not isinstance(paths, Sequence):
|
|
82
|
+
raise ValueError("Paths needs to be a sequence")
|
|
82
83
|
if filetypes:
|
|
83
84
|
if type(filetypes) is not list:
|
|
84
85
|
raise TypeError("Filetypes needs to be a list of str")
|
|
@@ -89,9 +90,7 @@ def get_files(
|
|
|
89
90
|
if not filetype.startswith("."):
|
|
90
91
|
filetype = f".{filetype}"
|
|
91
92
|
filetypes[idx] = filetype.lower()
|
|
92
|
-
for path in paths:
|
|
93
|
-
if not isinstance(path, Path):
|
|
94
|
-
raise TypeError("Paths needs to be a list of pathlib.Path")
|
|
93
|
+
for path in map(Path, paths):
|
|
95
94
|
if path.is_file():
|
|
96
95
|
file = path
|
|
97
96
|
if filetypes:
|
|
@@ -107,7 +106,7 @@ def get_files(
|
|
|
107
106
|
if file.is_file() and file.suffix.lower() == filetype:
|
|
108
107
|
files.add(file)
|
|
109
108
|
else:
|
|
110
|
-
raise
|
|
109
|
+
raise ValueError(f"{path} is neither a file nor a dir")
|
|
111
110
|
|
|
112
111
|
return sorted(list(files))
|
|
113
112
|
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from OTVision.domain.serialization import Deserializer
|
|
7
|
+
from OTVision.helpers.log import LOGGER_NAME
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class YamlDeserializer(Deserializer):
|
|
13
|
+
def deserialize(self, file: Path) -> dict:
|
|
14
|
+
with open(file, "r") as stream:
|
|
15
|
+
try:
|
|
16
|
+
yaml_config = yaml.safe_load(stream)
|
|
17
|
+
except yaml.YAMLError:
|
|
18
|
+
log.exception("Unable to parse user config. Using default config.")
|
|
19
|
+
raise
|
|
20
|
+
return yaml_config
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
|
|
4
|
+
from OTVision.application.config import Config
|
|
5
|
+
from OTVision.application.config_parser import ConfigParser
|
|
6
|
+
from OTVision.application.configure_logger import ConfigureLogger
|
|
7
|
+
from OTVision.application.get_config import GetConfig
|
|
8
|
+
from OTVision.application.get_current_config import GetCurrentConfig
|
|
9
|
+
from OTVision.application.track.get_track_cli_args import GetTrackCliArgs
|
|
10
|
+
from OTVision.application.track.update_current_track_config import (
|
|
11
|
+
UpdateCurrentTrackConfig,
|
|
12
|
+
)
|
|
13
|
+
from OTVision.application.track.update_track_config_with_cli_args import (
|
|
14
|
+
UpdateTrackConfigWithCliArgs,
|
|
15
|
+
)
|
|
16
|
+
from OTVision.application.update_current_config import UpdateCurrentConfig
|
|
17
|
+
from OTVision.domain.cli import TrackCliParser
|
|
18
|
+
from OTVision.domain.current_config import CurrentConfig
|
|
19
|
+
from OTVision.domain.serialization import Deserializer
|
|
20
|
+
from OTVision.plugin.yaml_serialization import YamlDeserializer
|
|
21
|
+
from OTVision.track.cli import ArgparseTrackCliParser
|
|
22
|
+
from OTVision.track.exporter.filebased_exporter import FinishedChunkTrackExporter
|
|
23
|
+
from OTVision.track.id_generator import (
|
|
24
|
+
StrIdGenerator,
|
|
25
|
+
track_id_generator,
|
|
26
|
+
tracking_run_uuid_generator,
|
|
27
|
+
)
|
|
28
|
+
from OTVision.track.model.filebased.frame_chunk import ChunkParser
|
|
29
|
+
from OTVision.track.model.filebased.frame_group import FrameGroupParser
|
|
30
|
+
from OTVision.track.model.track_exporter import FinishedTracksExporter
|
|
31
|
+
from OTVision.track.parser.chunk_parser_plugins import JsonChunkParser
|
|
32
|
+
from OTVision.track.parser.frame_group_parser_plugins import (
|
|
33
|
+
TimeThresholdFrameGroupParser,
|
|
34
|
+
)
|
|
35
|
+
from OTVision.track.track import OtvisionTrack
|
|
36
|
+
from OTVision.track.tracker.filebased_tracking import (
|
|
37
|
+
GroupedFilesTracker,
|
|
38
|
+
UnfinishedChunksBuffer,
|
|
39
|
+
)
|
|
40
|
+
from OTVision.track.tracker.tracker_plugin_iou import IouTracker
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TrackBuilder:
|
|
44
|
+
@cached_property
|
|
45
|
+
def get_config(self) -> GetConfig:
|
|
46
|
+
return GetConfig(self.config_parser)
|
|
47
|
+
|
|
48
|
+
@cached_property
|
|
49
|
+
def config_parser(self) -> ConfigParser:
|
|
50
|
+
return ConfigParser(self.yaml_deserializer)
|
|
51
|
+
|
|
52
|
+
@cached_property
|
|
53
|
+
def yaml_deserializer(self) -> Deserializer:
|
|
54
|
+
return YamlDeserializer()
|
|
55
|
+
|
|
56
|
+
@cached_property
|
|
57
|
+
def get_track_cli_args(self) -> GetTrackCliArgs:
|
|
58
|
+
return GetTrackCliArgs(self.track_cli_parser)
|
|
59
|
+
|
|
60
|
+
@cached_property
|
|
61
|
+
def track_cli_parser(self) -> TrackCliParser:
|
|
62
|
+
return ArgparseTrackCliParser(
|
|
63
|
+
parser=ArgumentParser("Track objects through detections"), argv=self.argv
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@cached_property
|
|
67
|
+
def update_track_config_with_cli_args(self) -> UpdateTrackConfigWithCliArgs:
|
|
68
|
+
return UpdateTrackConfigWithCliArgs(self.get_track_cli_args)
|
|
69
|
+
|
|
70
|
+
@cached_property
|
|
71
|
+
def configure_logger(self) -> ConfigureLogger:
|
|
72
|
+
return ConfigureLogger()
|
|
73
|
+
|
|
74
|
+
@cached_property
|
|
75
|
+
def update_current_config(self) -> UpdateCurrentConfig:
|
|
76
|
+
return UpdateCurrentConfig(self.current_config)
|
|
77
|
+
|
|
78
|
+
@cached_property
|
|
79
|
+
def get_current_config(self) -> GetCurrentConfig:
|
|
80
|
+
return GetCurrentConfig(self.current_config)
|
|
81
|
+
|
|
82
|
+
@cached_property
|
|
83
|
+
def current_config(self) -> CurrentConfig:
|
|
84
|
+
return CurrentConfig(Config())
|
|
85
|
+
|
|
86
|
+
@cached_property
|
|
87
|
+
def chunk_parser(self) -> ChunkParser:
|
|
88
|
+
return JsonChunkParser()
|
|
89
|
+
|
|
90
|
+
@cached_property
|
|
91
|
+
def frame_group_parser(self) -> FrameGroupParser:
|
|
92
|
+
return TimeThresholdFrameGroupParser(self.get_current_config)
|
|
93
|
+
|
|
94
|
+
@cached_property
|
|
95
|
+
def tracker(self) -> GroupedFilesTracker:
|
|
96
|
+
tracker = IouTracker(get_current_config=self.get_current_config)
|
|
97
|
+
return GroupedFilesTracker(
|
|
98
|
+
tracker=tracker,
|
|
99
|
+
chunk_parser=self.chunk_parser,
|
|
100
|
+
frame_group_parser=self.frame_group_parser,
|
|
101
|
+
id_generator_factory=lambda _: track_id_generator(),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@cached_property
|
|
105
|
+
def unfinished_chunks_buffer(self) -> UnfinishedChunksBuffer:
|
|
106
|
+
return UnfinishedChunksBuffer(tracker=self.tracker, keep_discarded=True)
|
|
107
|
+
|
|
108
|
+
@cached_property
|
|
109
|
+
def track_exporter(self) -> FinishedTracksExporter:
|
|
110
|
+
return FinishedChunkTrackExporter()
|
|
111
|
+
|
|
112
|
+
@cached_property
|
|
113
|
+
def update_current_track_config(self) -> UpdateCurrentTrackConfig:
|
|
114
|
+
return UpdateCurrentTrackConfig(
|
|
115
|
+
get_current_config=self.get_current_config,
|
|
116
|
+
update_current_config=self.update_current_config,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@cached_property
|
|
120
|
+
def tracking_run_id_generator(self) -> StrIdGenerator:
|
|
121
|
+
return tracking_run_uuid_generator
|
|
122
|
+
|
|
123
|
+
def __init__(self, argv: list[str] | None = None) -> None:
|
|
124
|
+
self.argv = argv
|
|
125
|
+
|
|
126
|
+
def build(self) -> OtvisionTrack:
|
|
127
|
+
return OtvisionTrack(
|
|
128
|
+
get_current_config=self.get_current_config,
|
|
129
|
+
track_exporter=self.track_exporter,
|
|
130
|
+
unfinished_chunks_buffer=self.unfinished_chunks_buffer,
|
|
131
|
+
tracking_run_id_generator=self.tracking_run_id_generator,
|
|
132
|
+
)
|
OTVision/track/cli.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from argparse import ArgumentParser, BooleanOptionalAction, Namespace
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from OTVision.domain.cli import CliParseError, TrackCliArgs, TrackCliParser
|
|
5
|
+
from OTVision.helpers.files import check_if_all_paths_exist
|
|
6
|
+
from OTVision.helpers.log import DEFAULT_LOG_FILE, VALID_LOG_LEVELS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ArgparseTrackCliParser(TrackCliParser):
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
parser: ArgumentParser,
|
|
13
|
+
argv: list[str] | None = None,
|
|
14
|
+
) -> None:
|
|
15
|
+
self._parser = parser
|
|
16
|
+
self._argv = argv
|
|
17
|
+
self.__setup()
|
|
18
|
+
|
|
19
|
+
def __setup(self) -> None:
|
|
20
|
+
self._parser.add_argument(
|
|
21
|
+
"-p",
|
|
22
|
+
"--paths",
|
|
23
|
+
nargs="+",
|
|
24
|
+
type=str,
|
|
25
|
+
help="Path or list of paths to detections files",
|
|
26
|
+
required=False,
|
|
27
|
+
)
|
|
28
|
+
self._parser.add_argument(
|
|
29
|
+
"-c",
|
|
30
|
+
"--config",
|
|
31
|
+
type=str,
|
|
32
|
+
help="Path to custom user configuration yaml file.",
|
|
33
|
+
required=False,
|
|
34
|
+
)
|
|
35
|
+
self._parser.add_argument(
|
|
36
|
+
"-o",
|
|
37
|
+
"--overwrite",
|
|
38
|
+
action=BooleanOptionalAction,
|
|
39
|
+
help="Overwrite existing output files",
|
|
40
|
+
)
|
|
41
|
+
self._parser.add_argument(
|
|
42
|
+
"--sigma-l",
|
|
43
|
+
type=float,
|
|
44
|
+
help="Set sigma_l parameter for tracking",
|
|
45
|
+
)
|
|
46
|
+
self._parser.add_argument(
|
|
47
|
+
"--sigma-h",
|
|
48
|
+
type=float,
|
|
49
|
+
help="Set sigma_h parameter for tracking",
|
|
50
|
+
)
|
|
51
|
+
self._parser.add_argument(
|
|
52
|
+
"--sigma-iou",
|
|
53
|
+
type=float,
|
|
54
|
+
help="Set sigma_iou parameter for tracking",
|
|
55
|
+
)
|
|
56
|
+
self._parser.add_argument(
|
|
57
|
+
"--t-min",
|
|
58
|
+
type=int,
|
|
59
|
+
help="Set t_min parameter for tracking",
|
|
60
|
+
)
|
|
61
|
+
self._parser.add_argument(
|
|
62
|
+
"--t-miss-max",
|
|
63
|
+
type=int,
|
|
64
|
+
help="Set t_miss_max parameter for tracking",
|
|
65
|
+
)
|
|
66
|
+
self._parser.add_argument(
|
|
67
|
+
"--log-level-console",
|
|
68
|
+
type=str,
|
|
69
|
+
choices=VALID_LOG_LEVELS,
|
|
70
|
+
help="Log level for logging to the console",
|
|
71
|
+
required=False,
|
|
72
|
+
)
|
|
73
|
+
self._parser.add_argument(
|
|
74
|
+
"--log-level-file",
|
|
75
|
+
type=str,
|
|
76
|
+
choices=VALID_LOG_LEVELS,
|
|
77
|
+
help="Log level for logging to a log file",
|
|
78
|
+
required=False,
|
|
79
|
+
)
|
|
80
|
+
self._parser.add_argument(
|
|
81
|
+
"--logfile",
|
|
82
|
+
default=str(DEFAULT_LOG_FILE),
|
|
83
|
+
type=str,
|
|
84
|
+
help="Specify log file directory.",
|
|
85
|
+
required=False,
|
|
86
|
+
)
|
|
87
|
+
self._parser.add_argument(
|
|
88
|
+
"--logfile-overwrite",
|
|
89
|
+
action="store_true",
|
|
90
|
+
help="Overwrite log file if it already exists.",
|
|
91
|
+
required=False,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def parse(self) -> TrackCliArgs:
|
|
95
|
+
args = self._parser.parse_args(self._argv)
|
|
96
|
+
self.__assert_cli_args_valid(args)
|
|
97
|
+
|
|
98
|
+
return TrackCliArgs(
|
|
99
|
+
paths=self._parse_files(args.paths),
|
|
100
|
+
config_file=args.config,
|
|
101
|
+
overwrite=args.overwrite,
|
|
102
|
+
sigma_l=float(args.sigma_l) if args.sigma_l is not None else None,
|
|
103
|
+
sigma_h=float(args.sigma_h) if args.sigma_h is not None else None,
|
|
104
|
+
sigma_iou=float(args.sigma_iou) if args.sigma_iou is not None else None,
|
|
105
|
+
t_min=int(args.t_min) if args.t_min is not None else None,
|
|
106
|
+
t_miss_max=int(args.t_miss_max) if args.t_miss_max is not None else None,
|
|
107
|
+
logfile=Path(args.logfile),
|
|
108
|
+
log_level_console=args.log_level_console,
|
|
109
|
+
log_level_file=args.log_level_file,
|
|
110
|
+
logfile_overwrite=args.logfile_overwrite,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def __assert_cli_args_valid(self, args: Namespace) -> None:
|
|
114
|
+
if args.paths is None and args.config is None:
|
|
115
|
+
raise CliParseError(
|
|
116
|
+
(
|
|
117
|
+
"No paths have been passed as command line args."
|
|
118
|
+
"No user config has been passed as command line arg."
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def _parse_files(self, files: list[str] | None) -> list[str] | None:
|
|
123
|
+
if files is None:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
result = [Path(file).expanduser() for file in files]
|
|
127
|
+
check_if_all_paths_exist(result)
|
|
128
|
+
return list(map(str, result))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from OTVision.config import
|
|
3
|
+
from OTVision.application.config import DEFAULT_FILETYPE, TRACK
|
|
4
|
+
from OTVision.config import CONFIG
|
|
4
5
|
from OTVision.track.model.filebased.frame_chunk import FinishedChunk
|
|
5
6
|
from OTVision.track.model.filebased.frame_group import get_output_file
|
|
6
7
|
from OTVision.track.model.track_exporter import FinishedTracksExporter
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import Callable, Iterator
|
|
3
|
+
|
|
4
|
+
StrIdGenerator = Callable[[], str]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def tracking_run_uuid_generator() -> str:
|
|
8
|
+
return str(uuid.uuid4())
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def track_id_generator() -> Iterator[int]:
|
|
12
|
+
track_id: int = 0
|
|
13
|
+
while True:
|
|
14
|
+
track_id += 1
|
|
15
|
+
yield track_id
|
|
@@ -5,7 +5,8 @@ from typing import Generic, Iterator, TypeVar
|
|
|
5
5
|
|
|
6
6
|
from tqdm import tqdm
|
|
7
7
|
|
|
8
|
-
from OTVision.config import
|
|
8
|
+
from OTVision.application.config import DEFAULT_FILETYPE, TRACK
|
|
9
|
+
from OTVision.config import CONFIG
|
|
9
10
|
from OTVision.dataformat import (
|
|
10
11
|
DATA,
|
|
11
12
|
DETECTIONS,
|
|
@@ -10,7 +10,7 @@ from OTVision.domain.frame import (
|
|
|
10
10
|
TrackedFrame,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
IdGenerator = Iterator[TrackId]
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Tracker(ABC):
|
|
@@ -23,7 +23,7 @@ class Tracker(ABC):
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
def track(
|
|
26
|
-
self, frames: Iterator[DetectedFrame], id_generator:
|
|
26
|
+
self, frames: Iterator[DetectedFrame], id_generator: IdGenerator
|
|
27
27
|
) -> Iterator[TrackedFrame]:
|
|
28
28
|
"""Process the given stream of Frames,
|
|
29
29
|
yielding TrackedFrames one by one as a lazy stream of TrackedFrames.
|
|
@@ -31,7 +31,7 @@ class Tracker(ABC):
|
|
|
31
31
|
Args:
|
|
32
32
|
frames (Iterator[DetectedFrame]): (lazy) stream of Frames
|
|
33
33
|
with untracked Detections.
|
|
34
|
-
id_generator (
|
|
34
|
+
id_generator (IdGenerator): provider of new (unique) track ids.
|
|
35
35
|
|
|
36
36
|
Yields:
|
|
37
37
|
Iterator[TrackedFrame]: (lazy) stream of TrackedFrames with
|
|
@@ -44,7 +44,7 @@ class Tracker(ABC):
|
|
|
44
44
|
def track_frame(
|
|
45
45
|
self,
|
|
46
46
|
frame: DetectedFrame,
|
|
47
|
-
id_generator:
|
|
47
|
+
id_generator: IdGenerator,
|
|
48
48
|
) -> TrackedFrame:
|
|
49
49
|
"""Process single Frame with untracked Detections,
|
|
50
50
|
by adding tracking information,
|
|
@@ -52,7 +52,7 @@ class Tracker(ABC):
|
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
54
|
frame (DetectedFrame): the Frame to be tracked.
|
|
55
|
-
id_generator (
|
|
55
|
+
id_generator (IdGenerator): provider of new (unique) track ids.
|
|
56
56
|
|
|
57
57
|
Returns:
|
|
58
58
|
TrackedFrame: TrackedFrame with TrackedDetections
|
|
@@ -270,7 +270,7 @@ class UnfinishedFramesBuffer(UnfinishedTracksBuffer[TrackedFrame, FinishedFrame]
|
|
|
270
270
|
self._tracker = tracker
|
|
271
271
|
|
|
272
272
|
def track(
|
|
273
|
-
self, frames: Iterator[DetectedFrame], id_generator:
|
|
273
|
+
self, frames: Iterator[DetectedFrame], id_generator: IdGenerator
|
|
274
274
|
) -> Iterator[FinishedFrame]:
|
|
275
275
|
tracked_frame_stream = self._tracker.track(frames, id_generator)
|
|
276
276
|
return self.track_and_finish(tracked_frame_stream)
|
|
@@ -2,7 +2,9 @@ import re
|
|
|
2
2
|
from datetime import datetime, timedelta
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from OTVision import version
|
|
5
|
+
from OTVision import dataformat, version
|
|
6
|
+
from OTVision.application.config import TrackConfig
|
|
7
|
+
from OTVision.application.get_current_config import GetCurrentConfig
|
|
6
8
|
from OTVision.dataformat import (
|
|
7
9
|
EXPECTED_DURATION,
|
|
8
10
|
FILENAME,
|
|
@@ -16,6 +18,7 @@ from OTVision.dataformat import (
|
|
|
16
18
|
TRACKING,
|
|
17
19
|
VIDEO,
|
|
18
20
|
)
|
|
21
|
+
from OTVision.detect.otdet import parse_video_length
|
|
19
22
|
from OTVision.helpers.files import (
|
|
20
23
|
FULL_FILE_NAME_PATTERN,
|
|
21
24
|
HOSTNAME,
|
|
@@ -30,12 +33,27 @@ MISSING_EXPECTED_DURATION = timedelta(minutes=15)
|
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
class TimeThresholdFrameGroupParser(FrameGroupParser):
|
|
36
|
+
@property
|
|
37
|
+
def config(self) -> TrackConfig:
|
|
38
|
+
return self._get_current_config.get().track
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def _tracker_data(self) -> dict:
|
|
42
|
+
return tracker_metadata(
|
|
43
|
+
sigma_l=self.config.sigma_l,
|
|
44
|
+
sigma_h=self.config.sigma_h,
|
|
45
|
+
sigma_iou=self.config.sigma_iou,
|
|
46
|
+
t_min=self.config.t_min,
|
|
47
|
+
t_miss_max=self.config.t_miss_max,
|
|
48
|
+
)
|
|
33
49
|
|
|
34
50
|
def __init__(
|
|
35
|
-
self,
|
|
51
|
+
self,
|
|
52
|
+
get_current_config: GetCurrentConfig,
|
|
53
|
+
time_without_frames: timedelta = timedelta(minutes=1),
|
|
36
54
|
):
|
|
55
|
+
self._get_current_config = get_current_config
|
|
37
56
|
self._time_without_frames = time_without_frames
|
|
38
|
-
self._tracker_data: dict = tracker_data
|
|
39
57
|
self._id_count = 0
|
|
40
58
|
|
|
41
59
|
def new_id(self) -> int:
|
|
@@ -86,8 +104,7 @@ class TimeThresholdFrameGroupParser(FrameGroupParser):
|
|
|
86
104
|
|
|
87
105
|
def parse_video_length(self, metadata: dict) -> timedelta:
|
|
88
106
|
video_length = metadata[VIDEO][LENGTH]
|
|
89
|
-
|
|
90
|
-
return timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
|
|
107
|
+
return parse_video_length(video_length)
|
|
91
108
|
|
|
92
109
|
def update_metadata(self, frame_group: FrameGroup) -> dict[Path, dict]:
|
|
93
110
|
metadata_by_file = dict(frame_group.metadata_by_file)
|
|
@@ -125,3 +142,16 @@ class TimeThresholdFrameGroupParser(FrameGroupParser):
|
|
|
125
142
|
last_group = current_group
|
|
126
143
|
merged_groups.append(last_group)
|
|
127
144
|
return merged_groups
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def tracker_metadata(
|
|
148
|
+
sigma_l: float, sigma_h: float, sigma_iou: float, t_min: float, t_miss_max: float
|
|
149
|
+
) -> dict:
|
|
150
|
+
return {
|
|
151
|
+
dataformat.NAME: "IOU",
|
|
152
|
+
dataformat.SIGMA_L: sigma_l,
|
|
153
|
+
dataformat.SIGMA_H: sigma_h,
|
|
154
|
+
dataformat.SIGMA_IOU: sigma_iou,
|
|
155
|
+
dataformat.T_MIN: t_min,
|
|
156
|
+
dataformat.T_MISS_MAX: t_miss_max,
|
|
157
|
+
}
|