OTVision 0.6.17__py3-none-any.whl → 0.7.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.
- OTVision/abstraction/observer.py +90 -1
- OTVision/abstraction/pipes_and_filter.py +4 -4
- OTVision/application/buffer.py +6 -6
- OTVision/application/detect/current_object_detector.py +4 -3
- OTVision/application/video/generate_video.py +2 -2
- OTVision/detect/builder.py +3 -3
- OTVision/detect/detect.py +4 -4
- OTVision/detect/detected_frame_buffer.py +9 -9
- OTVision/detect/detected_frame_producer.py +2 -2
- OTVision/detect/detected_frame_producer_factory.py +4 -4
- OTVision/detect/file_based_detect_builder.py +2 -2
- OTVision/detect/otdet_file_writer.py +7 -7
- OTVision/detect/rtsp_based_detect_builder.py +2 -2
- OTVision/detect/rtsp_input_source.py +12 -9
- OTVision/detect/video_input_source.py +18 -11
- OTVision/detect/yolo.py +9 -6
- OTVision/domain/detect_producer_consumer.py +5 -5
- OTVision/domain/input_source_detect.py +3 -3
- OTVision/domain/object_detection.py +5 -5
- OTVision/domain/video_writer.py +1 -1
- OTVision/plugin/ffmpeg_video_writer.py +4 -4
- OTVision/track/model/track_exporter.py +4 -4
- OTVision/track/model/tracking_interfaces.py +18 -15
- OTVision/track/stream_ottrk_file_writer.py +16 -14
- OTVision/track/track.py +4 -4
- OTVision/track/tracker/filebased_tracking.py +23 -16
- OTVision/version.py +1 -1
- {otvision-0.6.17.dist-info → otvision-0.7.1.dist-info}/METADATA +1 -1
- {otvision-0.6.17.dist-info → otvision-0.7.1.dist-info}/RECORD +31 -31
- {otvision-0.6.17.dist-info → otvision-0.7.1.dist-info}/WHEEL +1 -1
- {otvision-0.6.17.dist-info → otvision-0.7.1.dist-info}/licenses/LICENSE +0 -0
OTVision/abstraction/observer.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Awaitable, Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
from OTVision.helpers.log import LOGGER_NAME
|
|
2
6
|
|
|
3
7
|
VALUE = TypeVar("VALUE")
|
|
4
8
|
type Observer[T] = Callable[[T], None]
|
|
9
|
+
type AsyncObserver[T] = Callable[[T], Awaitable[None]]
|
|
5
10
|
|
|
6
11
|
|
|
7
12
|
class Subject[T]:
|
|
@@ -41,3 +46,87 @@ class Observable[T]:
|
|
|
41
46
|
|
|
42
47
|
def register(self, observer: Observer[T]) -> None:
|
|
43
48
|
self._subject.register(observer)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AsyncSubject[T]:
|
|
52
|
+
"""Generic async subject class to handle and notify async observers.
|
|
53
|
+
|
|
54
|
+
This class ensures that no duplicate observers can be registered.
|
|
55
|
+
The order that registered observers are notified is dictated by the order
|
|
56
|
+
they have been registered. Meaning, first to be registered is first to be
|
|
57
|
+
notified. Observers are executed as fire-and-forget background tasks for
|
|
58
|
+
non-blocking notifications.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self) -> None:
|
|
62
|
+
self._observers: list[AsyncObserver[T]] = []
|
|
63
|
+
self._pending_tasks: set[asyncio.Task[None]] = set()
|
|
64
|
+
|
|
65
|
+
def register(self, observer: AsyncObserver[T]) -> None:
|
|
66
|
+
"""Listen to changes of subject.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
observer (AsyncObserver[T]): the observer to be registered. This must be an
|
|
70
|
+
async `Callable` that returns an `Awaitable`.
|
|
71
|
+
"""
|
|
72
|
+
new_observers = self._observers.copy()
|
|
73
|
+
new_observers.append(observer)
|
|
74
|
+
self._observers = list(dict.fromkeys(new_observers))
|
|
75
|
+
|
|
76
|
+
async def notify(self, value: T) -> None:
|
|
77
|
+
"""Notifies observers about the value asynchronously.
|
|
78
|
+
|
|
79
|
+
All observers are notified as fire-and-forget background tasks,
|
|
80
|
+
allowing the event loop to continue processing while observers execute.
|
|
81
|
+
Each observer runs independently; exceptions are caught and logged
|
|
82
|
+
without affecting other observers or blocking the caller.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
value (T): value to notify the observer with.
|
|
86
|
+
"""
|
|
87
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
88
|
+
|
|
89
|
+
async def _safe_observer_call(observer: AsyncObserver[T], value: T) -> None:
|
|
90
|
+
"""Wrapper to safely call an observer and log any exceptions."""
|
|
91
|
+
try:
|
|
92
|
+
await observer(value)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
observer_name = (
|
|
95
|
+
observer.__name__
|
|
96
|
+
if hasattr(observer, "__name__")
|
|
97
|
+
else repr(observer)
|
|
98
|
+
)
|
|
99
|
+
log.error(
|
|
100
|
+
f"Exception in async observer {observer_name}: {e}",
|
|
101
|
+
exc_info=True,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
for observer in self._observers:
|
|
105
|
+
task = asyncio.create_task(_safe_observer_call(observer, value))
|
|
106
|
+
self._pending_tasks.add(task)
|
|
107
|
+
task.add_done_callback(self._pending_tasks.discard)
|
|
108
|
+
|
|
109
|
+
async def wait_for_all_observers(self) -> None:
|
|
110
|
+
"""Wait for all pending observer tasks to complete.
|
|
111
|
+
|
|
112
|
+
This method can be used when you need to ensure all observers have
|
|
113
|
+
finished processing before proceeding. Useful in tests or when
|
|
114
|
+
synchronization is required.
|
|
115
|
+
"""
|
|
116
|
+
if self._pending_tasks:
|
|
117
|
+
await asyncio.gather(*self._pending_tasks, return_exceptions=True)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AsyncObservable[T]:
|
|
121
|
+
def __init__(self, subject: AsyncSubject[T]) -> None:
|
|
122
|
+
self._subject = subject
|
|
123
|
+
|
|
124
|
+
def register(self, observer: AsyncObserver[T]) -> None:
|
|
125
|
+
self._subject.register(observer)
|
|
126
|
+
|
|
127
|
+
async def wait_for_all_observers(self) -> None:
|
|
128
|
+
"""Wait for all pending observer tasks to complete.
|
|
129
|
+
|
|
130
|
+
This method delegates to the subject's wait_for_all_observers method.
|
|
131
|
+
"""
|
|
132
|
+
await self._subject.wait_for_all_observers()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import AsyncIterator
|
|
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:
|
|
17
|
+
def filter(self, pipe: AsyncIterator[IN]) -> AsyncIterator[OUT]:
|
|
18
18
|
"""Process elements from the input pipe and produce output elements.
|
|
19
19
|
|
|
20
20
|
Args:
|
|
21
|
-
pipe (
|
|
21
|
+
pipe (AsyncIterator[IN]): Input stream of elements to be processed.
|
|
22
22
|
|
|
23
23
|
Returns:
|
|
24
|
-
|
|
24
|
+
AsyncIterator[OUT]: Output stream of processed elements.
|
|
25
25
|
|
|
26
26
|
"""
|
|
27
27
|
|
OTVision/application/buffer.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import AsyncIterator
|
|
3
3
|
|
|
4
4
|
from OTVision.abstraction.pipes_and_filter import Filter
|
|
5
5
|
|
|
@@ -8,12 +8,12 @@ class Buffer[T, OBSERVING_TYPE](Filter[T, T]):
|
|
|
8
8
|
def __init__(self) -> None:
|
|
9
9
|
self._buffer: list[T] = []
|
|
10
10
|
|
|
11
|
-
def filter(self, pipe:
|
|
12
|
-
for element in pipe:
|
|
13
|
-
self.buffer(element)
|
|
11
|
+
async def filter(self, pipe: AsyncIterator[T]) -> AsyncIterator[T]:
|
|
12
|
+
async for element in pipe:
|
|
13
|
+
await self.buffer(element)
|
|
14
14
|
yield element
|
|
15
15
|
|
|
16
|
-
def buffer(self, to_buffer: T) -> None:
|
|
16
|
+
async def buffer(self, to_buffer: T) -> None:
|
|
17
17
|
self._buffer.append(to_buffer)
|
|
18
18
|
|
|
19
19
|
def _get_buffered_elements(self) -> list[T]:
|
|
@@ -24,5 +24,5 @@ class Buffer[T, OBSERVING_TYPE](Filter[T, T]):
|
|
|
24
24
|
self._buffer = list()
|
|
25
25
|
|
|
26
26
|
@abstractmethod
|
|
27
|
-
def on_flush(self, event: OBSERVING_TYPE) -> None:
|
|
27
|
+
async def on_flush(self, event: OBSERVING_TYPE) -> None:
|
|
28
28
|
raise NotImplementedError
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import AsyncIterator
|
|
2
2
|
|
|
3
3
|
from OTVision.abstraction.pipes_and_filter import Filter
|
|
4
4
|
from OTVision.application.get_current_config import GetCurrentConfig
|
|
@@ -33,5 +33,6 @@ 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(self, pipe:
|
|
37
|
-
|
|
36
|
+
async def filter(self, pipe: AsyncIterator[Frame]) -> AsyncIterator[DetectedFrame]:
|
|
37
|
+
async for detected_frame in self.get().detect(pipe):
|
|
38
|
+
yield detected_frame
|
|
@@ -10,6 +10,6 @@ class GenerateVideo:
|
|
|
10
10
|
self._input_source = input_source
|
|
11
11
|
self._video_writer = video_writer
|
|
12
12
|
|
|
13
|
-
def generate(self) -> None:
|
|
14
|
-
for frame in self._video_writer.filter(self._input_source.produce()):
|
|
13
|
+
async def generate(self) -> None:
|
|
14
|
+
async for frame in self._video_writer.filter(self._input_source.produce()):
|
|
15
15
|
pass
|
OTVision/detect/builder.py
CHANGED
|
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
|
|
|
2
2
|
from argparse import ArgumentParser
|
|
3
3
|
from functools import cached_property
|
|
4
4
|
|
|
5
|
-
from OTVision.abstraction.observer import
|
|
5
|
+
from OTVision.abstraction.observer import AsyncSubject
|
|
6
6
|
from OTVision.application.config import Config, DetectConfig
|
|
7
7
|
from OTVision.application.config_parser import ConfigParser
|
|
8
8
|
from OTVision.application.configure_logger import ConfigureLogger
|
|
@@ -127,7 +127,7 @@ class DetectBuilder(ABC):
|
|
|
127
127
|
@cached_property
|
|
128
128
|
def otdet_file_writer(self) -> OtdetFileWriter:
|
|
129
129
|
return OtdetFileWriter(
|
|
130
|
-
subject=
|
|
130
|
+
subject=AsyncSubject[OtdetFileWrittenEvent](),
|
|
131
131
|
builder=self.otdet_builder,
|
|
132
132
|
get_current_config=self.get_current_config,
|
|
133
133
|
current_object_detector_metadata=self.current_object_detector_metadata,
|
|
@@ -147,7 +147,7 @@ class DetectBuilder(ABC):
|
|
|
147
147
|
|
|
148
148
|
@cached_property
|
|
149
149
|
def detected_frame_buffer(self) -> DetectedFrameBuffer:
|
|
150
|
-
return DetectedFrameBuffer(subject=
|
|
150
|
+
return DetectedFrameBuffer(subject=AsyncSubject[DetectedFrameBufferEvent]())
|
|
151
151
|
|
|
152
152
|
@cached_property
|
|
153
153
|
def detected_frame_producer(self) -> DetectedFrameProducer:
|
OTVision/detect/detect.py
CHANGED
|
@@ -30,10 +30,10 @@ class OTVisionVideoDetect(DetectedFrameConsumer):
|
|
|
30
30
|
def __init__(self, producer: DetectedFrameProducer) -> None:
|
|
31
31
|
self._producer = producer
|
|
32
32
|
|
|
33
|
-
def start(self) -> None:
|
|
33
|
+
async def start(self) -> None:
|
|
34
34
|
"""Starts the detection of objects in multiple videos and/or images."""
|
|
35
|
-
self.consume()
|
|
35
|
+
await self.consume()
|
|
36
36
|
|
|
37
|
-
def consume(self) -> None:
|
|
38
|
-
for _ in self._producer.produce():
|
|
37
|
+
async def consume(self) -> None:
|
|
38
|
+
async for _ in self._producer.produce():
|
|
39
39
|
pass
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import datetime, timedelta
|
|
3
3
|
|
|
4
|
-
from OTVision.abstraction.observer import
|
|
4
|
+
from OTVision.abstraction.observer import AsyncObservable, AsyncSubject
|
|
5
5
|
from OTVision.application.buffer import Buffer
|
|
6
6
|
from OTVision.domain.frame import DetectedFrame
|
|
7
7
|
|
|
@@ -51,25 +51,25 @@ class DetectedFrameBufferEvent:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class DetectedFrameBuffer(
|
|
54
|
-
Buffer[DetectedFrame, FlushEvent],
|
|
54
|
+
Buffer[DetectedFrame, FlushEvent], AsyncObservable[DetectedFrameBufferEvent]
|
|
55
55
|
):
|
|
56
|
-
def __init__(self, subject:
|
|
56
|
+
def __init__(self, subject: AsyncSubject[DetectedFrameBufferEvent]) -> None:
|
|
57
57
|
Buffer.__init__(self)
|
|
58
|
-
|
|
58
|
+
AsyncObservable.__init__(self, subject)
|
|
59
59
|
|
|
60
|
-
def on_flush(self, event: FlushEvent) -> None:
|
|
60
|
+
async def on_flush(self, event: FlushEvent) -> None:
|
|
61
61
|
buffered_elements = self._get_buffered_elements()
|
|
62
|
-
self._notify_observers(buffered_elements, event)
|
|
62
|
+
await self._notify_observers(buffered_elements, event)
|
|
63
63
|
self._reset_buffer()
|
|
64
64
|
|
|
65
|
-
def _notify_observers(
|
|
65
|
+
async def _notify_observers(
|
|
66
66
|
self, elements: list[DetectedFrame], event: FlushEvent
|
|
67
67
|
) -> None:
|
|
68
|
-
self._subject.notify(
|
|
68
|
+
await self._subject.notify(
|
|
69
69
|
DetectedFrameBufferEvent(
|
|
70
70
|
source_metadata=event.source_metadata, frames=elements
|
|
71
71
|
)
|
|
72
72
|
)
|
|
73
73
|
|
|
74
|
-
def buffer(self, to_buffer: DetectedFrame) -> None:
|
|
74
|
+
async def buffer(self, to_buffer: DetectedFrame) -> None:
|
|
75
75
|
self._buffer.append(to_buffer.without_image())
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import AsyncIterator
|
|
2
2
|
|
|
3
3
|
from OTVision.detect.detected_frame_producer_factory import DetectedFrameProducerFactory
|
|
4
4
|
from OTVision.domain.detect_producer_consumer import DetectedFrameProducer
|
|
@@ -10,5 +10,5 @@ class SimpleDetectedFrameProducer(DetectedFrameProducer):
|
|
|
10
10
|
def __init__(self, producer_factory: DetectedFrameProducerFactory) -> None:
|
|
11
11
|
self._producer_factory = producer_factory
|
|
12
12
|
|
|
13
|
-
def produce(self) ->
|
|
13
|
+
def produce(self) -> AsyncIterator[DetectedFrame]:
|
|
14
14
|
return self._producer_factory.create()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import AsyncIterator
|
|
2
2
|
|
|
3
3
|
from OTVision.abstraction.pipes_and_filter import Filter
|
|
4
4
|
from OTVision.application.get_current_config import GetCurrentConfig
|
|
@@ -21,17 +21,17 @@ class DetectedFrameProducerFactory:
|
|
|
21
21
|
self._detected_frame_buffer = detected_frame_buffer
|
|
22
22
|
self._get_current_config = get_current_config
|
|
23
23
|
|
|
24
|
-
def create(self) ->
|
|
24
|
+
def create(self) -> AsyncIterator[DetectedFrame]:
|
|
25
25
|
if self._get_current_config.get().detect.write_video:
|
|
26
26
|
return self.__create_with_video_writer()
|
|
27
27
|
return self.__create_without_video_writer()
|
|
28
28
|
|
|
29
|
-
def __create_without_video_writer(self) ->
|
|
29
|
+
def __create_without_video_writer(self) -> AsyncIterator[DetectedFrame]:
|
|
30
30
|
return self._detected_frame_buffer.filter(
|
|
31
31
|
self._detection_filter.filter(self._input_source.produce())
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
def __create_with_video_writer(self) ->
|
|
34
|
+
def __create_with_video_writer(self) -> AsyncIterator[DetectedFrame]:
|
|
35
35
|
return self._detected_frame_buffer.filter(
|
|
36
36
|
self._detection_filter.filter(
|
|
37
37
|
self._video_writer_filter.filter(self._input_source.produce())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from functools import cached_property
|
|
2
2
|
|
|
3
|
-
from OTVision.abstraction.observer import Subject
|
|
3
|
+
from OTVision.abstraction.observer import AsyncSubject, Subject
|
|
4
4
|
from OTVision.application.event.new_video_start import NewVideoStartEvent
|
|
5
5
|
from OTVision.detect.builder import DetectBuilder
|
|
6
6
|
from OTVision.detect.detected_frame_buffer import FlushEvent
|
|
@@ -19,7 +19,7 @@ class FileBasedDetectBuilder(DetectBuilder):
|
|
|
19
19
|
@cached_property
|
|
20
20
|
def input_source(self) -> VideoSource:
|
|
21
21
|
return VideoSource(
|
|
22
|
-
subject_flush=
|
|
22
|
+
subject_flush=AsyncSubject[FlushEvent](),
|
|
23
23
|
subject_new_video_start=Subject[NewVideoStartEvent](),
|
|
24
24
|
get_current_config=self.get_current_config,
|
|
25
25
|
frame_rotator=self.frame_rotator,
|
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from OTVision.abstraction.observer import
|
|
5
|
+
from OTVision.abstraction.observer import AsyncObserver, AsyncSubject
|
|
6
6
|
from OTVision.application.detect.current_object_detector_metadata import (
|
|
7
7
|
CurrentObjectDetectorMetadata,
|
|
8
8
|
)
|
|
@@ -45,7 +45,7 @@ class OtdetFileWriter:
|
|
|
45
45
|
|
|
46
46
|
def __init__(
|
|
47
47
|
self,
|
|
48
|
-
subject:
|
|
48
|
+
subject: AsyncSubject[OtdetFileWrittenEvent],
|
|
49
49
|
builder: OtdetBuilder,
|
|
50
50
|
get_current_config: GetCurrentConfig,
|
|
51
51
|
current_object_detector_metadata: CurrentObjectDetectorMetadata,
|
|
@@ -57,7 +57,7 @@ class OtdetFileWriter:
|
|
|
57
57
|
self._current_object_detector_metadata = current_object_detector_metadata
|
|
58
58
|
self._save_path_provider = save_path_provider
|
|
59
59
|
|
|
60
|
-
def write(self, event: DetectedFrameBufferEvent) -> None:
|
|
60
|
+
async def write(self, event: DetectedFrameBufferEvent) -> None:
|
|
61
61
|
"""Writes detection results to a file in OTDET format.
|
|
62
62
|
|
|
63
63
|
Processes the detected frames and associated metadata, builds the OTDET
|
|
@@ -118,16 +118,16 @@ class OtdetFileWriter:
|
|
|
118
118
|
|
|
119
119
|
finished_msg = "Finished detection"
|
|
120
120
|
log.info(finished_msg)
|
|
121
|
-
self.__notify(
|
|
121
|
+
await self.__notify(
|
|
122
122
|
num_frames=actual_frames,
|
|
123
123
|
builder_config=builder_config,
|
|
124
124
|
save_location=detections_file,
|
|
125
125
|
)
|
|
126
126
|
|
|
127
|
-
def __notify(
|
|
127
|
+
async def __notify(
|
|
128
128
|
self, num_frames: int, builder_config: OtdetBuilderConfig, save_location: Path
|
|
129
129
|
) -> None:
|
|
130
|
-
self._subject.notify(
|
|
130
|
+
await self._subject.notify(
|
|
131
131
|
OtdetFileWrittenEvent(
|
|
132
132
|
number_of_frames=num_frames,
|
|
133
133
|
otdet_builder_config=builder_config,
|
|
@@ -135,6 +135,6 @@ class OtdetFileWriter:
|
|
|
135
135
|
)
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
-
def register_observer(self, observer:
|
|
138
|
+
def register_observer(self, observer: AsyncObserver[OtdetFileWrittenEvent]) -> None:
|
|
139
139
|
"""Register an observer to receive notifications about otdet file writes.."""
|
|
140
140
|
self._subject.register(observer)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from functools import cached_property
|
|
2
2
|
|
|
3
|
-
from OTVision.abstraction.observer import Subject
|
|
3
|
+
from OTVision.abstraction.observer import AsyncSubject, Subject
|
|
4
4
|
from OTVision.application.config import StreamConfig
|
|
5
5
|
from OTVision.application.event.new_video_start import NewVideoStartEvent
|
|
6
6
|
from OTVision.detect.builder import DetectBuilder
|
|
@@ -33,7 +33,7 @@ class RtspBasedDetectBuilder(DetectBuilder):
|
|
|
33
33
|
@cached_property
|
|
34
34
|
def input_source(self) -> RtspInputSource:
|
|
35
35
|
return RtspInputSource(
|
|
36
|
-
subject_flush=
|
|
36
|
+
subject_flush=AsyncSubject[FlushEvent](),
|
|
37
37
|
subject_new_video_start=Subject[NewVideoStartEvent](),
|
|
38
38
|
datetime_provider=self.datetime_provider,
|
|
39
39
|
frame_counter=Counter(),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import socket
|
|
2
3
|
from datetime import datetime, timedelta
|
|
3
4
|
from time import sleep
|
|
4
|
-
from typing import
|
|
5
|
+
from typing import AsyncIterator
|
|
5
6
|
from urllib.parse import urlparse
|
|
6
7
|
|
|
7
8
|
from cv2 import (
|
|
@@ -13,7 +14,7 @@ from cv2 import (
|
|
|
13
14
|
)
|
|
14
15
|
from numpy import ndarray
|
|
15
16
|
|
|
16
|
-
from OTVision.abstraction.observer import Subject
|
|
17
|
+
from OTVision.abstraction.observer import AsyncSubject, Subject
|
|
17
18
|
from OTVision.application.config import (
|
|
18
19
|
DATETIME_FORMAT,
|
|
19
20
|
Config,
|
|
@@ -87,7 +88,7 @@ class RtspInputSource(InputSourceDetect):
|
|
|
87
88
|
|
|
88
89
|
def __init__(
|
|
89
90
|
self,
|
|
90
|
-
subject_flush:
|
|
91
|
+
subject_flush: AsyncSubject[FlushEvent],
|
|
91
92
|
subject_new_video_start: Subject[NewVideoStartEvent],
|
|
92
93
|
datetime_provider: DatetimeProvider,
|
|
93
94
|
frame_counter: Counter,
|
|
@@ -131,7 +132,7 @@ class RtspInputSource(InputSourceDetect):
|
|
|
131
132
|
self._current_video_capture = self._init_video_capture(self._current_stream)
|
|
132
133
|
return self._current_video_capture
|
|
133
134
|
|
|
134
|
-
def produce(self) ->
|
|
135
|
+
async def produce(self) -> AsyncIterator[Frame]:
|
|
135
136
|
self._stream_start_time = self._datetime_provider.provide()
|
|
136
137
|
self._current_video_start_time = self._stream_start_time
|
|
137
138
|
try:
|
|
@@ -153,10 +154,10 @@ class RtspInputSource(InputSourceDetect):
|
|
|
153
154
|
occurrence=occurrence,
|
|
154
155
|
)
|
|
155
156
|
if self.flush_condition_met():
|
|
156
|
-
self._notify_flush_observers()
|
|
157
|
+
await self._notify_flush_observers()
|
|
157
158
|
self._outdated = True
|
|
158
159
|
self._frame_counter.reset()
|
|
159
|
-
self._notify_flush_observers()
|
|
160
|
+
await self._notify_flush_observers()
|
|
160
161
|
except InvalidRtspUrlError as cause:
|
|
161
162
|
logger().error(cause)
|
|
162
163
|
|
|
@@ -209,7 +210,7 @@ class RtspInputSource(InputSourceDetect):
|
|
|
209
210
|
def flush_condition_met(self) -> bool:
|
|
210
211
|
return self.current_frame_number % self.flush_buffer_size == 0
|
|
211
212
|
|
|
212
|
-
def _notify_flush_observers(self) -> None:
|
|
213
|
+
async def _notify_flush_observers(self) -> None:
|
|
213
214
|
frame_width = self._get_width()
|
|
214
215
|
frame_height = self._get_height()
|
|
215
216
|
frames = (
|
|
@@ -219,7 +220,7 @@ class RtspInputSource(InputSourceDetect):
|
|
|
219
220
|
)
|
|
220
221
|
duration = timedelta(seconds=round(frames / self.fps))
|
|
221
222
|
output = self.create_output()
|
|
222
|
-
self.subject_flush.notify(
|
|
223
|
+
await self.subject_flush.notify(
|
|
223
224
|
FlushEvent.create(
|
|
224
225
|
source=self.rtsp_url,
|
|
225
226
|
output=output,
|
|
@@ -256,7 +257,9 @@ class RtspInputSource(InputSourceDetect):
|
|
|
256
257
|
def notify_new_config(self, config: NewOtvisionConfigEvent) -> None:
|
|
257
258
|
try:
|
|
258
259
|
logger().debug("New OTVision config detected. Flushing buffers...")
|
|
259
|
-
|
|
260
|
+
|
|
261
|
+
# Create task to handle async flush notification
|
|
262
|
+
asyncio.create_task(self._notify_flush_observers())
|
|
260
263
|
except NoConfigurationFoundError:
|
|
261
264
|
logger().info("No configuration found for RTSP stream. Skipping flushing.")
|
|
262
265
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import AsyncIterator, Iterable
|
|
5
5
|
|
|
6
6
|
import av
|
|
7
7
|
from av.container.input import InputContainer
|
|
8
|
-
from tqdm import tqdm
|
|
8
|
+
from tqdm.asyncio import tqdm
|
|
9
9
|
|
|
10
|
-
from OTVision.abstraction.observer import Subject
|
|
10
|
+
from OTVision.abstraction.observer import AsyncSubject, Subject
|
|
11
11
|
from OTVision.application.config import DATETIME_FORMAT, Config
|
|
12
12
|
from OTVision.application.detect.timestamper import Timestamper
|
|
13
13
|
from OTVision.application.event.new_video_start import NewVideoStartEvent
|
|
@@ -59,7 +59,7 @@ class VideoSource(InputSourceDetect):
|
|
|
59
59
|
|
|
60
60
|
def __init__(
|
|
61
61
|
self,
|
|
62
|
-
subject_flush:
|
|
62
|
+
subject_flush: AsyncSubject[FlushEvent],
|
|
63
63
|
subject_new_video_start: Subject[NewVideoStartEvent],
|
|
64
64
|
get_current_config: GetCurrentConfig,
|
|
65
65
|
frame_rotator: AvVideoFrameRotator,
|
|
@@ -74,7 +74,7 @@ class VideoSource(InputSourceDetect):
|
|
|
74
74
|
self._save_path_provider = save_path_provider
|
|
75
75
|
self.__should_flush = False
|
|
76
76
|
|
|
77
|
-
def produce(self) ->
|
|
77
|
+
async def produce(self) -> AsyncIterator[Frame]:
|
|
78
78
|
"""Generate frames from video files that meet detection requirements.
|
|
79
79
|
|
|
80
80
|
Yields frames from valid video files while managing rotation, timestamping,
|
|
@@ -84,11 +84,13 @@ class VideoSource(InputSourceDetect):
|
|
|
84
84
|
Frame: Processed video frames ready for detection.
|
|
85
85
|
"""
|
|
86
86
|
|
|
87
|
-
video_files = self._collect_files_to_detect()
|
|
87
|
+
video_files = await self._collect_files_to_detect()
|
|
88
88
|
|
|
89
89
|
log.info("Start detection of video files")
|
|
90
90
|
|
|
91
|
-
for video_file in tqdm(
|
|
91
|
+
async for video_file in tqdm(
|
|
92
|
+
video_files, desc="Detected video files", unit=" files"
|
|
93
|
+
):
|
|
92
94
|
detections_file = self._save_path_provider.provide(
|
|
93
95
|
str(video_file), self._current_config.filetypes.detect
|
|
94
96
|
)
|
|
@@ -135,11 +137,15 @@ class VideoSource(InputSourceDetect):
|
|
|
135
137
|
}
|
|
136
138
|
)
|
|
137
139
|
counter += 1
|
|
138
|
-
self.notify_flush_event_observers(video_file, video_fps)
|
|
140
|
+
await self.notify_flush_event_observers(video_file, video_fps)
|
|
139
141
|
self._on_video_finished(video_file)
|
|
140
142
|
except Exception as e:
|
|
141
143
|
log.error(f"Error processing {video_file}", exc_info=e)
|
|
142
144
|
|
|
145
|
+
# Wait for all flush event observers to complete their work
|
|
146
|
+
# (e.g., file writing) before this method returns
|
|
147
|
+
await self.subject_flush.wait_for_all_observers()
|
|
148
|
+
|
|
143
149
|
def _on_video_finished(self, video_file: Path) -> None:
|
|
144
150
|
"""Hook for handling video processing completion."""
|
|
145
151
|
pass
|
|
@@ -154,7 +160,7 @@ class VideoSource(InputSourceDetect):
|
|
|
154
160
|
)
|
|
155
161
|
return {}
|
|
156
162
|
|
|
157
|
-
def _collect_files_to_detect(self) -> Iterable[Path]:
|
|
163
|
+
async def _collect_files_to_detect(self) -> Iterable[Path]:
|
|
158
164
|
filetypes = self._current_config.filetypes.video_filetypes.to_list()
|
|
159
165
|
video_files = get_files(
|
|
160
166
|
paths=self._current_config.detect.paths, filetypes=filetypes
|
|
@@ -190,7 +196,7 @@ class VideoSource(InputSourceDetect):
|
|
|
190
196
|
return False
|
|
191
197
|
return True
|
|
192
198
|
|
|
193
|
-
def notify_flush_event_observers(
|
|
199
|
+
async def notify_flush_event_observers(
|
|
194
200
|
self, current_video_file: Path, video_fps: float
|
|
195
201
|
) -> None:
|
|
196
202
|
if expected_duration := self._current_config.detect.expected_duration:
|
|
@@ -203,7 +209,7 @@ class VideoSource(InputSourceDetect):
|
|
|
203
209
|
current_video_file, start_time=self._start_time
|
|
204
210
|
)
|
|
205
211
|
|
|
206
|
-
self.subject_flush.notify(
|
|
212
|
+
await self.subject_flush.notify(
|
|
207
213
|
FlushEvent.create(
|
|
208
214
|
source=str(current_video_file),
|
|
209
215
|
output=str(current_video_file),
|
|
@@ -214,6 +220,7 @@ class VideoSource(InputSourceDetect):
|
|
|
214
220
|
start_time=start_time,
|
|
215
221
|
)
|
|
216
222
|
)
|
|
223
|
+
await self.subject_flush.wait_for_all_observers()
|
|
217
224
|
|
|
218
225
|
def notify_new_video_start_observers(
|
|
219
226
|
self, current_video_file: Path, video_fps: float
|
OTVision/detect/yolo.py
CHANGED
|
@@ -22,10 +22,10 @@ OTVision module to detect objects using yolov5
|
|
|
22
22
|
import logging
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from time import perf_counter
|
|
25
|
-
from typing import
|
|
25
|
+
from typing import AsyncIterator
|
|
26
26
|
|
|
27
27
|
import torch
|
|
28
|
-
from tqdm import tqdm
|
|
28
|
+
from tqdm.asyncio import tqdm
|
|
29
29
|
from ultralytics import YOLO
|
|
30
30
|
from ultralytics.engine.results import Boxes
|
|
31
31
|
|
|
@@ -134,11 +134,14 @@ class YoloDetector(ObjectDetector, Filter[Frame, DetectedFrame]):
|
|
|
134
134
|
self._detection_converter = detection_converter
|
|
135
135
|
self._detected_frame_factory = detected_frame_factory
|
|
136
136
|
|
|
137
|
-
def filter(self, pipe:
|
|
138
|
-
|
|
137
|
+
async def filter(self, pipe: AsyncIterator[Frame]) -> AsyncIterator[DetectedFrame]:
|
|
138
|
+
async for detected_frame in self.detect(pipe):
|
|
139
|
+
yield detected_frame
|
|
139
140
|
|
|
140
|
-
def detect(
|
|
141
|
-
|
|
141
|
+
async def detect(
|
|
142
|
+
self, frames: AsyncIterator[Frame]
|
|
143
|
+
) -> AsyncIterator[DetectedFrame]:
|
|
144
|
+
async for frame in tqdm(
|
|
142
145
|
frames,
|
|
143
146
|
desc="Detected frames",
|
|
144
147
|
unit=" frames",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import AsyncIterator
|
|
3
3
|
|
|
4
4
|
from OTVision.domain.frame import DetectedFrame
|
|
5
5
|
|
|
@@ -8,7 +8,7 @@ class DetectedFrameConsumer(ABC):
|
|
|
8
8
|
"""Interface for components that consume detected frames."""
|
|
9
9
|
|
|
10
10
|
@abstractmethod
|
|
11
|
-
def consume(self) -> None:
|
|
11
|
+
async def consume(self) -> None:
|
|
12
12
|
"""Consume detected frames."""
|
|
13
13
|
raise NotImplementedError
|
|
14
14
|
|
|
@@ -21,10 +21,10 @@ class DetectedFrameProducer(ABC):
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
@abstractmethod
|
|
24
|
-
def produce(self) ->
|
|
24
|
+
def produce(self) -> AsyncIterator[DetectedFrame]:
|
|
25
25
|
"""Generate a stream of detected frames.
|
|
26
26
|
|
|
27
27
|
Returns:
|
|
28
|
-
|
|
28
|
+
AsyncIterator[DetectedFrame ]: A stream of detected frames.
|
|
29
29
|
"""
|
|
30
|
-
|
|
30
|
+
...
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import AsyncIterator
|
|
3
3
|
|
|
4
4
|
from OTVision.domain.frame import Frame
|
|
5
5
|
|
|
@@ -14,7 +14,7 @@ class InputSourceDetect(ABC):
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
@abstractmethod
|
|
17
|
-
def produce(self) ->
|
|
17
|
+
def produce(self) -> AsyncIterator[Frame]:
|
|
18
18
|
"""Generate a stream of frames from the input source.
|
|
19
19
|
|
|
20
20
|
Implementations should yield Frame objects one at a time from the source,
|
|
@@ -22,7 +22,7 @@ class InputSourceDetect(ABC):
|
|
|
22
22
|
at appropriate points (e.g., end of video segments or buffer boundaries).
|
|
23
23
|
|
|
24
24
|
Returns:
|
|
25
|
-
|
|
25
|
+
AsyncIterator[Frame]: A generator yielding Frame objects
|
|
26
26
|
sequentially from the input source.
|
|
27
27
|
"""
|
|
28
28
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import AsyncIterator
|
|
3
3
|
|
|
4
4
|
from OTVision.application.config import DetectConfig
|
|
5
5
|
from OTVision.domain.frame import DetectedFrame, Frame
|
|
@@ -25,17 +25,17 @@ class ObjectDetectorMetadata(ABC):
|
|
|
25
25
|
class ObjectDetector(ObjectDetectorMetadata):
|
|
26
26
|
|
|
27
27
|
@abstractmethod
|
|
28
|
-
def detect(self, frames:
|
|
28
|
+
def detect(self, frames: AsyncIterator[Frame]) -> AsyncIterator[DetectedFrame]:
|
|
29
29
|
"""Runs object detection on a video.
|
|
30
30
|
|
|
31
31
|
Args:
|
|
32
|
-
frames (
|
|
32
|
+
frames (AsyncIterator[Frame]): the source to read frames from.
|
|
33
33
|
|
|
34
34
|
Returns:
|
|
35
|
-
|
|
35
|
+
AsyncIterator[DetectedFrame]: nested list of detections.
|
|
36
36
|
First level is frames, second level is detections within frame.
|
|
37
37
|
"""
|
|
38
|
-
|
|
38
|
+
...
|
|
39
39
|
|
|
40
40
|
@abstractmethod
|
|
41
41
|
def preload(self) -> None:
|
OTVision/domain/video_writer.py
CHANGED
|
@@ -22,7 +22,7 @@ class VideoWriter(Filter[Frame, Frame], ABC):
|
|
|
22
22
|
raise NotImplementedError
|
|
23
23
|
|
|
24
24
|
@abstractmethod
|
|
25
|
-
def notify_on_flush_event(self, event: FlushEvent) -> None:
|
|
25
|
+
async def notify_on_flush_event(self, event: FlushEvent) -> None:
|
|
26
26
|
raise NotImplementedError
|
|
27
27
|
|
|
28
28
|
@abstractmethod
|
|
@@ -3,7 +3,7 @@ from enum import IntEnum, StrEnum
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from subprocess import PIPE, Popen, TimeoutExpired
|
|
5
5
|
from threading import Thread
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import AsyncIterator, Callable
|
|
7
7
|
|
|
8
8
|
import ffmpeg
|
|
9
9
|
from numpy import ndarray
|
|
@@ -242,7 +242,7 @@ class FfmpegVideoWriter(VideoWriter):
|
|
|
242
242
|
|
|
243
243
|
self.__current_video_metadata = None
|
|
244
244
|
|
|
245
|
-
def notify_on_flush_event(self, event: FlushEvent) -> None:
|
|
245
|
+
async def notify_on_flush_event(self, event: FlushEvent) -> None:
|
|
246
246
|
self.close()
|
|
247
247
|
|
|
248
248
|
def notify_on_new_video_start(self, event: NewVideoStartEvent) -> None:
|
|
@@ -282,8 +282,8 @@ class FfmpegVideoWriter(VideoWriter):
|
|
|
282
282
|
log.info(f"Writing new video file to '{save_file}'.")
|
|
283
283
|
return process
|
|
284
284
|
|
|
285
|
-
def filter(self, pipe:
|
|
286
|
-
for frame in pipe:
|
|
285
|
+
async def filter(self, pipe: AsyncIterator[Frame]) -> AsyncIterator[Frame]:
|
|
286
|
+
async for frame in pipe:
|
|
287
287
|
if (image := frame.get(FrameKeys.data)) is not None:
|
|
288
288
|
self.write(image)
|
|
289
289
|
yield frame
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import AsyncIterator, Generic, TypeVar
|
|
5
5
|
|
|
6
6
|
from tqdm import tqdm
|
|
7
7
|
|
|
@@ -48,10 +48,10 @@ class FinishedTracksExporter(ABC, Generic[F]):
|
|
|
48
48
|
def get_frame_group_id(self, container: F) -> int:
|
|
49
49
|
pass
|
|
50
50
|
|
|
51
|
-
def export(
|
|
52
|
-
self, tracking_run_id: str, stream:
|
|
51
|
+
async def export(
|
|
52
|
+
self, tracking_run_id: str, stream: AsyncIterator[F], overwrite: bool
|
|
53
53
|
) -> None:
|
|
54
|
-
for container in stream:
|
|
54
|
+
async for container in stream:
|
|
55
55
|
self.export_frames(container, tracking_run_id, overwrite)
|
|
56
56
|
|
|
57
57
|
def export_frames(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Generic, Iterator, TypeVar
|
|
2
|
+
from typing import AsyncIterator, Generic, Iterator, TypeVar
|
|
3
3
|
|
|
4
4
|
from OTVision.domain.detection import TrackId
|
|
5
5
|
from OTVision.domain.frame import (
|
|
@@ -22,22 +22,22 @@ class Tracker(ABC):
|
|
|
22
22
|
track_frame for processing a single frame.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
def track(
|
|
26
|
-
self, frames:
|
|
27
|
-
) ->
|
|
25
|
+
async def track(
|
|
26
|
+
self, frames: AsyncIterator[DetectedFrame], id_generator: IdGenerator
|
|
27
|
+
) -> AsyncIterator[TrackedFrame]:
|
|
28
28
|
"""Process the given stream of Frames,
|
|
29
29
|
yielding TrackedFrames one by one as a lazy stream of TrackedFrames.
|
|
30
30
|
|
|
31
31
|
Args:
|
|
32
|
-
frames (
|
|
32
|
+
frames (AsyncIterator[DetectedFrame]): (lazy) stream of Frames
|
|
33
33
|
with untracked Detections.
|
|
34
34
|
id_generator (IdGenerator): provider of new (unique) track ids.
|
|
35
35
|
|
|
36
36
|
Yields:
|
|
37
|
-
|
|
37
|
+
AsyncIterator[TrackedFrame]: (lazy) stream of TrackedFrames with
|
|
38
38
|
TrackedDetections
|
|
39
39
|
"""
|
|
40
|
-
for frame in frames:
|
|
40
|
+
async for frame in frames:
|
|
41
41
|
yield self.track_frame(frame, id_generator)
|
|
42
42
|
|
|
43
43
|
@abstractmethod
|
|
@@ -182,10 +182,10 @@ class UnfinishedTracksBuffer(ABC, Generic[C, F]):
|
|
|
182
182
|
"""
|
|
183
183
|
pass
|
|
184
184
|
|
|
185
|
-
def track_and_finish(self, containers:
|
|
185
|
+
async def track_and_finish(self, containers: AsyncIterator[C]) -> AsyncIterator[F]:
|
|
186
186
|
# TODO template method to obtain containers?
|
|
187
187
|
|
|
188
|
-
for container in containers:
|
|
188
|
+
async for container in containers:
|
|
189
189
|
|
|
190
190
|
# if track is observed in current iteration, update its last observed frame
|
|
191
191
|
new_last_track_frames = self._get_last_track_frames(container)
|
|
@@ -215,7 +215,8 @@ class UnfinishedTracksBuffer(ABC, Generic[C, F]):
|
|
|
215
215
|
]
|
|
216
216
|
|
|
217
217
|
finished_containers: list[F] = self._finish_containers(ready_containers)
|
|
218
|
-
|
|
218
|
+
for finished_container in finished_containers:
|
|
219
|
+
yield finished_container
|
|
219
220
|
|
|
220
221
|
# finish remaining containers with pending tracks
|
|
221
222
|
remaining_containers = [c for c, _ in self._unfinished_containers]
|
|
@@ -223,7 +224,8 @@ class UnfinishedTracksBuffer(ABC, Generic[C, F]):
|
|
|
223
224
|
|
|
224
225
|
finished_containers = self._finish_containers(remaining_containers)
|
|
225
226
|
self._merged_last_track_frame = dict()
|
|
226
|
-
|
|
227
|
+
for finished_container in finished_containers:
|
|
228
|
+
yield finished_container
|
|
227
229
|
|
|
228
230
|
def _finish_containers(self, containers: list[C]) -> list[F]:
|
|
229
231
|
if len(containers) == 0:
|
|
@@ -269,11 +271,12 @@ class UnfinishedFramesBuffer(UnfinishedTracksBuffer[TrackedFrame, FinishedFrame]
|
|
|
269
271
|
super().__init__(keep_discarded)
|
|
270
272
|
self._tracker = tracker
|
|
271
273
|
|
|
272
|
-
def track(
|
|
273
|
-
self, frames:
|
|
274
|
-
) ->
|
|
274
|
+
async def track(
|
|
275
|
+
self, frames: AsyncIterator[DetectedFrame], id_generator: IdGenerator
|
|
276
|
+
) -> AsyncIterator[FinishedFrame]:
|
|
275
277
|
tracked_frame_stream = self._tracker.track(frames, id_generator)
|
|
276
|
-
|
|
278
|
+
async for finished_frame in self.track_and_finish(tracked_frame_stream):
|
|
279
|
+
yield finished_frame
|
|
277
280
|
|
|
278
281
|
def _get_last_track_frames(self, container: TrackedFrame) -> dict[TrackId, int]:
|
|
279
282
|
return {o: container.no for o in container.observed_tracks}
|
|
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from OTVision.abstraction.observer import
|
|
5
|
+
from OTVision.abstraction.observer import AsyncObserver, AsyncSubject
|
|
6
6
|
from OTVision.application.buffer import Buffer
|
|
7
7
|
from OTVision.application.config import Config, TrackConfig
|
|
8
8
|
from OTVision.application.configure_logger import logger
|
|
@@ -45,7 +45,7 @@ class StreamOttrkFileWriter(Buffer[TrackedFrame, OtdetFileWrittenEvent]):
|
|
|
45
45
|
|
|
46
46
|
def __init__(
|
|
47
47
|
self,
|
|
48
|
-
subject:
|
|
48
|
+
subject: AsyncSubject[OttrkFileWrittenEvent],
|
|
49
49
|
builder: OttrkBuilder,
|
|
50
50
|
get_current_config: GetCurrentConfig,
|
|
51
51
|
get_current_tracking_run_id: GetCurrentTrackingRunId,
|
|
@@ -62,7 +62,7 @@ class StreamOttrkFileWriter(Buffer[TrackedFrame, OtdetFileWrittenEvent]):
|
|
|
62
62
|
self._ottrk_unfinished_tracks: set[TrackId] = set()
|
|
63
63
|
self._current_output_file: Path | None = None
|
|
64
64
|
|
|
65
|
-
def on_flush(self, event: OtdetFileWrittenEvent) -> None:
|
|
65
|
+
async def on_flush(self, event: OtdetFileWrittenEvent) -> None:
|
|
66
66
|
tracked_frames = self._get_buffered_elements()
|
|
67
67
|
if not tracked_frames:
|
|
68
68
|
return
|
|
@@ -100,7 +100,7 @@ class StreamOttrkFileWriter(Buffer[TrackedFrame, OtdetFileWrittenEvent]):
|
|
|
100
100
|
def reset(self) -> None:
|
|
101
101
|
self._reset_buffer()
|
|
102
102
|
|
|
103
|
-
def buffer(self, to_buffer: TrackedFrame) -> None:
|
|
103
|
+
async def buffer(self, to_buffer: TrackedFrame) -> None:
|
|
104
104
|
self._buffer.append(to_buffer.without_image())
|
|
105
105
|
|
|
106
106
|
if self._in_writing_state:
|
|
@@ -113,11 +113,11 @@ class StreamOttrkFileWriter(Buffer[TrackedFrame, OtdetFileWrittenEvent]):
|
|
|
113
113
|
)
|
|
114
114
|
logger().warning(f"Unfinished tracks: {self._ottrk_unfinished_tracks}")
|
|
115
115
|
if self.build_condition_fulfilled:
|
|
116
|
-
self._create_ottrk()
|
|
116
|
+
await self._create_ottrk()
|
|
117
117
|
|
|
118
|
-
def _create_ottrk(self) -> None:
|
|
118
|
+
async def _create_ottrk(self) -> None:
|
|
119
119
|
ottrk_data = self._builder.build()
|
|
120
|
-
self.write(ottrk_data)
|
|
120
|
+
await self.write(ottrk_data)
|
|
121
121
|
self.full_reset()
|
|
122
122
|
|
|
123
123
|
def full_reset(self) -> None:
|
|
@@ -126,7 +126,7 @@ class StreamOttrkFileWriter(Buffer[TrackedFrame, OtdetFileWrittenEvent]):
|
|
|
126
126
|
self._ottrk_unfinished_tracks = set()
|
|
127
127
|
self._current_output_file = None
|
|
128
128
|
|
|
129
|
-
def write(self, ottrk: dict) -> None:
|
|
129
|
+
async def write(self, ottrk: dict) -> None:
|
|
130
130
|
current_output_file = self.current_output_file
|
|
131
131
|
write_json(
|
|
132
132
|
dict_to_write=ottrk,
|
|
@@ -134,13 +134,15 @@ class StreamOttrkFileWriter(Buffer[TrackedFrame, OtdetFileWrittenEvent]):
|
|
|
134
134
|
filetype=self.config.filetypes.track,
|
|
135
135
|
overwrite=True,
|
|
136
136
|
)
|
|
137
|
-
self._notify_ottrk_file_written(save_location=current_output_file)
|
|
137
|
+
await self._notify_ottrk_file_written(save_location=current_output_file)
|
|
138
138
|
|
|
139
|
-
def force_flush(self, _: Any) -> None:
|
|
140
|
-
self._create_ottrk()
|
|
139
|
+
async def force_flush(self, _: Any) -> None:
|
|
140
|
+
await self._create_ottrk()
|
|
141
141
|
|
|
142
|
-
def register_observers(
|
|
142
|
+
def register_observers(
|
|
143
|
+
self, observer: AsyncObserver[OttrkFileWrittenEvent]
|
|
144
|
+
) -> None:
|
|
143
145
|
self._subject.register(observer)
|
|
144
146
|
|
|
145
|
-
def _notify_ottrk_file_written(self, save_location: Path) -> None:
|
|
146
|
-
self._subject.notify(OttrkFileWrittenEvent(save_location=save_location))
|
|
147
|
+
async def _notify_ottrk_file_written(self, save_location: Path) -> None:
|
|
148
|
+
await self._subject.notify(OttrkFileWrittenEvent(save_location=save_location))
|
OTVision/track/track.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from tqdm import tqdm
|
|
3
|
+
from tqdm.asyncio import tqdm
|
|
4
4
|
|
|
5
5
|
from OTVision.application.config import Config
|
|
6
6
|
from OTVision.application.get_current_config import GetCurrentConfig
|
|
@@ -32,7 +32,7 @@ class OtvisionTrack:
|
|
|
32
32
|
self._buffer = unfinished_chunks_buffer
|
|
33
33
|
self._tracking_run_id_generator = tracking_run_id_generator
|
|
34
34
|
|
|
35
|
-
def start(self) -> None:
|
|
35
|
+
async def start(self) -> None:
|
|
36
36
|
check_types(
|
|
37
37
|
self.config.track.sigma_l,
|
|
38
38
|
self.config.track.sigma_h,
|
|
@@ -61,6 +61,6 @@ class OtvisionTrack:
|
|
|
61
61
|
finished_chunk_progress = tqdm(
|
|
62
62
|
finished_chunk_stream, desc="export FrameChunk", total=len(detections_files)
|
|
63
63
|
)
|
|
64
|
-
self._track_exporter.export(
|
|
65
|
-
tracking_run_id,
|
|
64
|
+
await self._track_exporter.export(
|
|
65
|
+
tracking_run_id, finished_chunk_progress, self.config.track.overwrite
|
|
66
66
|
)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import AsyncIterator, Callable
|
|
4
4
|
|
|
5
5
|
from more_itertools import peekable
|
|
6
|
-
from tqdm import tqdm
|
|
6
|
+
from tqdm.asyncio import tqdm
|
|
7
7
|
|
|
8
8
|
from OTVision.application.config import DEFAULT_FILETYPE, OVERWRITE, TRACK
|
|
9
9
|
from OTVision.config import CONFIG
|
|
@@ -40,7 +40,7 @@ class ChunkBasedTracker(Tracker):
|
|
|
40
40
|
) -> TrackedFrame:
|
|
41
41
|
return self._tracker.track_frame(frames, id_generator)
|
|
42
42
|
|
|
43
|
-
def track_chunk(
|
|
43
|
+
async def track_chunk(
|
|
44
44
|
self,
|
|
45
45
|
chunk: FrameChunk,
|
|
46
46
|
is_last_chunk: bool,
|
|
@@ -50,16 +50,16 @@ class ChunkBasedTracker(Tracker):
|
|
|
50
50
|
chunk.frames, desc="track Frame", total=len(chunk.frames), leave=False
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
-
tracked_frames = self.track(
|
|
53
|
+
tracked_frames = self.track(frames_progress, id_generator)
|
|
54
54
|
return TrackedChunk(
|
|
55
55
|
file=chunk.file,
|
|
56
|
-
frames=
|
|
56
|
+
frames=[frame async for frame in tracked_frames],
|
|
57
57
|
metadata=chunk.metadata,
|
|
58
58
|
is_last_chunk=is_last_chunk,
|
|
59
59
|
frame_group_id=chunk.frame_group_id,
|
|
60
60
|
)
|
|
61
61
|
|
|
62
|
-
def track_file(
|
|
62
|
+
async def track_file(
|
|
63
63
|
self,
|
|
64
64
|
file: Path,
|
|
65
65
|
frame_group: FrameGroup,
|
|
@@ -68,7 +68,7 @@ class ChunkBasedTracker(Tracker):
|
|
|
68
68
|
frame_offset: int = 0,
|
|
69
69
|
) -> TrackedChunk:
|
|
70
70
|
chunk = self._chunk_parser.parse(file, frame_group, frame_offset)
|
|
71
|
-
return self.track_chunk(chunk, is_last_file, id_generator)
|
|
71
|
+
return await self.track_chunk(chunk, is_last_file, id_generator)
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
IdGeneratorFactory = Callable[[FrameGroup], IdGenerator]
|
|
@@ -91,10 +91,12 @@ class GroupedFilesTracker(ChunkBasedTracker):
|
|
|
91
91
|
self._overwrite = overwrite
|
|
92
92
|
self._file_type = file_type
|
|
93
93
|
|
|
94
|
-
def track_group(self, group: FrameGroup) ->
|
|
94
|
+
async def track_group(self, group: FrameGroup) -> AsyncIterator[TrackedChunk]:
|
|
95
95
|
if self.check_skip_due_to_existing_output_files(group):
|
|
96
96
|
log.warning(f"Skip FrameGroup {group.id}")
|
|
97
|
-
|
|
97
|
+
empty: list[TrackedChunk] = []
|
|
98
|
+
for item in empty:
|
|
99
|
+
yield item
|
|
98
100
|
|
|
99
101
|
frame_offset = 0 # frame no starts a 0 for each frame group
|
|
100
102
|
id_generator = self._id_generator_of(group) # new id generator per group
|
|
@@ -113,17 +115,20 @@ class GroupedFilesTracker(ChunkBasedTracker):
|
|
|
113
115
|
chunk = self._chunk_parser.parse(file, group, frame_offset)
|
|
114
116
|
frame_offset = chunk.frames[-1].no + 1 # assuming frames are sorted by no
|
|
115
117
|
|
|
116
|
-
tracked_chunk = self.track_chunk(chunk, is_last, id_generator)
|
|
118
|
+
tracked_chunk = await self.track_chunk(chunk, is_last, id_generator)
|
|
117
119
|
yield tracked_chunk
|
|
118
120
|
|
|
119
|
-
def group_and_track_files(
|
|
121
|
+
async def group_and_track_files(
|
|
122
|
+
self, files: list[Path]
|
|
123
|
+
) -> AsyncIterator[TrackedChunk]:
|
|
120
124
|
processed = self._group_parser.process_all(files)
|
|
121
125
|
|
|
122
126
|
processed_progress = tqdm(
|
|
123
127
|
processed, desc="track FrameGroup", total=len(processed), leave=False
|
|
124
128
|
)
|
|
125
129
|
for group in processed_progress:
|
|
126
|
-
|
|
130
|
+
async for tracked_chunk in self.track_group(group):
|
|
131
|
+
yield tracked_chunk
|
|
127
132
|
|
|
128
133
|
def check_skip_due_to_existing_output_files(self, group: FrameGroup) -> bool:
|
|
129
134
|
if not self._overwrite and group.check_any_output_file_exists(self._file_type):
|
|
@@ -151,18 +156,20 @@ class UnfinishedChunksBuffer(UnfinishedTracksBuffer[TrackedChunk, FinishedChunk]
|
|
|
151
156
|
super().__init__(keep_discarded)
|
|
152
157
|
self.tracker = tracker
|
|
153
158
|
|
|
154
|
-
def group_and_track(self, files: list[Path]) ->
|
|
159
|
+
async def group_and_track(self, files: list[Path]) -> AsyncIterator[FinishedChunk]:
|
|
155
160
|
processed = self.tracker._group_parser.process_all(files)
|
|
156
161
|
|
|
157
162
|
processed_progress = tqdm(
|
|
158
163
|
processed, desc="track FrameGroup", total=len(processed), leave=False
|
|
159
164
|
)
|
|
160
165
|
for group in processed_progress:
|
|
161
|
-
|
|
166
|
+
async for finished_chunk in self.track_group(group):
|
|
167
|
+
yield finished_chunk
|
|
162
168
|
|
|
163
|
-
def track_group(self, group: FrameGroup) ->
|
|
169
|
+
async def track_group(self, group: FrameGroup) -> AsyncIterator[FinishedChunk]:
|
|
164
170
|
tracked_chunk_stream = self.tracker.track_group(group)
|
|
165
|
-
|
|
171
|
+
async for finished_chunk in self.track_and_finish(tracked_chunk_stream):
|
|
172
|
+
yield finished_chunk
|
|
166
173
|
|
|
167
174
|
def _get_last_track_frames(self, container: TrackedChunk) -> dict[TrackId, FrameNo]:
|
|
168
175
|
return container.last_track_frame
|
OTVision/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: OTVision
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: OTVision is a core module of the OpenTrafficCam framework to perform object detection and tracking.
|
|
5
5
|
Project-URL: Homepage, https://opentrafficcam.org/
|
|
6
6
|
Project-URL: Documentation, https://opentrafficcam.org/overview/
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
OTVision/__init__.py,sha256=CLnfgTlVHM4_nzDacvy06Z_Crc3hU6usd0mUyEvBf24,781
|
|
2
2
|
OTVision/config.py,sha256=D4NIio27JG9hZk7yHI6kNKiMxKeKa_MGfrKNDdEH370,5389
|
|
3
3
|
OTVision/dataformat.py,sha256=BHF7qHzyNb80hI1EKfwcdJ9bgG_X4bp_hCXzdg7_MSA,1941
|
|
4
|
-
OTVision/version.py,sha256=
|
|
4
|
+
OTVision/version.py,sha256=t5L4l1wP_cwjTajQ9Ohra7CsFare4eme1NfQDb4e5Uk,175
|
|
5
5
|
OTVision/abstraction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
OTVision/abstraction/defaults.py,sha256=ftETDe25gmr563RPSbG6flcEiNiHnRb0iXK1Zj_zdNg,442
|
|
7
|
-
OTVision/abstraction/observer.py,sha256=
|
|
8
|
-
OTVision/abstraction/pipes_and_filter.py,sha256=
|
|
7
|
+
OTVision/abstraction/observer.py,sha256=jCNtp1_b9VzEDtdjbZQmOX6yXuQQxzYB-NvTgwzjRNk,4751
|
|
8
|
+
OTVision/abstraction/pipes_and_filter.py,sha256=aU28pDf4zJBOcghinPhywBQidhPf0lgpyqaAG_dQ9eo,912
|
|
9
9
|
OTVision/application/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
OTVision/application/buffer.py,sha256=
|
|
10
|
+
OTVision/application/buffer.py,sha256=LVRimNWZupcvncZ-UecrnAUtTsaEhVsBPUPjMeZQCKQ,788
|
|
11
11
|
OTVision/application/config.py,sha256=tTFVo2iJt3zCJ_p06FCeQOJ_NbuwmPd60ttrh-6fANI,13670
|
|
12
12
|
OTVision/application/config_parser.py,sha256=EVFEnrNs5xd1bWa023s_PPoxAxpLEaWbFoGjsS8PM8U,11920
|
|
13
13
|
OTVision/application/configure_logger.py,sha256=1TzHB-zm7vGTPtUp7m28ne4WxOyiUYeChLZU-ZPyOVQ,623
|
|
@@ -17,7 +17,7 @@ OTVision/application/get_current_config.py,sha256=iqtY10FRpn2FgLsasejjlyPFP3NrQJ
|
|
|
17
17
|
OTVision/application/otvision_save_path_provider.py,sha256=lUzjQ92Qjvxf9sbaZX591FuEaUW5gFAq4GpgmN1k_tM,2179
|
|
18
18
|
OTVision/application/update_current_config.py,sha256=iW1rpCClTHn8tnmVSpLVxdEB0nh1O_JCyxEqog0r0NU,333
|
|
19
19
|
OTVision/application/detect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
OTVision/application/detect/current_object_detector.py,sha256=
|
|
20
|
+
OTVision/application/detect/current_object_detector.py,sha256=jow7-h0D6FziHduBiihzcML4rt3bjxk7SOmPGwLyD6Y,1378
|
|
21
21
|
OTVision/application/detect/current_object_detector_metadata.py,sha256=xai0UBEzxr-rxXCc8mTmNDECds7mdsw2sem5HZxvQ4Q,1017
|
|
22
22
|
OTVision/application/detect/detected_frame_factory.py,sha256=sW_l0xaPz44_lPEmCPKz4Xg1Mv4ZGKN9CyBCG_iN8dQ,1007
|
|
23
23
|
OTVision/application/detect/factory.py,sha256=UCnLtgpWdNqwwjW0v2yzKF9Gacx6gewjTyy43wXs2Jg,938
|
|
@@ -34,38 +34,38 @@ OTVision/application/track/tracking_run_id.py,sha256=9_RgQSHR-IMgAM4LawtJWxNcVyw
|
|
|
34
34
|
OTVision/application/track/update_current_track_config.py,sha256=pCFNuGl6rqpsUUGm2i1kjLSuuITVXv2R79Xf81n45as,1667
|
|
35
35
|
OTVision/application/track/update_track_config_with_cli_args.py,sha256=5MDr2ih4xHzZ0kI6TSQ2C4mcWsPXW2PB80kA22-M_5I,2273
|
|
36
36
|
OTVision/application/video/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
OTVision/application/video/generate_video.py,sha256=
|
|
37
|
+
OTVision/application/video/generate_video.py,sha256=uv7cJPhWLZV53plkP4rCr6VqsGBQixSQ9NHViq3SiQM,525
|
|
38
38
|
OTVision/convert/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
39
|
OTVision/convert/convert.py,sha256=UUfzpbtMlxlJgKE6XeT5nyNqK26-K02bQDhq3o7KrXE,11050
|
|
40
40
|
OTVision/detect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
OTVision/detect/builder.py,sha256=
|
|
41
|
+
OTVision/detect/builder.py,sha256=oVVGj9iPO8IqZOB98E3759WdJS2rtn34Ye64Krvu_tE,8062
|
|
42
42
|
OTVision/detect/cli.py,sha256=3yf0G_B9cVm2CAz7AFzG10Cctv4ogMOxRHWEg8SZHss,7562
|
|
43
|
-
OTVision/detect/detect.py,sha256=
|
|
44
|
-
OTVision/detect/detected_frame_buffer.py,sha256=
|
|
45
|
-
OTVision/detect/detected_frame_producer.py,sha256=
|
|
46
|
-
OTVision/detect/detected_frame_producer_factory.py,sha256=
|
|
47
|
-
OTVision/detect/file_based_detect_builder.py,sha256=
|
|
43
|
+
OTVision/detect/detect.py,sha256=cBMw-sFCyXfd6vzl3zEa-5kVHZ5uW1FpOmtf1GZrw8o,1352
|
|
44
|
+
OTVision/detect/detected_frame_buffer.py,sha256=D_W4ZNyQxWd8ncm7tTvDmQMmRJLCW_EBuKBYfIcAz1k,1986
|
|
45
|
+
OTVision/detect/detected_frame_producer.py,sha256=RkAiw3RRUJmk05XQWR3Z-gEYkz7tbwhh8hEnRJpeSHI,540
|
|
46
|
+
OTVision/detect/detected_frame_producer_factory.py,sha256=eS9ekU2mI8MGjGq_UT-ejxuZDBp6-tauxLAd5i0lSBM,1596
|
|
47
|
+
OTVision/detect/file_based_detect_builder.py,sha256=jz8if-1_vUJSsqHRcyC_7ed_iuXqC9cJxysTFYWkGDw,2334
|
|
48
48
|
OTVision/detect/otdet.py,sha256=cIwCBVFYWma7oJynoaT0eyIAZw-M2iH1xkvjy6-TwEM,8475
|
|
49
|
-
OTVision/detect/otdet_file_writer.py,sha256=
|
|
49
|
+
OTVision/detect/otdet_file_writer.py,sha256=N9oQ9ddFpzVjH9mNX_EIHwcH6izJIvGlqPO2D4lrQMA,5432
|
|
50
50
|
OTVision/detect/pyav_frame_count_provider.py,sha256=w7p9iM3F2fljV8SD7q491gQhIHANbVczqtalcUiKj-E,453
|
|
51
|
-
OTVision/detect/rtsp_based_detect_builder.py,sha256=
|
|
52
|
-
OTVision/detect/rtsp_input_source.py,sha256=
|
|
51
|
+
OTVision/detect/rtsp_based_detect_builder.py,sha256=CiMAKCtNojvm7DLuAdrrDMOUPlrZXIwWiW8CoJ6fa9M,2681
|
|
52
|
+
OTVision/detect/rtsp_input_source.py,sha256=8lErJm18wFWCU0yyF2VK6VaDPaTPOdeblAOTlF-Y8d4,10963
|
|
53
53
|
OTVision/detect/timestamper.py,sha256=VvDTzHu9fTI7qQL9x775Gc27r47R8D5Pb040ffwO04k,5288
|
|
54
|
-
OTVision/detect/video_input_source.py,sha256=
|
|
55
|
-
OTVision/detect/yolo.py,sha256=
|
|
54
|
+
OTVision/detect/video_input_source.py,sha256=FJH1Tf0pSO5xHq_5pMj1VdXxrzYcpUxQ7JCfRp4MEII,10592
|
|
55
|
+
OTVision/detect/yolo.py,sha256=N68U8N7eiBFruIjWy391W7cvkV_WRvvJYUO0QTW-ZQI,10671
|
|
56
56
|
OTVision/detect/plugin_av/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
57
|
OTVision/detect/plugin_av/rotate_frame.py,sha256=4wJqTYI2HRlfa4p2Ffap33vLmKIzE_EwFvQraEkQ4R8,1055
|
|
58
58
|
OTVision/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
59
|
OTVision/domain/cli.py,sha256=INiw1dxQNO4XI6GGCIMhX2G08ig68FNS04fdtMt3gNs,2061
|
|
60
60
|
OTVision/domain/current_config.py,sha256=Q38NCktoGNU1Z_miXNoJXLH8-NDbVszwVOMGR1aAwWM,286
|
|
61
|
-
OTVision/domain/detect_producer_consumer.py,sha256=
|
|
61
|
+
OTVision/domain/detect_producer_consumer.py,sha256=MHnBuF39YnmUn6blnPqtiWp3kYqCpsCt_rsz6RwFqrw,828
|
|
62
62
|
OTVision/domain/detection.py,sha256=SZLP-87XE3NcTkeYz7GTqp4oPMiqI1P5gILp1_yHtxY,3761
|
|
63
63
|
OTVision/domain/frame.py,sha256=1H0Cqg_LvO9BEwzGRkvRyJm6AauR_yS6kq0PHujonZk,6791
|
|
64
|
-
OTVision/domain/input_source_detect.py,sha256=
|
|
65
|
-
OTVision/domain/object_detection.py,sha256=
|
|
64
|
+
OTVision/domain/input_source_detect.py,sha256=zUZZycqlkJpEqCnyOo188P12K2i9Os4QUQm2wVje0s8,1095
|
|
65
|
+
OTVision/domain/object_detection.py,sha256=BKwHo80u9w1Cn9Xpca3kqfBZC9JEj8iDRLYfonbxxRU,1330
|
|
66
66
|
OTVision/domain/serialization.py,sha256=S7gb648z_W8U3Fb6TSk7hVU4qHlGwOZ7D6FeYSLXQwM,257
|
|
67
67
|
OTVision/domain/time.py,sha256=_6a4zDbhXU7DmK7PdBYWRrrO2yQ4D68qtSYLTwnwWMQ,302
|
|
68
|
-
OTVision/domain/video_writer.py,sha256=
|
|
68
|
+
OTVision/domain/video_writer.py,sha256=hq67zMahLXAtHv57booORputnexhmUMu3XQk8n1Na-U,916
|
|
69
69
|
OTVision/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
70
70
|
OTVision/helpers/date.py,sha256=L99CFQ7u34RwGDisFlrQoopZnkOOggQGDehWUi8kLiY,1347
|
|
71
71
|
OTVision/helpers/files.py,sha256=G7zoOHzWIYrMmkjgHJHkZbh2hcGtnwZomuspthG2GsE,18444
|
|
@@ -75,20 +75,20 @@ OTVision/helpers/log.py,sha256=fOSMTXQRQ3_3zzYL8pDlx85IXPwyDsI2WGpK-V_R47Q,4985
|
|
|
75
75
|
OTVision/helpers/machine.py,sha256=8Bz_Eg7PS0IL4riOVeJcEIi5D9E8Ju8-JomTkW975p8,2166
|
|
76
76
|
OTVision/helpers/video.py,sha256=xyI35CiWXqoeGd3HeLhZUPxrLz8GccWyzHusxoweJr4,1480
|
|
77
77
|
OTVision/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
78
|
-
OTVision/plugin/ffmpeg_video_writer.py,sha256=
|
|
78
|
+
OTVision/plugin/ffmpeg_video_writer.py,sha256=rtzYjk74Vd0ydO2duY4B_AggM6fZwCvKCmGA3GmUx2g,10461
|
|
79
79
|
OTVision/plugin/generate_video.py,sha256=Jxk8iQBP8YhobRIRJ535yW3gx0h4d7H8oz0rULRPgcc,840
|
|
80
80
|
OTVision/plugin/yaml_serialization.py,sha256=LjJ_QLJPClRwsaw7ooagWT7LBW08OvSb527jbex1qIQ,557
|
|
81
81
|
OTVision/track/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
82
|
OTVision/track/builder.py,sha256=iDq9-QxU8c3CQQd5HDPzT2qkXWIyFl0fk658KQ7Soh0,4905
|
|
83
83
|
OTVision/track/cli.py,sha256=Lzmij9vMuaArB8MerDcGlaefwKMgRWNWov1YLGWA6SI,4294
|
|
84
84
|
OTVision/track/id_generator.py,sha256=2Lkegjhz8T2FXiK1HaiS_FbNZrJjIWzHi407-IoKAHg,241
|
|
85
|
-
OTVision/track/stream_ottrk_file_writer.py,sha256=
|
|
86
|
-
OTVision/track/track.py,sha256=
|
|
85
|
+
OTVision/track/stream_ottrk_file_writer.py,sha256=hncE6msUU4lHmYLP9JSv2VUKCJxnqQcX-DJQ0_fCQBg,5595
|
|
86
|
+
OTVision/track/track.py,sha256=wdb8krQGiSxFtZ0kGZkrUkgMElmTvKkQRpPzD7INeCI,2371
|
|
87
87
|
OTVision/track/exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
88
88
|
OTVision/track/exporter/filebased_exporter.py,sha256=sdaWY4CxjawyeEKG329T92Bodf1E8wjnXuXocv0Ppgo,986
|
|
89
89
|
OTVision/track/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
90
|
-
OTVision/track/model/track_exporter.py,sha256=
|
|
91
|
-
OTVision/track/model/tracking_interfaces.py,sha256=
|
|
90
|
+
OTVision/track/model/track_exporter.py,sha256=D3aU1qX-4Z_YNt21UJWq5PS8T_o1fX4wBJaM4JS93Mo,3289
|
|
91
|
+
OTVision/track/model/tracking_interfaces.py,sha256=Q2b2XUh4R4frJ1kfru9SK_9IsryACxOmaM7TqOIVYdM,11029
|
|
92
92
|
OTVision/track/model/filebased/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
93
93
|
OTVision/track/model/filebased/frame_chunk.py,sha256=rXhQCHXWGJbePy5ZW3JZCdltGz5mZxFdcrW0mgez-2k,6771
|
|
94
94
|
OTVision/track/model/filebased/frame_group.py,sha256=f-hXS1Vc5U_qf2cgNbYVeSTZ3dg5NUJhasOEHuuX1HE,2977
|
|
@@ -96,7 +96,7 @@ OTVision/track/parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
|
96
96
|
OTVision/track/parser/chunk_parser_plugins.py,sha256=iHAuxnoBQvwQ2j6RYfLZgk-EmsBgU0q29bYnf4kAJIM,2607
|
|
97
97
|
OTVision/track/parser/frame_group_parser_plugins.py,sha256=CMl_muqSKl4scIm4URluGZtPbQmMk1JfbJm3d5Yod9g,3815
|
|
98
98
|
OTVision/track/tracker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
|
-
OTVision/track/tracker/filebased_tracking.py,sha256=
|
|
99
|
+
OTVision/track/tracker/filebased_tracking.py,sha256=HKJouBDCCHZdUoZzongdiipB4IS0I9xwuqur4On7-LE,6878
|
|
100
100
|
OTVision/track/tracker/tracker_plugin_iou.py,sha256=AecE4CXRf4qUdN3_AvSFcsW4so-zDUGAVXqzfjSb-i0,7517
|
|
101
101
|
OTVision/transform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
102
102
|
OTVision/transform/get_homography.py,sha256=29waW61uzCCB7tlBAS2zck9sliAxZqnjjOa4jOOHHIc,5970
|
|
@@ -110,7 +110,7 @@ OTVision/view/view_helpers.py,sha256=a5yV_6ZxO5bxsSymOmxdHqzOEv0VFq4wFBopVRGuVRo
|
|
|
110
110
|
OTVision/view/view_track.py,sha256=vmfMqpbUfnzg_EsWiL-IIKNOApVF09dzSojHpUfYY6M,5393
|
|
111
111
|
OTVision/view/view_transform.py,sha256=HvRd8g8geKRy0OoiZUDn_oC3SJC5nuXhZf3uZelfGKg,5473
|
|
112
112
|
OTVision/view/helpers/OTC.ico,sha256=G9kwlDtgBXmXO3yxW6Z-xVFV2q4nUGuz9E1VPHSu_I8,21662
|
|
113
|
-
otvision-0.
|
|
114
|
-
otvision-0.
|
|
115
|
-
otvision-0.
|
|
116
|
-
otvision-0.
|
|
113
|
+
otvision-0.7.1.dist-info/METADATA,sha256=KDRy1WrtZHqplKsNC5sBkNt1PDJt3YWaoDe67iUWxjA,6943
|
|
114
|
+
otvision-0.7.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
115
|
+
otvision-0.7.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
116
|
+
otvision-0.7.1.dist-info/RECORD,,
|
|
File without changes
|