OTVision 0.5.5__py3-none-any.whl → 0.6.0__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/__init__.py +0 -0
- OTVision/abstraction/observer.py +43 -0
- OTVision/abstraction/pipes_and_filter.py +28 -0
- OTVision/application/buffer.py +34 -0
- OTVision/application/detect/current_object_detector.py +40 -0
- OTVision/application/detect/current_object_detector_metadata.py +27 -0
- OTVision/application/detect/detected_frame_factory.py +26 -0
- OTVision/application/detect/detected_frame_producer.py +25 -0
- OTVision/application/detect/detection_file_save_path_provider.py +59 -0
- OTVision/application/detect/factory.py +25 -0
- OTVision/application/detect/timestamper.py +25 -0
- OTVision/application/detect/update_detect_config_with_cli_args.py +5 -0
- OTVision/application/frame_count_provider.py +23 -0
- OTVision/application/get_current_config.py +10 -0
- OTVision/application/update_current_config.py +10 -0
- OTVision/config.py +70 -1
- OTVision/detect/builder.py +133 -1
- OTVision/detect/cli.py +16 -1
- OTVision/detect/detect.py +11 -280
- OTVision/detect/detected_frame_buffer.py +57 -0
- OTVision/detect/otdet.py +36 -15
- OTVision/detect/otdet_file_writer.py +106 -0
- OTVision/detect/pyav_frame_count_provider.py +15 -0
- OTVision/detect/timestamper.py +152 -0
- OTVision/detect/video_input_source.py +213 -0
- OTVision/detect/yolo.py +198 -205
- OTVision/domain/cli.py +2 -1
- OTVision/domain/current_config.py +12 -0
- OTVision/domain/detect_producer_consumer.py +30 -0
- OTVision/domain/detection.py +20 -0
- OTVision/domain/frame.py +29 -0
- OTVision/domain/input_source_detect.py +31 -0
- OTVision/domain/object_detection.py +47 -0
- OTVision/helpers/files.py +1 -1
- OTVision/helpers/video.py +6 -0
- OTVision/track/preprocess.py +4 -2
- OTVision/version.py +1 -1
- {otvision-0.5.5.dist-info → otvision-0.6.0.dist-info}/METADATA +1 -1
- otvision-0.6.0.dist-info/RECORD +75 -0
- otvision-0.5.5.dist-info/RECORD +0 -50
- {otvision-0.5.5.dist-info → otvision-0.6.0.dist-info}/WHEEL +0 -0
- {otvision-0.5.5.dist-info → otvision-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Callable, TypeVar
|
|
2
|
+
|
|
3
|
+
VALUE = TypeVar("VALUE")
|
|
4
|
+
type Observer[T] = Callable[[T], None]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Subject[T]:
|
|
8
|
+
"""Generic subject class to handle and notify observers.
|
|
9
|
+
|
|
10
|
+
This class ensures that no duplicate observers can be registered.
|
|
11
|
+
The order that registered observers are notified is dictated by the order they have
|
|
12
|
+
been registered. Meaning, first to be registered is first to be notified.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self._observers: list[Observer[T]] = []
|
|
17
|
+
|
|
18
|
+
def register(self, observer: Observer[T]) -> None:
|
|
19
|
+
"""Listen to changes of subject.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
observer (OBSERVER): the observer to be registered. This must be a
|
|
23
|
+
`Callable`.
|
|
24
|
+
"""
|
|
25
|
+
new_observers = self._observers.copy()
|
|
26
|
+
new_observers.append(observer)
|
|
27
|
+
self._observers = list(dict.fromkeys(new_observers))
|
|
28
|
+
|
|
29
|
+
def notify(self, value: T) -> None:
|
|
30
|
+
"""Notifies observers about the list of tracks.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
value (VALUE): value to notify the observer with.
|
|
34
|
+
"""
|
|
35
|
+
[notify_observer(value) for notify_observer in self._observers]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Observable[T]:
|
|
39
|
+
def __init__(self, subject: Subject[T]) -> None:
|
|
40
|
+
self._subject = subject
|
|
41
|
+
|
|
42
|
+
def register(self, observer: Observer[T]) -> None:
|
|
43
|
+
self._subject.register(observer)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Generator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Filter[IN, OUT](ABC):
|
|
6
|
+
"""Abstract base class for implementing pipe-and-filter pattern filters.
|
|
7
|
+
|
|
8
|
+
A filter processes a stream of input elements and produces a stream of output
|
|
9
|
+
elements. This class defines the interface for all concrete filter implementations.
|
|
10
|
+
|
|
11
|
+
Type Parameters:
|
|
12
|
+
IN: The type of elements that the filter receives as input.
|
|
13
|
+
OUT: The type of elements that the filter produces as output.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def filter(self, pipe: Generator[IN, None, None]) -> Generator[OUT, None, None]:
|
|
18
|
+
"""Process elements from the input pipe and produce output elements.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
pipe (Generator[IN, None, None]): Input stream of elements to be processed.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Generator[OUT, None, None]: Output stream of processed elements.
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import Generator
|
|
3
|
+
|
|
4
|
+
from OTVision.abstraction.observer import Observable, Subject
|
|
5
|
+
from OTVision.abstraction.pipes_and_filter import Filter
|
|
6
|
+
|
|
7
|
+
|
|
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)
|
|
11
|
+
self._buffer: list[T] = []
|
|
12
|
+
|
|
13
|
+
def filter(self, pipe: Generator[T, None, None]) -> Generator[T, None, None]:
|
|
14
|
+
for element in pipe:
|
|
15
|
+
self.buffer(element)
|
|
16
|
+
yield element
|
|
17
|
+
|
|
18
|
+
def buffer(self, to_buffer: T) -> None:
|
|
19
|
+
self._buffer.append(to_buffer)
|
|
20
|
+
|
|
21
|
+
def _get_buffered_elements(self) -> list[T]:
|
|
22
|
+
return self._buffer
|
|
23
|
+
|
|
24
|
+
def _reset_buffer(self) -> None:
|
|
25
|
+
self._buffer = []
|
|
26
|
+
|
|
27
|
+
def on_flush(self, event: OBSERVING_TYPE) -> None:
|
|
28
|
+
buffered_elements = self._get_buffered_elements()
|
|
29
|
+
self._notify_observers(buffered_elements, event)
|
|
30
|
+
self._reset_buffer()
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def _notify_observers(self, elements: list[T], event: OBSERVING_TYPE) -> None:
|
|
34
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Generator
|
|
2
|
+
|
|
3
|
+
from OTVision.abstraction.pipes_and_filter import Filter
|
|
4
|
+
from OTVision.application.get_current_config import GetCurrentConfig
|
|
5
|
+
from OTVision.domain.detection import DetectedFrame
|
|
6
|
+
from OTVision.domain.frame import Frame
|
|
7
|
+
from OTVision.domain.object_detection import ObjectDetector, ObjectDetectorFactory
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CurrentObjectDetector(Filter[Frame, DetectedFrame]):
|
|
11
|
+
"""Use case to retrieve the currently used object detector.
|
|
12
|
+
|
|
13
|
+
Filter implementation for detecting objects in frames using current
|
|
14
|
+
configuration.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
get_current_config (GetCurrentConfig): Provider of current configuration.
|
|
18
|
+
factory (ObjectDetectorFactory): Factory for creating object detector instances.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self, get_current_config: GetCurrentConfig, factory: ObjectDetectorFactory
|
|
23
|
+
) -> None:
|
|
24
|
+
self._get_current_config = get_current_config
|
|
25
|
+
self._factory = factory
|
|
26
|
+
|
|
27
|
+
def get(self) -> ObjectDetector:
|
|
28
|
+
"""Retrieve the currently used object detector.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
ObjectDetector: The object detector.
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
detect_config = self._get_current_config.get().detect
|
|
35
|
+
return self._factory.create(detect_config)
|
|
36
|
+
|
|
37
|
+
def filter(
|
|
38
|
+
self, pipe: Generator[Frame, None, None]
|
|
39
|
+
) -> Generator[DetectedFrame, None, None]:
|
|
40
|
+
return self.get().detect(pipe)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from OTVision.application.detect.current_object_detector import CurrentObjectDetector
|
|
2
|
+
from OTVision.domain.object_detection import ObjectDetectorMetadata
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CurrentObjectDetectorMetadata:
|
|
6
|
+
"""Provider for metadata about the currently configured object detector.
|
|
7
|
+
|
|
8
|
+
This class serves as a wrapper to access metadata information about the
|
|
9
|
+
currently configured object detector.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
current_object_detector (CurrentObjectDetector): The current object detector
|
|
13
|
+
instance from which to retrieve metadata.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, current_object_detector: CurrentObjectDetector) -> None:
|
|
17
|
+
self.current_object_detector = current_object_detector
|
|
18
|
+
|
|
19
|
+
def get(self) -> ObjectDetectorMetadata:
|
|
20
|
+
"""Retrieve metadata about the currently configured object detector.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
ObjectDetectorMetadata: Metadata information about the current object
|
|
24
|
+
detector configuration.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
return self.current_object_detector.get()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from OTVision.domain.detection import DetectedFrame, Detection
|
|
2
|
+
from OTVision.domain.frame import Frame, FrameKeys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DetectedFrameFactory:
|
|
6
|
+
"""Factory for creating DetectedFrame objects from Frame and Detection data."""
|
|
7
|
+
|
|
8
|
+
def create(self, frame: Frame, detections: list[Detection]) -> DetectedFrame:
|
|
9
|
+
"""Creates a DetectedFrame object from a Frame and its detections.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
frame (Frame): the frame object.
|
|
13
|
+
detections (list[Detection]): The detections to be associated with
|
|
14
|
+
the frame.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
DetectedFrame: A new DetectedFrame instance containing the combined frame
|
|
18
|
+
and detection information.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
return DetectedFrame(
|
|
22
|
+
source=frame[FrameKeys.source],
|
|
23
|
+
frame_number=frame[FrameKeys.frame],
|
|
24
|
+
occurrence=frame[FrameKeys.occurrence],
|
|
25
|
+
detections=detections,
|
|
26
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Generator
|
|
2
|
+
|
|
3
|
+
from OTVision.abstraction.pipes_and_filter import Filter
|
|
4
|
+
from OTVision.domain.detect_producer_consumer import DetectedFrameProducer
|
|
5
|
+
from OTVision.domain.detection import DetectedFrame
|
|
6
|
+
from OTVision.domain.frame import Frame
|
|
7
|
+
from OTVision.domain.input_source_detect import InputSourceDetect
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SimpleDetectedFrameProducer(DetectedFrameProducer):
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
input_source: InputSourceDetect,
|
|
15
|
+
detection_filter: Filter[Frame, DetectedFrame],
|
|
16
|
+
detected_frame_buffer: Filter[DetectedFrame, DetectedFrame],
|
|
17
|
+
) -> None:
|
|
18
|
+
self._input_source = input_source
|
|
19
|
+
self._detection_filter = detection_filter
|
|
20
|
+
self._detected_frame_buffer = detected_frame_buffer
|
|
21
|
+
|
|
22
|
+
def produce(self) -> Generator[DetectedFrame, None, None]:
|
|
23
|
+
return self._detected_frame_buffer.filter(
|
|
24
|
+
self._detection_filter.filter(self._input_source.produce())
|
|
25
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from OTVision.application.get_current_config import GetCurrentConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DetectionFileSavePathProvider:
|
|
7
|
+
"""Provides a mechanism to generate file save paths for detections.
|
|
8
|
+
|
|
9
|
+
This class is responsible for deriving the appropriate filenames for
|
|
10
|
+
detection files based on the source and current configuration
|
|
11
|
+
settings. It utilizes a configuration provider to dynamically fetch
|
|
12
|
+
the required configuration values.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
get_current_config (GetCurrentConfig): Retrieve the current application
|
|
16
|
+
configuration.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, get_current_config: GetCurrentConfig) -> None:
|
|
20
|
+
self._get_current_config = get_current_config
|
|
21
|
+
|
|
22
|
+
def provide(self, source: str) -> Path:
|
|
23
|
+
config = self._get_current_config.get()
|
|
24
|
+
return derive_filename(
|
|
25
|
+
video_file=Path(source),
|
|
26
|
+
detect_suffix=config.filetypes.detect,
|
|
27
|
+
detect_start=config.detect.detect_start,
|
|
28
|
+
detect_end=config.detect.detect_end,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def derive_filename(
|
|
33
|
+
video_file: Path,
|
|
34
|
+
detect_suffix: str,
|
|
35
|
+
detect_start: int | None = None,
|
|
36
|
+
detect_end: int | None = None,
|
|
37
|
+
) -> Path:
|
|
38
|
+
"""
|
|
39
|
+
Generates a filename for detection files by appending specified start and end
|
|
40
|
+
markers and a suffix to the stem of the input video file.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
video_file (Path): The input video file whose filename is to be modified.
|
|
44
|
+
detect_start (int | None): The starting marker to append to the filename.
|
|
45
|
+
If None, no starting marker will be appended.
|
|
46
|
+
detect_end (int | None): The ending marker to append to the filename. If None,
|
|
47
|
+
no ending marker will be appended.
|
|
48
|
+
detect_suffix (str): The file suffix to apply to the derived filename.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Path: The modified video file path with the updated stem and suffix applied.
|
|
52
|
+
"""
|
|
53
|
+
cutout = ""
|
|
54
|
+
if detect_start is not None:
|
|
55
|
+
cutout += f"_start_{detect_start}"
|
|
56
|
+
if detect_end is not None:
|
|
57
|
+
cutout += f"_end_{detect_end}"
|
|
58
|
+
new_stem = f"{video_file.stem}{cutout}"
|
|
59
|
+
return video_file.with_stem(new_stem).with_suffix(detect_suffix)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from OTVision.config import DetectConfig
|
|
2
|
+
from OTVision.domain.object_detection import ObjectDetector, ObjectDetectorFactory
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ObjectDetectorCachedFactory(ObjectDetectorFactory):
|
|
6
|
+
|
|
7
|
+
def __init__(self, other: ObjectDetectorFactory) -> None:
|
|
8
|
+
self._other = other
|
|
9
|
+
self.__cache: dict[str, ObjectDetector] = {}
|
|
10
|
+
|
|
11
|
+
def create(self, config: DetectConfig) -> ObjectDetector:
|
|
12
|
+
if cached_model := self.__cache.get(config.yolo_config.weights):
|
|
13
|
+
return cached_model
|
|
14
|
+
model = self._other.create(config)
|
|
15
|
+
self.__add_to_cache(model)
|
|
16
|
+
return model
|
|
17
|
+
|
|
18
|
+
def __add_to_cache(self, model: ObjectDetector) -> None:
|
|
19
|
+
weights = model.config.yolo_config.weights
|
|
20
|
+
if not self.__cache.get(weights):
|
|
21
|
+
self.__cache[weights] = model
|
|
22
|
+
|
|
23
|
+
def __remove_from_cache(self, weights: str) -> None:
|
|
24
|
+
if self.__cache.get(weights):
|
|
25
|
+
del self.__cache[weights]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from OTVision.domain.frame import Frame
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Timestamper(ABC):
|
|
7
|
+
"""Interface for adding timestamps to frame data.
|
|
8
|
+
|
|
9
|
+
This class defines the interface for timestamp processors that convert raw frame
|
|
10
|
+
dictionaries into Frame objects with proper timestamp information.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def stamp(self, frame: dict) -> Frame:
|
|
15
|
+
"""Add timestamp information to a frame.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
frame (dict): Raw frame data dictionary to be processed.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Frame: A Frame object with added timestamp information.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
raise NotImplementedError
|
|
@@ -68,6 +68,11 @@ class UpdateDetectConfigWithCliArgs:
|
|
|
68
68
|
if cli_args.half is not None
|
|
69
69
|
else detect_config.half_precision
|
|
70
70
|
),
|
|
71
|
+
start_time=(
|
|
72
|
+
cli_args.start_time
|
|
73
|
+
if cli_args.start_time is not None
|
|
74
|
+
else detect_config.start_time
|
|
75
|
+
),
|
|
71
76
|
detect_start=(
|
|
72
77
|
cli_args.detect_start
|
|
73
78
|
if cli_args.detect_start is not None
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FrameCountProvider(ABC):
|
|
6
|
+
"""Abstract base class for retrieving the total number of frames in a video file.
|
|
7
|
+
|
|
8
|
+
This interface defines the contract for components that can determine the frame
|
|
9
|
+
count of video files. Implementations might use different methods or libraries
|
|
10
|
+
to calculate the total number of frames.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def provide(self, video_file: Path) -> int:
|
|
14
|
+
"""Get the total number of frames in a video file.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
video_file (Path): Path to the video file to analyze.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
int: Total number of frames in the video.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from OTVision.config import Config
|
|
2
|
+
from OTVision.domain.current_config import CurrentConfig
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GetCurrentConfig:
|
|
6
|
+
def __init__(self, current_config: CurrentConfig) -> None:
|
|
7
|
+
self._current_config = current_config
|
|
8
|
+
|
|
9
|
+
def get(self) -> Config:
|
|
10
|
+
return self._current_config.get()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from OTVision.config import Config
|
|
2
|
+
from OTVision.domain.current_config import CurrentConfig
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UpdateCurrentConfig:
|
|
6
|
+
def __init__(self, current_config: CurrentConfig) -> None:
|
|
7
|
+
self._current_config = current_config
|
|
8
|
+
|
|
9
|
+
def update(self, config: Config) -> None:
|
|
10
|
+
self._current_config.update(config)
|
OTVision/config.py
CHANGED
|
@@ -22,7 +22,7 @@ OTVision config module for setting default values
|
|
|
22
22
|
|
|
23
23
|
import logging
|
|
24
24
|
from dataclasses import dataclass, field
|
|
25
|
-
from datetime import timedelta
|
|
25
|
+
from datetime import datetime, timedelta
|
|
26
26
|
from pathlib import Path
|
|
27
27
|
|
|
28
28
|
import yaml
|
|
@@ -85,9 +85,12 @@ LOG = "LOG"
|
|
|
85
85
|
LOG_LEVEL_CONSOLE = "LOG_LEVEL_CONSOLE"
|
|
86
86
|
LOG_LEVEL_FILE = "LOG_LEVEL_FILE"
|
|
87
87
|
LOG_DIR = "LOG_DIR"
|
|
88
|
+
START_TIME = "START_TIME"
|
|
88
89
|
DETECT_END = "DETECT_END"
|
|
89
90
|
DETECT_START = "DETECT_START"
|
|
90
91
|
|
|
92
|
+
DATETIME_FORMAT = "%Y-%m-%d_%H-%M-%S"
|
|
93
|
+
|
|
91
94
|
"""Default length of a video is 15 minutes."""
|
|
92
95
|
DEFAULT_EXPECTED_DURATION: timedelta = timedelta(minutes=15)
|
|
93
96
|
|
|
@@ -260,6 +263,18 @@ class _YoloWeights:
|
|
|
260
263
|
|
|
261
264
|
@dataclass(frozen=True)
|
|
262
265
|
class YoloConfig:
|
|
266
|
+
"""Represents the configuration for the YOLO model.
|
|
267
|
+
|
|
268
|
+
Attributes:
|
|
269
|
+
weights (str): Path to YOLO model weights.
|
|
270
|
+
available_weights (_YoloWeights): List of available default YOLO model weights.
|
|
271
|
+
conf (float): Confidence threshold.
|
|
272
|
+
iou (float): Intersection over union threshold.
|
|
273
|
+
img_size (int): Size of the input image.
|
|
274
|
+
chunk_size (int): Chunk size for processing.
|
|
275
|
+
normalized (bool): Whether to normalize the bounding boxes.
|
|
276
|
+
"""
|
|
277
|
+
|
|
263
278
|
weights: str = _YoloWeights.yolov8s
|
|
264
279
|
available_weights: _YoloWeights = _YoloWeights()
|
|
265
280
|
conf: float = 0.25
|
|
@@ -291,12 +306,55 @@ class YoloConfig:
|
|
|
291
306
|
|
|
292
307
|
@dataclass(frozen=True)
|
|
293
308
|
class DetectConfig:
|
|
309
|
+
"""Represents the configuration for the `detect` command.
|
|
310
|
+
|
|
311
|
+
Attributes:
|
|
312
|
+
paths (list[Path]): List of files to be processed.
|
|
313
|
+
run_chained (bool): Whether to run chained commands.
|
|
314
|
+
yolo_config (YoloConfig): Configuration for the YOLO model.
|
|
315
|
+
expected_duration (timedelta | None): Expected duration of the video.
|
|
316
|
+
`None` if unknown.
|
|
317
|
+
overwrite (bool): Whether to overwrite existing files.
|
|
318
|
+
half_precision (bool): Whether to use half precision.
|
|
319
|
+
detect_start (int | None): Start frame for detection expressed in seconds.
|
|
320
|
+
Value `None` marks the start of the video.
|
|
321
|
+
detect_end (int | None): End frame for detection expressed in seconds.
|
|
322
|
+
Value `None` marks the end of the video.
|
|
323
|
+
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def confidence(self) -> float:
|
|
328
|
+
"""Gets the confidence level set in the YOLO configuration.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
float: The intersection over union threshold value.
|
|
332
|
+
"""
|
|
333
|
+
return self.yolo_config.conf
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def weights(self) -> str:
|
|
337
|
+
return self.yolo_config.weights
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def iou(self) -> float:
|
|
341
|
+
return self.yolo_config.iou
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def img_size(self) -> int:
|
|
345
|
+
return self.yolo_config.img_size
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def normalized(self) -> bool:
|
|
349
|
+
return self.yolo_config.normalized
|
|
350
|
+
|
|
294
351
|
paths: list[Path] = field(default_factory=list)
|
|
295
352
|
run_chained: bool = True
|
|
296
353
|
yolo_config: YoloConfig = YoloConfig()
|
|
297
354
|
expected_duration: timedelta | None = None
|
|
298
355
|
overwrite: bool = True
|
|
299
356
|
half_precision: bool = False
|
|
357
|
+
start_time: datetime | None = None
|
|
300
358
|
detect_start: int | None = None
|
|
301
359
|
detect_end: int | None = None
|
|
302
360
|
|
|
@@ -314,6 +372,7 @@ class DetectConfig:
|
|
|
314
372
|
if expected_duration is not None:
|
|
315
373
|
expected_duration = timedelta(seconds=int(expected_duration))
|
|
316
374
|
|
|
375
|
+
start_time = DetectConfig._parse_start_time(d)
|
|
317
376
|
return DetectConfig(
|
|
318
377
|
files,
|
|
319
378
|
d.get(RUN_CHAINED, DetectConfig.run_chained),
|
|
@@ -321,10 +380,17 @@ class DetectConfig:
|
|
|
321
380
|
expected_duration,
|
|
322
381
|
d.get(OVERWRITE, DetectConfig.overwrite),
|
|
323
382
|
d.get(HALF_PRECISION, DetectConfig.half_precision),
|
|
383
|
+
start_time,
|
|
324
384
|
d.get(DETECT_START, DetectConfig.detect_start),
|
|
325
385
|
d.get(DETECT_END, DetectConfig.detect_end),
|
|
326
386
|
)
|
|
327
387
|
|
|
388
|
+
@staticmethod
|
|
389
|
+
def _parse_start_time(d: dict) -> datetime | None:
|
|
390
|
+
if start_time := d.get(START_TIME, DetectConfig.start_time):
|
|
391
|
+
return datetime.strptime(start_time, DATETIME_FORMAT)
|
|
392
|
+
return start_time
|
|
393
|
+
|
|
328
394
|
def to_dict(self) -> dict:
|
|
329
395
|
expected_duration = (
|
|
330
396
|
int(self.expected_duration.total_seconds())
|
|
@@ -338,6 +404,9 @@ class DetectConfig:
|
|
|
338
404
|
EXPECTED_DURATION: expected_duration,
|
|
339
405
|
OVERWRITE: self.overwrite,
|
|
340
406
|
HALF_PRECISION: self.half_precision,
|
|
407
|
+
START_TIME: self.start_time,
|
|
408
|
+
DETECT_START: self.detect_start,
|
|
409
|
+
DETECT_END: self.detect_end,
|
|
341
410
|
}
|
|
342
411
|
|
|
343
412
|
|