numpyimage 3.0.1__tar.gz → 3.1.0__tar.gz
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.
- {numpyimage-3.0.1/numpyimage.egg-info → numpyimage-3.1.0}/PKG-INFO +1 -1
- {numpyimage-3.0.1 → numpyimage-3.1.0}/npimage/vidio.py +47 -14
- {numpyimage-3.0.1 → numpyimage-3.1.0/numpyimage.egg-info}/PKG-INFO +1 -1
- {numpyimage-3.0.1 → numpyimage-3.1.0}/pyproject.toml +1 -1
- {numpyimage-3.0.1 → numpyimage-3.1.0}/LICENSE +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/README.md +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/npimage/__init__.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/npimage/align.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/npimage/graphics.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/npimage/imageio.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/npimage/nrrd_utils.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/npimage/operations.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/npimage/utils.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/numpyimage.egg-info/SOURCES.txt +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/numpyimage.egg-info/dependency_links.txt +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/numpyimage.egg-info/requires.txt +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/numpyimage.egg-info/top_level.txt +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/setup.cfg +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/tests/test_heic.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/tests/test_pbm.py +0 -0
- {numpyimage-3.0.1 → numpyimage-3.1.0}/tests/test_video_writers.py +0 -0
|
@@ -161,6 +161,7 @@ class VideoStreamer:
|
|
|
161
161
|
self.container = av.open(str(self.filename))
|
|
162
162
|
self.stream = self.container.streams.video[0]
|
|
163
163
|
self.time_base = self.stream.time_base
|
|
164
|
+
self._frame_iterator = self.container.decode(self.stream)
|
|
164
165
|
self._shape = None
|
|
165
166
|
self._first_frame = None
|
|
166
167
|
self._width = None
|
|
@@ -313,6 +314,7 @@ class VideoStreamer:
|
|
|
313
314
|
return float(1.0 / self._framerate)
|
|
314
315
|
|
|
315
316
|
def frame_number_to_pts(self, frame_number: int) -> int:
|
|
317
|
+
frame_number = self._normalize_frame_number(frame_number)
|
|
316
318
|
if self._framerate == 'variable':
|
|
317
319
|
return self.frames_pts[frame_number]
|
|
318
320
|
else:
|
|
@@ -321,6 +323,22 @@ class VideoStreamer:
|
|
|
321
323
|
def frame_number_to_time(self, frame_number: int) -> float:
|
|
322
324
|
return float(self.frame_number_to_pts(frame_number) * self.time_base)
|
|
323
325
|
|
|
326
|
+
def pts_to_frame_number(self, pts: int) -> int:
|
|
327
|
+
if self._framerate == 'variable':
|
|
328
|
+
if pts not in self.frames_pts:
|
|
329
|
+
raise ValueError(f'PTS {pts} not in video index.')
|
|
330
|
+
return self.frames_pts.index(pts)
|
|
331
|
+
else:
|
|
332
|
+
if pts < self.pts0:
|
|
333
|
+
raise ValueError(f'PTS {pts} is before the start of the'
|
|
334
|
+
f' video (PTS {self.pts0}).')
|
|
335
|
+
if pts > self.pts_delta * (self.n_frames - 1) + self.pts0:
|
|
336
|
+
raise ValueError(f'PTS {pts} is after the end of the video (PTS '
|
|
337
|
+
f'{self.pts_delta * (self.n_frames - 1) + self.pts0}).')
|
|
338
|
+
if (pts - self.pts0) % self.pts_delta != 0:
|
|
339
|
+
raise ValueError(f'PTS {pts} is between frames for this video.')
|
|
340
|
+
return (pts - self.pts0) // self.pts_delta
|
|
341
|
+
|
|
324
342
|
def __getitem__(self, key) -> np.ndarray:
|
|
325
343
|
if isinstance(key, tuple):
|
|
326
344
|
frame_idx = key[0]
|
|
@@ -349,9 +367,11 @@ class VideoStreamer:
|
|
|
349
367
|
to the requested frame number.
|
|
350
368
|
"""
|
|
351
369
|
target_pts = self.frame_number_to_pts(frame_number)
|
|
352
|
-
for frame in self.
|
|
370
|
+
for frame in self._frame_iterator:
|
|
353
371
|
if frame.pts is None:
|
|
354
|
-
|
|
372
|
+
if self.verbose:
|
|
373
|
+
print('WARNING: Skipping a frame with no PTS.')
|
|
374
|
+
continue
|
|
355
375
|
if frame.pts == target_pts:
|
|
356
376
|
frame = frame.to_ndarray(format='rgb24')
|
|
357
377
|
self._current_frame_number = frame_number
|
|
@@ -359,23 +379,14 @@ class VideoStreamer:
|
|
|
359
379
|
if frame.pts > target_pts:
|
|
360
380
|
raise RuntimeError(f'Frame with PTS {target_pts} not found after'
|
|
361
381
|
f' seeking – current frame PTS: {frame.pts}')
|
|
362
|
-
raise RuntimeError(
|
|
363
|
-
f'
|
|
382
|
+
raise RuntimeError('Ran out of frames before finding frame with requested'
|
|
383
|
+
f' PTS {target_pts} – current frame PTS: {frame.pts}')
|
|
364
384
|
|
|
365
385
|
if isinstance(frame_number, slice): # Support slicing
|
|
366
386
|
start, stop, step = frame_number.indices(self.n_frames)
|
|
367
387
|
return np.array([self._get_frame(i) for i in range(start, stop, step)])
|
|
368
388
|
with self._lock:
|
|
369
|
-
|
|
370
|
-
if frame_number < 0 and frame_number + self.n_frames >= 0:
|
|
371
|
-
frame_number += self.n_frames
|
|
372
|
-
elif frame_number >= self.n_frames:
|
|
373
|
-
raise IndexError(f'Frame {frame_number} out of range:'
|
|
374
|
-
f' [0, {self.n_frames-1}]')
|
|
375
|
-
elif frame_number < 0:
|
|
376
|
-
raise IndexError(f'Negative frame {frame_number} out of'
|
|
377
|
-
f' range: [-{self.n_frames}, -1]')
|
|
378
|
-
|
|
389
|
+
frame_number = self._normalize_frame_number(frame_number)
|
|
379
390
|
if (self._current_frame_number is None
|
|
380
391
|
or frame_number <= self._current_frame_number
|
|
381
392
|
or frame_number > self._current_frame_number + 100):
|
|
@@ -391,6 +402,28 @@ class VideoStreamer:
|
|
|
391
402
|
image = np.rot90(image, k=-int(self.rotation) // 90)
|
|
392
403
|
return image
|
|
393
404
|
|
|
405
|
+
def _normalize_frame_number(self, frame_number: int) -> int:
|
|
406
|
+
"""
|
|
407
|
+
Support negative indexing by converting negative frame numbers to
|
|
408
|
+
positive ones, e.g. -1 becomes n_frames - 1, -2 becomes n_frames - 2, etc.
|
|
409
|
+
"""
|
|
410
|
+
try:
|
|
411
|
+
frame_number = int(frame_number)
|
|
412
|
+
except ValueError:
|
|
413
|
+
raise TypeError(f'Frame number must be castable to int but got "{frame_number}"')
|
|
414
|
+
|
|
415
|
+
if frame_number < -self.n_frames:
|
|
416
|
+
raise IndexError(f'Negative frame {frame_number} not in'
|
|
417
|
+
f' valid range [-{self.n_frames}, -1]')
|
|
418
|
+
elif -self.n_frames <= frame_number and frame_number < 0:
|
|
419
|
+
return frame_number + self.n_frames
|
|
420
|
+
elif 0 <= frame_number and frame_number < self.n_frames:
|
|
421
|
+
return frame_number
|
|
422
|
+
elif self.n_frames <= frame_number:
|
|
423
|
+
raise IndexError(f'Frame {frame_number} not in'
|
|
424
|
+
f' valid range [0, {self.n_frames-1}]')
|
|
425
|
+
raise IndexError(f'Frame {frame_number} not understood')
|
|
426
|
+
|
|
394
427
|
@property
|
|
395
428
|
def first_frame(self):
|
|
396
429
|
if self._first_frame is None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|