OTVision 0.6.7__py3-none-any.whl → 0.6.9__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.
Files changed (49) hide show
  1. OTVision/abstraction/pipes_and_filter.py +4 -4
  2. OTVision/application/buffer.py +5 -12
  3. OTVision/application/config.py +18 -0
  4. OTVision/application/config_parser.py +68 -11
  5. OTVision/application/detect/current_object_detector.py +2 -4
  6. OTVision/application/detect/update_detect_config_with_cli_args.py +16 -0
  7. OTVision/application/event/__init__.py +0 -0
  8. OTVision/application/event/new_otvision_config.py +6 -0
  9. OTVision/application/event/new_video_start.py +9 -0
  10. OTVision/application/{detect/detection_file_save_path_provider.py → otvision_save_path_provider.py} +8 -7
  11. OTVision/application/track/ottrk.py +203 -0
  12. OTVision/application/track/tracking_run_id.py +35 -0
  13. OTVision/application/video/__init__.py +0 -0
  14. OTVision/application/video/generate_video.py +15 -0
  15. OTVision/config.py +2 -0
  16. OTVision/detect/builder.py +35 -22
  17. OTVision/detect/cli.py +44 -1
  18. OTVision/detect/detected_frame_buffer.py +13 -1
  19. OTVision/detect/detected_frame_producer.py +14 -0
  20. OTVision/detect/detected_frame_producer_factory.py +39 -0
  21. OTVision/detect/file_based_detect_builder.py +39 -3
  22. OTVision/detect/otdet.py +109 -41
  23. OTVision/detect/otdet_file_writer.py +52 -29
  24. OTVision/detect/rtsp_based_detect_builder.py +35 -3
  25. OTVision/detect/rtsp_input_source.py +134 -37
  26. OTVision/detect/video_input_source.py +46 -14
  27. OTVision/detect/yolo.py +12 -8
  28. OTVision/domain/cli.py +10 -0
  29. OTVision/domain/detect_producer_consumer.py +3 -3
  30. OTVision/domain/frame.py +12 -0
  31. OTVision/domain/input_source_detect.py +4 -6
  32. OTVision/domain/object_detection.py +4 -6
  33. OTVision/domain/time.py +2 -2
  34. OTVision/domain/video_writer.py +30 -0
  35. OTVision/helpers/date.py +16 -0
  36. OTVision/plugin/ffmpeg_video_writer.py +298 -0
  37. OTVision/plugin/generate_video.py +24 -0
  38. OTVision/track/builder.py +2 -5
  39. OTVision/track/id_generator.py +1 -3
  40. OTVision/track/parser/chunk_parser_plugins.py +1 -19
  41. OTVision/track/parser/frame_group_parser_plugins.py +19 -74
  42. OTVision/track/stream_ottrk_file_writer.py +116 -0
  43. OTVision/track/track.py +2 -1
  44. OTVision/version.py +1 -1
  45. {otvision-0.6.7.dist-info → otvision-0.6.9.dist-info}/METADATA +6 -5
  46. {otvision-0.6.7.dist-info → otvision-0.6.9.dist-info}/RECORD +48 -36
  47. OTVision/application/detect/detected_frame_producer.py +0 -24
  48. {otvision-0.6.7.dist-info → otvision-0.6.9.dist-info}/WHEEL +0 -0
  49. {otvision-0.6.7.dist-info → otvision-0.6.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Generator
2
+ from typing import Iterator
3
3
 
4
4
 
5
5
  class Filter[IN, OUT](ABC):
@@ -14,14 +14,14 @@ class Filter[IN, OUT](ABC):
14
14
  """
15
15
 
16
16
  @abstractmethod
17
- def filter(self, pipe: Generator[IN, None, None]) -> Generator[OUT, None, None]:
17
+ def filter(self, pipe: Iterator[IN]) -> Iterator[OUT]:
18
18
  """Process elements from the input pipe and produce output elements.
19
19
 
20
20
  Args:
21
- pipe (Generator[IN, None, None]): Input stream of elements to be processed.
21
+ pipe (Iterator[IN]): Input stream of elements to be processed.
22
22
 
23
23
  Returns:
24
- Generator[OUT, None, None]: Output stream of processed elements.
24
+ Iterator[OUT]: Output stream of processed elements.
25
25
 
26
26
  """
27
27
 
@@ -1,16 +1,14 @@
1
1
  from abc import abstractmethod
2
- from typing import Generator
2
+ from typing import Iterator
3
3
 
4
- from OTVision.abstraction.observer import Observable, Subject
5
4
  from OTVision.abstraction.pipes_and_filter import Filter
6
5
 
7
6
 
8
- class Buffer[T, SUBJECT_TYPE, OBSERVING_TYPE](Observable[SUBJECT_TYPE], Filter[T, T]):
9
- def __init__(self, subject: Subject[SUBJECT_TYPE]) -> None:
10
- super().__init__(subject)
7
+ class Buffer[T, OBSERVING_TYPE](Filter[T, T]):
8
+ def __init__(self) -> None:
11
9
  self._buffer: list[T] = []
12
10
 
13
- def filter(self, pipe: Generator[T, None, None]) -> Generator[T, None, None]:
11
+ def filter(self, pipe: Iterator[T]) -> Iterator[T]:
14
12
  for element in pipe:
15
13
  self.buffer(element)
16
14
  yield element
@@ -25,11 +23,6 @@ class Buffer[T, SUBJECT_TYPE, OBSERVING_TYPE](Observable[SUBJECT_TYPE], Filter[T
25
23
  del self._buffer
26
24
  self._buffer = list()
27
25
 
28
- def on_flush(self, event: OBSERVING_TYPE) -> None:
29
- buffered_elements = self._get_buffered_elements()
30
- self._notify_observers(buffered_elements, event)
31
- self._reset_buffer()
32
-
33
26
  @abstractmethod
34
- def _notify_observers(self, elements: list[T], event: OBSERVING_TYPE) -> None:
27
+ def on_flush(self, event: OBSERVING_TYPE) -> None:
35
28
  raise NotImplementedError
@@ -2,6 +2,12 @@ from dataclasses import dataclass, field
2
2
  from datetime import datetime, timedelta
3
3
  from pathlib import Path
4
4
 
5
+ from OTVision.plugin.ffmpeg_video_writer import (
6
+ ConstantRateFactor,
7
+ EncodingSpeed,
8
+ VideoCodec,
9
+ )
10
+
5
11
  AVAILABLE_WEIGHTS = "AVAILABLEWEIGHTS"
6
12
  CALIBRATIONS = "CALIBRATIONS"
7
13
  COL_WIDTH = "COLWIDTH"
@@ -58,6 +64,10 @@ LOG_DIR = "LOG_DIR"
58
64
  START_TIME = "START_TIME"
59
65
  DETECT_END = "DETECT_END"
60
66
  DETECT_START = "DETECT_START"
67
+ WRITE_VIDEO = "WRITE_VIDEO"
68
+ VIDEO_CODEC = "VIDEO_CODEC"
69
+ ENCODING_SPEED = "ENCODING_SPEED"
70
+ CRF = "CRF"
61
71
  DATETIME_FORMAT = "%Y-%m-%d_%H-%M-%S"
62
72
  DEFAULT_EXPECTED_DURATION: timedelta = timedelta(minutes=15)
63
73
  """Default length of a video is 15 minutes."""
@@ -289,6 +299,10 @@ class DetectConfig:
289
299
  start_time: datetime | None = None
290
300
  detect_start: int | None = None
291
301
  detect_end: int | None = None
302
+ write_video: bool = False
303
+ video_codec: VideoCodec = VideoCodec.H264_SOFTWARE
304
+ encoding_speed: EncodingSpeed = EncodingSpeed.FAST
305
+ crf: ConstantRateFactor = ConstantRateFactor.DEFAULT
292
306
 
293
307
  def to_dict(self) -> dict:
294
308
  expected_duration = (
@@ -306,6 +320,10 @@ class DetectConfig:
306
320
  START_TIME: self.start_time,
307
321
  DETECT_START: self.detect_start,
308
322
  DETECT_END: self.detect_end,
323
+ WRITE_VIDEO: self.write_video,
324
+ VIDEO_CODEC: self.video_codec.value,
325
+ ENCODING_SPEED: self.encoding_speed.value,
326
+ CRF: self.crf.name,
309
327
  }
310
328
 
311
329
 
@@ -5,12 +5,14 @@ from OTVision.application.config import (
5
5
  COL_WIDTH,
6
6
  CONF,
7
7
  CONVERT,
8
+ CRF,
8
9
  DATETIME_FORMAT,
9
10
  DEFAULT_FILETYPE,
10
11
  DELETE_INPUT,
11
12
  DETECT,
12
13
  DETECT_END,
13
14
  DETECT_START,
15
+ ENCODING_SPEED,
14
16
  EXPECTED_DURATION,
15
17
  FLUSH_BUFFER_SIZE,
16
18
  FONT,
@@ -51,8 +53,10 @@ from OTVision.application.config import (
51
53
  TRANSFORM,
52
54
  UNDISTORT,
53
55
  VID,
56
+ VIDEO_CODEC,
54
57
  WEIGHTS,
55
58
  WINDOW,
59
+ WRITE_VIDEO,
56
60
  YOLO,
57
61
  Config,
58
62
  ConvertConfig,
@@ -71,14 +75,21 @@ from OTVision.application.config import (
71
75
  from OTVision.domain.serialization import Deserializer
72
76
 
73
77
 
78
+ class InvalidOtvisionConfigError(Exception):
79
+ pass
80
+
81
+
74
82
  class ConfigParser:
75
83
 
76
84
  def __init__(self, deserializer: Deserializer) -> None:
77
85
  self._deserialize = deserializer
78
86
 
79
87
  def parse(self, file: Path) -> Config:
80
- data = self._deserialize.deserialize(file)
81
- return self.parse_from_dict(data)
88
+ raw_data = self._deserialize.deserialize(file)
89
+ config = self.parse_from_dict(raw_data)
90
+
91
+ self.validate_config(config)
92
+ return config
82
93
 
83
94
  def parse_from_dict(self, d: dict) -> Config:
84
95
  log_dict = d.get(LOG)
@@ -181,15 +192,19 @@ class ConfigParser:
181
192
 
182
193
  start_time = self._parse_start_time(data)
183
194
  return DetectConfig(
184
- sources,
185
- data.get(RUN_CHAINED, DetectConfig.run_chained),
186
- yolo_config,
187
- expected_duration,
188
- data.get(OVERWRITE, DetectConfig.overwrite),
189
- data.get(HALF_PRECISION, DetectConfig.half_precision),
190
- start_time,
191
- data.get(DETECT_START, DetectConfig.detect_start),
192
- data.get(DETECT_END, DetectConfig.detect_end),
195
+ paths=sources,
196
+ run_chained=data.get(RUN_CHAINED, DetectConfig.run_chained),
197
+ yolo_config=yolo_config,
198
+ expected_duration=expected_duration,
199
+ overwrite=data.get(OVERWRITE, DetectConfig.overwrite),
200
+ half_precision=data.get(HALF_PRECISION, DetectConfig.half_precision),
201
+ start_time=start_time,
202
+ detect_start=data.get(DETECT_START, DetectConfig.detect_start),
203
+ detect_end=data.get(DETECT_END, DetectConfig.detect_end),
204
+ write_video=data.get(WRITE_VIDEO, DetectConfig.write_video),
205
+ video_codec=data.get(VIDEO_CODEC, DetectConfig.video_codec),
206
+ encoding_speed=data.get(ENCODING_SPEED, DetectConfig.encoding_speed),
207
+ crf=data.get(CRF, DetectConfig.crf),
193
208
  )
194
209
 
195
210
  def parse_yolo_config(self, data: dict) -> YoloConfig:
@@ -278,3 +293,45 @@ class ConfigParser:
278
293
  save_dir=save_dir,
279
294
  flush_buffer_size=flush_buffer_size,
280
295
  )
296
+
297
+ def validate_config(self, config: Config) -> None:
298
+ self.validate_flush_buffer_support_track_lifecycle(config)
299
+
300
+ def validate_flush_buffer_support_track_lifecycle(self, config: Config) -> None:
301
+ """Validate that the flush buffer size supports complete track lifecycle.
302
+
303
+ In streaming mode, the flush buffer size must be larger than the tracking
304
+ parameters t_min and t_miss_max to ensure tracks can complete their full
305
+ lifecycle before being flushed. This prevents premature flushing of
306
+ unfinished tracks.
307
+
308
+ Args:
309
+ config (Config): The configuration to validate
310
+
311
+ Raises:
312
+ InvalidOtvisionConfigError: If the flush buffer size is not greater
313
+ than both t_min and t_miss_max values
314
+
315
+ Note:
316
+ This validation only applies when stream configuration is present.
317
+ The constraint ensures that:
318
+ - Tracks have enough frames to reach minimum track length (t_min)
319
+ - Tracks can handle maximum missing frames (t_miss_max) before completion
320
+ """
321
+
322
+ if config.stream is None:
323
+ return
324
+
325
+ flush_buffer_size = config.stream.flush_buffer_size
326
+ if (
327
+ config.track.t_min < flush_buffer_size
328
+ and config.track.t_miss_max < flush_buffer_size
329
+ ):
330
+ return
331
+
332
+ raise InvalidOtvisionConfigError(
333
+ f"The flush buffer size ({flush_buffer_size}) must be greater than the "
334
+ f"t_min ({config.track.t_min}) and t_miss_max "
335
+ f"({config.track.t_miss_max}) values to allow tracks to complete "
336
+ "before flushing."
337
+ )
@@ -1,4 +1,4 @@
1
- from typing import Generator
1
+ from typing import Iterator
2
2
 
3
3
  from OTVision.abstraction.pipes_and_filter import Filter
4
4
  from OTVision.application.get_current_config import GetCurrentConfig
@@ -33,7 +33,5 @@ class CurrentObjectDetector(Filter[Frame, DetectedFrame]):
33
33
  detect_config = self._get_current_config.get().detect
34
34
  return self._factory.create(detect_config)
35
35
 
36
- def filter(
37
- self, pipe: Generator[Frame, None, None]
38
- ) -> Generator[DetectedFrame, None, None]:
36
+ def filter(self, pipe: Iterator[Frame]) -> Iterator[DetectedFrame]:
39
37
  return self.get().detect(pipe)
@@ -84,6 +84,22 @@ class UpdateDetectConfigWithCliArgs:
84
84
  if cli_args.detect_end is not None
85
85
  else detect_config.detect_end
86
86
  ),
87
+ write_video=(
88
+ cli_args.write_video
89
+ if cli_args.write_video is not None
90
+ else detect_config.write_video
91
+ ),
92
+ video_codec=(
93
+ cli_args.video_codec
94
+ if cli_args.video_codec is not None
95
+ else detect_config.video_codec
96
+ ),
97
+ encoding_speed=(
98
+ cli_args.encoding_speed
99
+ if cli_args.encoding_speed is not None
100
+ else detect_config.encoding_speed
101
+ ),
102
+ crf=cli_args.crf if cli_args.crf is not None else detect_config.crf,
87
103
  )
88
104
 
89
105
  def _update_log_config(self, config: Config, cli_args: DetectCliArgs) -> _LogConfig:
File without changes
@@ -0,0 +1,6 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(slots=True)
5
+ class NewOtvisionConfigEvent:
6
+ pass
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(frozen=True)
5
+ class NewVideoStartEvent:
6
+ output: str
7
+ width: int
8
+ height: int
9
+ fps: float
@@ -3,8 +3,9 @@ from pathlib import Path
3
3
  from OTVision.application.get_current_config import GetCurrentConfig
4
4
 
5
5
 
6
- class DetectionFileSavePathProvider:
7
- """Provides a mechanism to generate file save paths for detections.
6
+ class OtvisionSavePathProvider:
7
+ """Provides a mechanism to generate file save paths for OTVision tasks such as
8
+ detect and track.
8
9
 
9
10
  This class is responsible for deriving the appropriate filenames for
10
11
  detection files based on the source and current configuration
@@ -19,11 +20,11 @@ class DetectionFileSavePathProvider:
19
20
  def __init__(self, get_current_config: GetCurrentConfig) -> None:
20
21
  self._get_current_config = get_current_config
21
22
 
22
- def provide(self, source: str) -> Path:
23
+ def provide(self, source: str, file_type: str) -> Path:
23
24
  config = self._get_current_config.get()
24
25
  return derive_filename(
25
26
  video_file=Path(source),
26
- detect_suffix=config.filetypes.detect,
27
+ file_type=file_type,
27
28
  detect_start=config.detect.detect_start,
28
29
  detect_end=config.detect.detect_end,
29
30
  )
@@ -31,7 +32,7 @@ class DetectionFileSavePathProvider:
31
32
 
32
33
  def derive_filename(
33
34
  video_file: Path,
34
- detect_suffix: str,
35
+ file_type: str,
35
36
  detect_start: int | None = None,
36
37
  detect_end: int | None = None,
37
38
  ) -> Path:
@@ -45,7 +46,7 @@ def derive_filename(
45
46
  If None, no starting marker will be appended.
46
47
  detect_end (int | None): The ending marker to append to the filename. If None,
47
48
  no ending marker will be appended.
48
- detect_suffix (str): The file suffix to apply to the derived filename.
49
+ file_type (str): The file suffix to apply to the derived filename.
49
50
 
50
51
  Returns:
51
52
  Path: The modified video file path with the updated stem and suffix applied.
@@ -56,4 +57,4 @@ def derive_filename(
56
57
  if detect_end is not None:
57
58
  cutout += f"_end_{detect_end}"
58
59
  new_stem = f"{video_file.stem}{cutout}"
59
- return video_file.with_stem(new_stem).with_suffix(detect_suffix)
60
+ return video_file.with_stem(new_stem).with_suffix(file_type)
@@ -0,0 +1,203 @@
1
+ from collections import defaultdict
2
+ from dataclasses import dataclass
3
+ from datetime import datetime
4
+ from typing import Self
5
+
6
+ from OTVision import dataformat, version
7
+ from OTVision.detect.otdet import (
8
+ OtdetBuilderConfig,
9
+ OtdetMetadataBuilder,
10
+ extract_expected_duration_from_otdet,
11
+ extract_start_date_from_otdet,
12
+ )
13
+ from OTVision.domain.detection import TrackedDetection, TrackId
14
+ from OTVision.domain.frame import FrameNo, TrackedFrame
15
+
16
+
17
+ @dataclass
18
+ class OttrkBuilderConfig:
19
+ otdet_builder_config: OtdetBuilderConfig
20
+ number_of_frames: int
21
+ sigma_l: float
22
+ sigma_h: float
23
+ sigma_iou: float
24
+ t_min: int
25
+ t_miss_max: int
26
+ tracking_run_id: str
27
+ frame_group: int
28
+
29
+
30
+ class OttrkBuilderError(Exception):
31
+ pass
32
+
33
+
34
+ class OttrkBuilder:
35
+ @property
36
+ def config(self) -> OttrkBuilderConfig:
37
+ if self._config is None:
38
+ raise OttrkBuilderError("Ottrk builder config is not set")
39
+ return self._config
40
+
41
+ def __init__(self, otdet_metadata_builder: OtdetMetadataBuilder) -> None:
42
+ self._config: OttrkBuilderConfig | None = None
43
+ self._otdet_metadata_builder = otdet_metadata_builder
44
+ self._tracked_detections: dict[TrackId, list[dict]] = defaultdict(list)
45
+
46
+ def build(self) -> dict:
47
+ result = {
48
+ dataformat.METADATA: self.build_metadata(),
49
+ dataformat.DATA: {
50
+ dataformat.DETECTIONS: self._build_data(),
51
+ },
52
+ }
53
+ self.reset()
54
+ return result
55
+
56
+ def build_metadata(self) -> dict:
57
+ otdet_metadata = self._otdet_metadata_builder.build(
58
+ self.config.number_of_frames
59
+ )
60
+ start_date = extract_start_date_from_otdet(otdet_metadata)
61
+ duration = extract_expected_duration_from_otdet(otdet_metadata)
62
+ end_date = start_date + duration
63
+ ottrk_metadata = self._build_track_metadata(start_date, end_date)
64
+ return {
65
+ **otdet_metadata,
66
+ **ottrk_metadata,
67
+ }
68
+
69
+ def _build_track_metadata(self, start_date: datetime, end_date: datetime) -> dict:
70
+ result = create_ottrk_metadata_entry(
71
+ start_date=start_date,
72
+ end_date=end_date,
73
+ sigma_l=self.config.sigma_l,
74
+ sigma_h=self.config.sigma_h,
75
+ sigma_iou=self.config.sigma_iou,
76
+ t_min=self.config.t_min,
77
+ t_miss_max=self.config.t_miss_max,
78
+ )
79
+ result[dataformat.TRACKING_RUN_ID] = self.config.tracking_run_id
80
+ result[dataformat.FRAME_GROUP] = self.config.frame_group
81
+ return result
82
+
83
+ def _build_data(self) -> list[dict]:
84
+ return sorted(
85
+ (
86
+ detection
87
+ for detections in self._tracked_detections.values()
88
+ for detection in detections
89
+ ),
90
+ key=lambda detection: (
91
+ detection[dataformat.FRAME],
92
+ detection[dataformat.OCCURRENCE],
93
+ ),
94
+ )
95
+
96
+ def set_config(self, config: OttrkBuilderConfig) -> Self:
97
+ self._config = config
98
+ self._otdet_metadata_builder.add_config(config.otdet_builder_config)
99
+ return self
100
+
101
+ def add_tracked_frames(self, tracked_frames: list[TrackedFrame]) -> Self:
102
+ finished_tracks = set()
103
+ discarded_tracks = set()
104
+
105
+ for tracked_frame in tracked_frames:
106
+ finished_tracks.update(tracked_frame.finished_tracks)
107
+ discarded_tracks.update(tracked_frame.discarded_tracks)
108
+
109
+ for detection in tracked_frame.detections:
110
+ self._tracked_detections[detection.track_id].append(
111
+ self._serialize_tracked_detection(
112
+ frame_no=tracked_frame.no,
113
+ detection=detection,
114
+ occurrence=tracked_frame.occurrence,
115
+ )
116
+ )
117
+ self.__sort_detections()
118
+ self.discard_tracks(discarded_tracks)
119
+ self.finish_tracks(finished_tracks)
120
+ return self
121
+
122
+ def _serialize_tracked_detection(
123
+ self,
124
+ frame_no: FrameNo,
125
+ detection: TrackedDetection,
126
+ occurrence: datetime,
127
+ ) -> dict:
128
+ result = detection.to_otdet()
129
+ result[dataformat.INTERPOLATED_DETECTION] = False
130
+ result[dataformat.FIRST] = detection.is_first
131
+ result[dataformat.FINISHED] = False
132
+ result[dataformat.TRACK_ID] = detection.track_id
133
+ result[dataformat.FRAME] = frame_no
134
+ result[dataformat.OCCURRENCE] = occurrence.timestamp()
135
+ result[dataformat.INPUT_FILE_PATH] = self.config.otdet_builder_config.source
136
+ return result
137
+
138
+ def __sort_detections(self) -> None:
139
+ for detections in self._tracked_detections.values():
140
+ detections.sort(key=lambda detection: (detection[dataformat.OCCURRENCE]))
141
+
142
+ def reset(self) -> Self:
143
+ self._config = None
144
+ self._otdet_metadata_builder.reset()
145
+ self._tracked_detections = defaultdict(list)
146
+ return self
147
+
148
+ def discard_tracks(self, tracks: set[TrackId]) -> Self:
149
+ for track_id in tracks:
150
+ self.discard_track(track_id)
151
+ return self
152
+
153
+ def discard_track(self, track_id: TrackId) -> Self:
154
+ try:
155
+ del self._tracked_detections[track_id]
156
+ except KeyError:
157
+ pass
158
+ return self
159
+
160
+ def finish_tracks(self, tracks: set[TrackId]) -> Self:
161
+ for track_id in tracks:
162
+ self.finish_track(track_id)
163
+ return self
164
+
165
+ def finish_track(self, track_id: TrackId) -> Self:
166
+ if track_id in self._tracked_detections:
167
+ self._tracked_detections[track_id][-1][dataformat.FINISHED] = True
168
+ return self
169
+
170
+
171
+ def create_ottrk_metadata_entry(
172
+ start_date: datetime,
173
+ end_date: datetime,
174
+ sigma_l: float,
175
+ sigma_h: float,
176
+ sigma_iou: float,
177
+ t_min: int,
178
+ t_miss_max: int,
179
+ ) -> dict:
180
+ return {
181
+ dataformat.OTTRACK_VERSION: version.ottrack_version(),
182
+ dataformat.TRACKING: {
183
+ dataformat.OTVISION_VERSION: version.otvision_version(),
184
+ dataformat.FIRST_TRACKED_VIDEO_START: start_date.timestamp(),
185
+ dataformat.LAST_TRACKED_VIDEO_END: end_date.timestamp(),
186
+ dataformat.TRACKER: create_tracker_metadata(
187
+ sigma_l, sigma_h, sigma_iou, t_min, t_miss_max
188
+ ),
189
+ },
190
+ }
191
+
192
+
193
+ def create_tracker_metadata(
194
+ sigma_l: float, sigma_h: float, sigma_iou: float, t_min: int, t_miss_max: int
195
+ ) -> dict:
196
+ return {
197
+ dataformat.NAME: "IOU",
198
+ dataformat.SIGMA_L: sigma_l,
199
+ dataformat.SIGMA_H: sigma_h,
200
+ dataformat.SIGMA_IOU: sigma_iou,
201
+ dataformat.T_MIN: t_min,
202
+ dataformat.T_MISS_MAX: t_miss_max,
203
+ }
@@ -0,0 +1,35 @@
1
+ from typing import Callable
2
+
3
+ StrIdGenerator = Callable[[], str]
4
+
5
+
6
+ class CurrentTrackingRunId:
7
+ def __init__(self) -> None:
8
+ self._current_tracking_run_id: str | None = None
9
+
10
+ def update(self, tracking_run_id: str) -> None:
11
+ self._current_tracking_run_id = tracking_run_id
12
+
13
+ def get(self) -> str:
14
+ if self._current_tracking_run_id is None:
15
+ raise ValueError("Tracking run id is not set!")
16
+ return self._current_tracking_run_id
17
+
18
+
19
+ class GenerateNewTrackingRunId:
20
+ def __init__(
21
+ self, id_generator: StrIdGenerator, current: CurrentTrackingRunId
22
+ ) -> None:
23
+ self._generate_id = id_generator
24
+ self._current = current
25
+
26
+ def generate(self) -> None:
27
+ self._current.update(self._generate_id())
28
+
29
+
30
+ class GetCurrentTrackingRunId:
31
+ def __init__(self, current: CurrentTrackingRunId) -> None:
32
+ self._current = current
33
+
34
+ def get(self) -> str:
35
+ return self._current.get()
File without changes
@@ -0,0 +1,15 @@
1
+ from OTVision.abstraction.pipes_and_filter import Filter
2
+ from OTVision.domain.frame import Frame
3
+ from OTVision.domain.input_source_detect import InputSourceDetect
4
+
5
+
6
+ class GenerateVideo:
7
+ def __init__(
8
+ self, input_source: InputSourceDetect, video_writer: Filter[Frame, Frame]
9
+ ) -> None:
10
+ self._input_source = input_source
11
+ self._video_writer = video_writer
12
+
13
+ def generate(self) -> None:
14
+ for frame in self._video_writer.filter(self._input_source.produce()):
15
+ pass
OTVision/config.py CHANGED
@@ -77,6 +77,7 @@ from OTVision.application.config import (
77
77
  VIDEOS,
78
78
  WEIGHTS,
79
79
  WINDOW,
80
+ WRITE_VIDEO,
80
81
  YOLO,
81
82
  Config,
82
83
  )
@@ -176,6 +177,7 @@ CONFIG[DETECT][OVERWRITE] = True
176
177
  CONFIG[DETECT][HALF_PRECISION] = False
177
178
  CONFIG[DETECT][DETECT_START] = None
178
179
  CONFIG[DETECT][DETECT_END] = None
180
+ CONFIG[DETECT][WRITE_VIDEO] = False
179
181
 
180
182
  # TRACK
181
183
  CONFIG[TRACK] = {}