Skip to content

Commit 6cec53f

Browse files
authored
Merge pull request #278 from clamsproject/rep-frame-helper
Rep frame helper
2 parents 262015b + fa069fc commit 6cec53f

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

mmif/utils/video_document_helper.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import mmif
77
from mmif import Annotation, Document, Mmif
88
from mmif.utils.timeunit_helper import convert
9-
from mmif.vocabulary import DocumentTypes
9+
from mmif.vocabulary import DocumentTypes, AnnotationTypes
1010

1111
for cv_dep in ('cv2', 'ffmpeg', 'PIL'):
1212
try:
@@ -127,6 +127,42 @@ def extract_mid_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
127127
return extract_frames_as_images(vd, [get_mid_framenum(mmif, time_frame)], as_PIL=as_PIL)[0]
128128

129129

130+
def get_representative_framenum(mmif: Mmif, time_frame: Annotation):
131+
"""
132+
Calculates the representative frame number from an annotation.
133+
134+
:param mmif: :py:class:`~mmif.serialize.mmif.Mmif` instance
135+
:param time_frame: :py:class:`~mmif.serialize.annotation.Annotation` instance that holds a time interval annotation containing a `representatives` property (``"@type": ".../TimeFrame/..."``)
136+
:return: representative frame number as an integer
137+
"""
138+
if 'representatives' not in time_frame.properties:
139+
raise ValueError(f'The time frame {time_frame.id} does not have a representative.')
140+
timeunit = time_frame.get_property('timeUnit')
141+
video_document = mmif[time_frame.get_property('document')]
142+
fps = get_framerate(video_document)
143+
representatives = time_frame.get_property('representatives')
144+
top_representative_id = representatives[0]
145+
try:
146+
representative_timepoint_anno = mmif[time_frame._parent_view_id+time_frame.id_delimiter+top_representative_id]
147+
except KeyError:
148+
raise ValueError(f'Representative timepoint {top_representative_id} not found in any view.')
149+
return convert(representative_timepoint_anno.get_property('timePoint'), timeunit, 'frame', fps)
150+
151+
152+
def extract_representative_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
153+
"""
154+
Extracts the representative frame of an annotation as a numpy ndarray or PIL Image.
155+
156+
:param mmif: :py:class:`~mmif.serialize.mmif.Mmif` instance
157+
:param time_frame: :py:class:`~mmif.serialize.annotation.Annotation` instance that holds a time interval annotation (``"@type": ".../TimeFrame/..."``)
158+
:param as_PIL: return :py:class:`~PIL.Image.Image` instead of :py:class:`~numpy.ndarray`
159+
:return: frame as a :py:class:`numpy.ndarray` or :py:class:`PIL.Image.Image`
160+
"""
161+
video_document = mmif[time_frame.get_property('document')]
162+
rep_frame_num = get_representative_framenum(mmif, time_frame)
163+
return extract_frames_as_images(video_document, [rep_frame_num], as_PIL=as_PIL)[0]
164+
165+
130166
def sample_frames(start_frame: int, end_frame: int, sample_rate: float = 1) -> List[int]:
131167
"""
132168
Helper function to sample frames from a time interval.

tests/test_utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ def test_extract_mid_frame(self):
4444
tf = self.a_view.new_annotation(AnnotationTypes.TimeFrame, start=0, end=3, timeUnit='seconds', document='d1')
4545
self.assertEqual(vdh.convert(1.5, 's', 'f', self.fps), vdh.get_mid_framenum(self.mmif_obj, tf))
4646

47+
def test_extract_representative_frame(self):
48+
tp = self.a_view.new_annotation(AnnotationTypes.TimePoint, timePoint=1500, timeUnit='milliseconds', document='d1')
49+
tf = self.a_view.new_annotation(AnnotationTypes.TimeFrame, start=1000, end=2000, timeUnit='milliseconds', document='d1')
50+
tf.add_property('representatives', [tp.id])
51+
rep_frame_num = vdh.get_representative_framenum(self.mmif_obj, tf)
52+
expected_frame_num = vdh.millisecond_to_framenum(self.video_doc, tp.get_property('timePoint'))
53+
self.assertEqual(expected_frame_num, rep_frame_num)
54+
# check there is an error if no representatives
55+
tf = self.a_view.new_annotation(AnnotationTypes.TimeFrame, start=1000, end=2000, timeUnit='milliseconds', document='d1')
56+
with pytest.raises(ValueError):
57+
vdh.get_representative_framenum(self.mmif_obj, tf)
58+
# check there is an error if there is a representative referencing a timepoint that
59+
# does not exist
60+
tf.add_property('representatives', ['fake_tp_id'])
61+
with pytest.raises(ValueError):
62+
vdh.get_representative_framenum(self.mmif_obj, tf)
63+
4764
def test_get_framerate(self):
4865
self.assertAlmostEqual(29.97, vdh.get_framerate(self.video_doc), places=0)
4966

0 commit comments

Comments
 (0)