OTVision 0.6.5__py3-none-any.whl → 0.6.7__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.
@@ -20,6 +20,7 @@ class DetectedFrameFactory:
20
20
 
21
21
  return DetectedFrame(
22
22
  source=frame[FrameKeys.source],
23
+ output=frame[FrameKeys.output],
23
24
  no=frame[FrameKeys.frame],
24
25
  occurrence=frame[FrameKeys.occurrence],
25
26
  detections=detections,
@@ -62,7 +62,7 @@ class OtdetFileWriter:
62
62
  detect_config = config.detect
63
63
 
64
64
  actual_frames = len(event.frames)
65
- if (expected_duration := detect_config.expected_duration) is not None:
65
+ if expected_duration := detect_config.expected_duration:
66
66
  actual_fps = actual_frames / expected_duration.total_seconds()
67
67
  else:
68
68
  actual_fps = actual_frames / source_metadata.duration.total_seconds()
@@ -27,10 +27,12 @@ from OTVision.domain.time import DatetimeProvider
27
27
 
28
28
  RTSP_URL = "rtsp://127.0.0.1:8554/test"
29
29
  RETRY_SECONDS = 1
30
+ DEFAULT_READ_FAIL_THRESHOLD = 5
30
31
 
31
32
 
32
33
  class Counter:
33
34
  def __init__(self, start_value: int = 0) -> None:
35
+ self._start_value = start_value
34
36
  self.__counter = start_value
35
37
 
36
38
  def increment(self) -> None:
@@ -39,6 +41,9 @@ class Counter:
39
41
  def get(self) -> int:
40
42
  return self.__counter
41
43
 
44
+ def reset(self) -> None:
45
+ self.__counter = self._start_value
46
+
42
47
 
43
48
  class RtspInputSource(InputSourceDetect):
44
49
 
@@ -68,12 +73,17 @@ class RtspInputSource(InputSourceDetect):
68
73
  def flush_buffer_size(self) -> int:
69
74
  return self.stream_config.flush_buffer_size
70
75
 
76
+ @property
77
+ def fps(self) -> float:
78
+ return self.config.convert.output_fps
79
+
71
80
  def __init__(
72
81
  self,
73
82
  subject: Subject[FlushEvent],
74
83
  datetime_provider: DatetimeProvider,
75
84
  frame_counter: Counter,
76
85
  get_current_config: GetCurrentConfig,
86
+ read_fail_threshold: int = DEFAULT_READ_FAIL_THRESHOLD,
77
87
  ) -> None:
78
88
  super().__init__(subject)
79
89
  self._datetime_provider = datetime_provider
@@ -82,6 +92,11 @@ class RtspInputSource(InputSourceDetect):
82
92
  self._get_current_config = get_current_config
83
93
  self._current_stream: str | None = None
84
94
  self._current_video_capture: VideoCapture | None = None
95
+ self._stream_start_time: datetime = self._datetime_provider.provide()
96
+ self._current_video_start_time = self._stream_start_time
97
+ self._outdated = True
98
+ self._read_fail_threshold = read_fail_threshold
99
+ self._consecutive_read_fails = 0
85
100
 
86
101
  @property
87
102
  def _video_capture(self) -> VideoCapture:
@@ -106,20 +121,30 @@ class RtspInputSource(InputSourceDetect):
106
121
  return self._current_video_capture
107
122
 
108
123
  def produce(self) -> Generator[Frame, None, None]:
109
- start_time = self._datetime_provider.provide()
124
+ self._stream_start_time = self._datetime_provider.provide()
125
+ self._current_video_start_time = self._stream_start_time
110
126
  while not self.should_stop():
111
127
  if (frame := self._read_next_frame()) is not None:
112
128
  self._frame_counter.increment()
129
+ occurrence = self._datetime_provider.provide()
130
+
131
+ if self._outdated:
132
+ self._current_video_start_time = occurrence
133
+ self._outdated = False
113
134
 
114
135
  yield Frame(
115
136
  data=convert_frame_to_rgb(frame), # YOLO expects RGB
116
137
  frame=self.current_frame_number,
117
138
  source=self.rtsp_url,
118
- occurrence=self._datetime_provider.provide(),
139
+ output=self.create_output(),
140
+ occurrence=occurrence,
119
141
  )
120
142
  if self.flush_condition_met():
121
- self._notify(start_time)
122
- self._notify(start_time)
143
+ self._notify()
144
+ self._outdated = True
145
+ self._frame_counter.reset()
146
+
147
+ self._notify()
123
148
 
124
149
  def _init_video_capture(self, source: str) -> VideoCapture:
125
150
  cap = VideoCapture(source)
@@ -136,10 +161,22 @@ class RtspInputSource(InputSourceDetect):
136
161
  def _read_next_frame(self) -> ndarray | None:
137
162
  successful, frame = self._video_capture.read()
138
163
  if successful:
164
+ self._consecutive_read_fails = 0
139
165
  return frame
166
+ self._consecutive_read_fails += 1
167
+
168
+ if self._consecutive_read_fails >= self._read_fail_threshold:
169
+ self._try_reconnecting_stream()
170
+
140
171
  logger().debug("Failed to grab frame")
141
172
  return None
142
173
 
174
+ def _try_reconnecting_stream(self) -> None:
175
+ self._video_capture.release()
176
+ self._current_video_capture = None
177
+ if not self.should_stop() and self._current_stream is not None:
178
+ self._current_video_capture = self._init_video_capture(self._current_stream)
179
+
143
180
  def should_stop(self) -> bool:
144
181
  return self._stop_capture
145
182
 
@@ -152,24 +189,16 @@ class RtspInputSource(InputSourceDetect):
152
189
  def flush_condition_met(self) -> bool:
153
190
  return self.current_frame_number % self.flush_buffer_size == 0
154
191
 
155
- def _notify(self, start_time: datetime) -> None:
192
+ def _notify(self) -> None:
156
193
  frame_width = int(self._video_capture.get(CAP_PROP_FRAME_WIDTH))
157
194
  frame_height = int(self._video_capture.get(CAP_PROP_FRAME_HEIGHT))
158
- fps = self.config.convert.output_fps
159
- _start_time = calculate_start_time(
160
- start_time, self.current_frame_number, fps, self.flush_buffer_size
161
- )
162
195
  frames = (
163
196
  self.flush_buffer_size
164
197
  if self.current_frame_number % self.flush_buffer_size == 0
165
198
  else self.current_frame_number % self.flush_buffer_size
166
199
  )
167
- duration = timedelta(seconds=round(frames / fps))
168
- output_filename = (
169
- f"{self.stream_config.name}_FR{round(fps)}"
170
- f"_{_start_time.strftime(DATETIME_FORMAT)}.mp4"
171
- )
172
- output = str(self.stream_config.save_dir / output_filename)
200
+ duration = timedelta(seconds=round(frames / self.fps))
201
+ output = self.create_output()
173
202
  self._subject.notify(
174
203
  FlushEvent.create(
175
204
  source=self.rtsp_url,
@@ -177,22 +206,17 @@ class RtspInputSource(InputSourceDetect):
177
206
  duration=duration,
178
207
  source_width=frame_width,
179
208
  source_height=frame_height,
180
- source_fps=fps,
181
- start_time=_start_time,
209
+ source_fps=self.fps,
210
+ start_time=self._current_video_start_time,
182
211
  )
183
212
  )
184
213
 
185
-
186
- def calculate_start_time(
187
- start: datetime, current_frame_number: int, fps: float, flush_buffer_size: int
188
- ) -> datetime:
189
- offset_in_frames = (
190
- current_frame_number // flush_buffer_size - 1
191
- ) * flush_buffer_size
192
- if offset_in_frames == 0:
193
- return start
194
- offset_in_seconds = offset_in_frames / fps
195
- return start + timedelta(seconds=offset_in_seconds)
214
+ def create_output(self) -> str:
215
+ output_filename = (
216
+ f"{self.stream_config.name}_FR{round(self.fps)}"
217
+ f"_{self._current_video_start_time.strftime(DATETIME_FORMAT)}.mp4"
218
+ )
219
+ return str(self.stream_config.save_dir / output_filename)
196
220
 
197
221
 
198
222
  def convert_frame_to_rgb(frame: ndarray) -> ndarray:
@@ -61,6 +61,7 @@ class VideoTimestamper(Timestamper):
61
61
  data=frame[FrameKeys.data],
62
62
  frame=frame[FrameKeys.frame],
63
63
  source=frame[FrameKeys.source],
64
+ output=frame[FrameKeys.output],
64
65
  occurrence=occurrence,
65
66
  )
66
67
 
@@ -116,6 +116,7 @@ class VideoSource(InputSourceDetect):
116
116
  FrameKeys.data: rotated_image,
117
117
  FrameKeys.frame: frame_number,
118
118
  FrameKeys.source: str(video_file),
119
+ FrameKeys.output: str(video_file),
119
120
  }
120
121
  )
121
122
  else:
@@ -124,6 +125,7 @@ class VideoSource(InputSourceDetect):
124
125
  FrameKeys.data: None,
125
126
  FrameKeys.frame: frame_number,
126
127
  FrameKeys.source: str(video_file),
128
+ FrameKeys.output: str(video_file),
127
129
  }
128
130
  )
129
131
  counter += 1
@@ -206,8 +208,9 @@ class VideoSource(InputSourceDetect):
206
208
  def __add_occurrence(self, timestamper: Timestamper, frame: dict) -> Frame:
207
209
  updated = timestamper.stamp(frame)
208
210
  return Frame(
209
- data=updated["data"],
210
- frame=updated["frame"],
211
- source=updated["source"],
212
- occurrence=updated["occurrence"],
211
+ data=updated[FrameKeys.data],
212
+ frame=updated[FrameKeys.frame],
213
+ source=updated[FrameKeys.source],
214
+ output=updated[FrameKeys.output],
215
+ occurrence=updated[FrameKeys.occurrence],
213
216
  )
OTVision/domain/frame.py CHANGED
@@ -20,6 +20,7 @@ class FrameKeys:
20
20
  frame: Literal["frame"] = "frame"
21
21
  source: Literal["source"] = "source"
22
22
  occurrence: Literal["occurrence"] = "occurrence"
23
+ output: Literal["output"] = "output"
23
24
 
24
25
 
25
26
  class Frame(TypedDict):
@@ -35,6 +36,7 @@ class Frame(TypedDict):
35
36
  data: Optional[ndarray]
36
37
  frame: int
37
38
  source: str
39
+ output: str
38
40
  occurrence: datetime
39
41
 
40
42
 
@@ -49,6 +51,7 @@ class DetectedFrame:
49
51
  no (FrameNo): Frame number.
50
52
  occurrence (datetime): Time stamp, at which frame was recorded.
51
53
  source (str): Source from where frame was obtained, e.g. video file path.
54
+ output (str): Output file name, e.g. video file name.
52
55
  detections (Sequence[Detection]): A sequence of Detections occurring in frame.
53
56
  image (Optional[ndarray]): Optional image data of frame.
54
57
  """
@@ -56,6 +59,7 @@ class DetectedFrame:
56
59
  no: FrameNo
57
60
  occurrence: datetime
58
61
  source: str
62
+ output: str
59
63
  detections: Sequence[Detection]
60
64
  image: Optional[ndarray] = None
61
65
 
@@ -64,6 +68,7 @@ class DetectedFrame:
64
68
  no=self.no,
65
69
  occurrence=self.occurrence,
66
70
  source=self.source,
71
+ output=self.output,
67
72
  detections=self.detections,
68
73
  image=None,
69
74
  )
@@ -153,6 +158,7 @@ class TrackedFrame(DetectedFrame):
153
158
  no=self.no,
154
159
  occurrence=self.occurrence,
155
160
  source=self.source,
161
+ output=self.output,
156
162
  finished_tracks=self.finished_tracks,
157
163
  detections=detections,
158
164
  image=self.image,
@@ -62,6 +62,7 @@ class JsonChunkParser(ChunkParser):
62
62
  no=int(key) + frame_offset,
63
63
  occurrence=occurrence,
64
64
  source=str(file),
65
+ output=str(file),
65
66
  detections=detections,
66
67
  image=None,
67
68
  )
@@ -222,6 +222,7 @@ class IouTracker(Tracker):
222
222
  no=frame.no,
223
223
  occurrence=frame.occurrence,
224
224
  source=frame.source,
225
+ output=frame.output,
225
226
  detections=tracked_detections,
226
227
  image=frame.image,
227
228
  finished_tracks=set(finished_track_ids),
OTVision/version.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "v0.6.5"
1
+ __version__ = "v0.6.7"
2
2
 
3
3
 
4
4
  def otdet_version() -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OTVision
3
- Version: 0.6.5
3
+ Version: 0.6.7
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/
@@ -27,7 +27,6 @@ Requires-Dist: numpy==2.1.1; sys_platform != 'win32'
27
27
  Requires-Dist: opencv-python==4.10.0.84
28
28
  Requires-Dist: pandas==2.2.3
29
29
  Requires-Dist: pyyaml==6.0.2
30
- Requires-Dist: setuptools==74.0.0
31
30
  Requires-Dist: torch==2.3.1
32
31
  Requires-Dist: torchvision==0.18.1
33
32
  Requires-Dist: tqdm==4.67.1
@@ -1,7 +1,7 @@
1
1
  OTVision/__init__.py,sha256=CLnfgTlVHM4_nzDacvy06Z_Crc3hU6usd0mUyEvBf24,781
2
2
  OTVision/config.py,sha256=0ecnI0N2rS2q0Ld6gBpK4iU2iyuUw683XWjj4g-L1m4,5336
3
3
  OTVision/dataformat.py,sha256=BHF7qHzyNb80hI1EKfwcdJ9bgG_X4bp_hCXzdg7_MSA,1941
4
- OTVision/version.py,sha256=pPG0EpzyxpxNsBhpPGGgivl4nunilLlKyXa5gBTSYYo,175
4
+ OTVision/version.py,sha256=FMJEQ4Oc-KD5z3-00dYgMptQRxDN5LgvDe6DvUmDwwI,175
5
5
  OTVision/abstraction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  OTVision/abstraction/defaults.py,sha256=ftETDe25gmr563RPSbG6flcEiNiHnRb0iXK1Zj_zdNg,442
7
7
  OTVision/abstraction/observer.py,sha256=ZFGxUUjI3wUpf5ogXg2yDe-QjCcXre6SxH5zOogOx2U,1350
@@ -18,7 +18,7 @@ OTVision/application/update_current_config.py,sha256=iW1rpCClTHn8tnmVSpLVxdEB0nh
18
18
  OTVision/application/detect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  OTVision/application/detect/current_object_detector.py,sha256=J4U5k1s902agN90FYR4M_gO8MYYa_5XkYGREjStWCxQ,1343
20
20
  OTVision/application/detect/current_object_detector_metadata.py,sha256=xai0UBEzxr-rxXCc8mTmNDECds7mdsw2sem5HZxvQ4Q,1017
21
- OTVision/application/detect/detected_frame_factory.py,sha256=Rl6ElFYulgWPK_XZQSXZcT6caF8hvfzMKsOFefYO_pY,963
21
+ OTVision/application/detect/detected_frame_factory.py,sha256=sW_l0xaPz44_lPEmCPKz4Xg1Mv4ZGKN9CyBCG_iN8dQ,1007
22
22
  OTVision/application/detect/detected_frame_producer.py,sha256=LD4AnQG04YGE68TpxmaRuWRZeCZfhp8oixk6SHTru7c,906
23
23
  OTVision/application/detect/detection_file_save_path_provider.py,sha256=nUyzgR7imrH8PkUl_72kdUDiolPXq1_RQqbpFwLI5Cs,2165
24
24
  OTVision/application/detect/factory.py,sha256=UCnLtgpWdNqwwjW0v2yzKF9Gacx6gewjTyy43wXs2Jg,938
@@ -38,12 +38,12 @@ OTVision/detect/detect.py,sha256=YaVS-DJXdEmh-OzwE31UPNl2uk7mcFyO_CKKTgMeiuM,132
38
38
  OTVision/detect/detected_frame_buffer.py,sha256=TrLbImpvCm1B09Z3c000e2uDO5WguyVwoMmFAqH8Zvk,1505
39
39
  OTVision/detect/file_based_detect_builder.py,sha256=C6BqVuknbJzX-B4N4nSwJgEpt9Nf79Oen8H6so9AflU,754
40
40
  OTVision/detect/otdet.py,sha256=-8rZGY9NVRAwIHeVcNCjm665SmnW5UVIO_PSKdHegDA,6274
41
- OTVision/detect/otdet_file_writer.py,sha256=l4NRWwMywiho4KOhrzxpJqAklyzE6nOtxpkt1kyy3Ss,4318
41
+ OTVision/detect/otdet_file_writer.py,sha256=idgPKwhgEd18HT7HshaT304JPlSw0kT4Isn2gWJRaSk,4304
42
42
  OTVision/detect/pyav_frame_count_provider.py,sha256=w7p9iM3F2fljV8SD7q491gQhIHANbVczqtalcUiKj-E,453
43
43
  OTVision/detect/rtsp_based_detect_builder.py,sha256=5-jg4ivJhiWi3PVIILVThorDNKg-i4Z-rFqZ2n01RDY,1322
44
- OTVision/detect/rtsp_input_source.py,sha256=U0yY-t56wxyh1j-7R48Dbrb4bTGYnCwIAiuHZjM2EGw,6568
45
- OTVision/detect/timestamper.py,sha256=lexX7zahtoyg0tnyu5lkiqHvauewqBCLe0DPXBR10YY,5244
46
- OTVision/detect/video_input_source.py,sha256=vaf1RfoeXnOqrk76xvcrX_UsAseNKeKXkg73Aurj3NI,8555
44
+ OTVision/detect/rtsp_input_source.py,sha256=11R6ogEJ8tNBi7qYdV3RzlP_Bp1jojSnQe_tpDBvZBs,7552
45
+ OTVision/detect/timestamper.py,sha256=VvDTzHu9fTI7qQL9x775Gc27r47R8D5Pb040ffwO04k,5288
46
+ OTVision/detect/video_input_source.py,sha256=GLzG4LeZNYcOE1tHyebL51HxBHzJOVaDfJKsLiAtu4A,8775
47
47
  OTVision/detect/yolo.py,sha256=Ksj8X7DZmONalaMB_iz-AtXwhEk4Fu7nZNzrXpqfhQw,10451
48
48
  OTVision/detect/plugin_av/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  OTVision/detect/plugin_av/rotate_frame.py,sha256=4wJqTYI2HRlfa4p2Ffap33vLmKIzE_EwFvQraEkQ4R8,1055
@@ -52,7 +52,7 @@ OTVision/domain/cli.py,sha256=VNZZ_zc1x6RzndQlOFp5HSeWTg_VAiF1G3IhYymdrzA,1781
52
52
  OTVision/domain/current_config.py,sha256=Q38NCktoGNU1Z_miXNoJXLH8-NDbVszwVOMGR1aAwWM,286
53
53
  OTVision/domain/detect_producer_consumer.py,sha256=gD7NwscQZLmCxMDZpZkGql0oMrpGHDBBNvdTXs58Vvw,855
54
54
  OTVision/domain/detection.py,sha256=SZLP-87XE3NcTkeYz7GTqp4oPMiqI1P5gILp1_yHtxY,3761
55
- OTVision/domain/frame.py,sha256=qHduCRbBTgzGLIuu7MlLWvhzphPlD3V0nrjlEApmr00,6211
55
+ OTVision/domain/frame.py,sha256=Hv1v9cegfhVGgl2MB4uKYbvngfkOLrvES_w4gD0HtMo,6410
56
56
  OTVision/domain/input_source_detect.py,sha256=9DzkTg5dh7_KmxE9oxdmxrcTYhvZY8hHLZwhrh7Gz2o,1245
57
57
  OTVision/domain/object_detection.py,sha256=kyrTbP9sZBKtGo54vCNfluDMM8wpWZST9Oqf8m8Q1y4,1394
58
58
  OTVision/domain/serialization.py,sha256=S7gb648z_W8U3Fb6TSk7hVU4qHlGwOZ7D6FeYSLXQwM,257
@@ -81,11 +81,11 @@ OTVision/track/model/filebased/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
81
81
  OTVision/track/model/filebased/frame_chunk.py,sha256=rXhQCHXWGJbePy5ZW3JZCdltGz5mZxFdcrW0mgez-2k,6771
82
82
  OTVision/track/model/filebased/frame_group.py,sha256=f-hXS1Vc5U_qf2cgNbYVeSTZ3dg5NUJhasOEHuuX1HE,2977
83
83
  OTVision/track/parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
- OTVision/track/parser/chunk_parser_plugins.py,sha256=ojBv_mCOXABmygxz5mhXzeysur3ECmT4a4VlGkeT11k,3108
84
+ OTVision/track/parser/chunk_parser_plugins.py,sha256=X86W_TBQN20ldZIEuv63FSvWBacG0wEaD0YkC9jcJVg,3142
85
85
  OTVision/track/parser/frame_group_parser_plugins.py,sha256=TWnGhM-N7ldN8LHZ1YYecEjn4xo2o91PyOGgr4Jzh9M,5479
86
86
  OTVision/track/tracker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
87
  OTVision/track/tracker/filebased_tracking.py,sha256=H3WYbsSca-67898diBoixpLjBqQDnOSiqnbvvySE6fc,6576
88
- OTVision/track/tracker/tracker_plugin_iou.py,sha256=PQOB3fXlNKSTEGK7HFfaUfTRgXSY1ZhlVmjk1VWugFU,7484
88
+ OTVision/track/tracker/tracker_plugin_iou.py,sha256=AecE4CXRf4qUdN3_AvSFcsW4so-zDUGAVXqzfjSb-i0,7517
89
89
  OTVision/transform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  OTVision/transform/get_homography.py,sha256=29waW61uzCCB7tlBAS2zck9sliAxZqnjjOa4jOOHHIc,5970
91
91
  OTVision/transform/reference_points_picker.py,sha256=LOmwzCaqbJnXVhaTSaZtkCzTMDamYjbTpY8I99pN0rg,16578
@@ -98,7 +98,7 @@ OTVision/view/view_helpers.py,sha256=a5yV_6ZxO5bxsSymOmxdHqzOEv0VFq4wFBopVRGuVRo
98
98
  OTVision/view/view_track.py,sha256=vmfMqpbUfnzg_EsWiL-IIKNOApVF09dzSojHpUfYY6M,5393
99
99
  OTVision/view/view_transform.py,sha256=HvRd8g8geKRy0OoiZUDn_oC3SJC5nuXhZf3uZelfGKg,5473
100
100
  OTVision/view/helpers/OTC.ico,sha256=G9kwlDtgBXmXO3yxW6Z-xVFV2q4nUGuz9E1VPHSu_I8,21662
101
- otvision-0.6.5.dist-info/METADATA,sha256=-YE_U1NlesErB101C72im0uWFlf_7ItDmD0YIg5cVvY,6262
102
- otvision-0.6.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
103
- otvision-0.6.5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
104
- otvision-0.6.5.dist-info/RECORD,,
101
+ otvision-0.6.7.dist-info/METADATA,sha256=kt20s58WTB5nfTZK6ow-iXvXmiBlOQmRk0Dks_PalDE,6228
102
+ otvision-0.6.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
103
+ otvision-0.6.7.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
104
+ otvision-0.6.7.dist-info/RECORD,,