Skip to content

Commit e776810

Browse files
authored
Merge pull request #291 from clamsproject/281-improved-rep-frame-extr
improving representative frame extraction
2 parents af83e13 + ab40d2b commit e776810

File tree

2 files changed

+42
-18
lines changed

2 files changed

+42
-18
lines changed

mmif/utils/video_document_helper.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ def extract_frames_as_images(video_document: Document, framenums: List[int], as_
101101
return frames
102102

103103

104-
def get_mid_framenum(mmif: Mmif, time_frame: Annotation):
104+
def get_mid_framenum(mmif: Mmif, time_frame: Annotation) -> int:
105+
warnings.warn('This function is deprecated. Use ``get_representative_framenums()`` instead.', DeprecationWarning, stacklevel=2)
106+
return _get_mid_framenum(mmif, time_frame)
107+
108+
109+
def _get_mid_framenum(mmif: Mmif, time_frame: Annotation) -> int:
105110
"""
106111
Calculates the middle frame number of a time interval annotation.
107112
@@ -112,7 +117,7 @@ def get_mid_framenum(mmif: Mmif, time_frame: Annotation):
112117
timeunit = time_frame.get_property('timeUnit')
113118
video_document = mmif[time_frame.get_property('document')]
114119
fps = get_framerate(video_document)
115-
return convert(time_frame.get_property('start') + time_frame.get_property('end'), timeunit, 'frame', fps) // 2
120+
return int(convert(time_frame.get_property('start') + time_frame.get_property('end'), timeunit, 'frame', fps) // 2)
116121

117122

118123
def extract_mid_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
@@ -124,44 +129,65 @@ def extract_mid_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
124129
:param as_PIL: return :py:class:`~PIL.Image.Image` instead of :py:class:`~numpy.ndarray`
125130
:return: frame as a :py:class:`numpy.ndarray` or :py:class:`PIL.Image.Image`
126131
"""
132+
warnings.warn('This function is deprecated. Use ``extract_representative_frames()`` instead.', DeprecationWarning, stacklevel=2)
127133
vd = mmif[time_frame.get_property('document')]
128134
return extract_frames_as_images(vd, [get_mid_framenum(mmif, time_frame)], as_PIL=as_PIL)[0]
129135

130136

131-
def get_representative_framenum(mmif: Mmif, time_frame: Annotation):
137+
def get_representative_framenums(mmif: Mmif, time_frame: Annotation) -> List[int]:
132138
"""
133-
Calculates the representative frame number from an annotation.
139+
Calculates the representative frame numbers from an annotation. To pick the representative frames, it first looks
140+
up the ``representatives`` property of the ``TimeFrame`` annotation. If it is not found, it will calculate the
141+
number of the middle frame.
134142
135143
:param mmif: :py:class:`~mmif.serialize.mmif.Mmif` instance
136144
:param time_frame: :py:class:`~mmif.serialize.annotation.Annotation` instance that holds a time interval annotation containing a `representatives` property (``"@type": ".../TimeFrame/..."``)
137145
:return: representative frame number as an integer
138146
"""
139147
if 'representatives' not in time_frame.properties:
140-
raise ValueError(f'The time frame {time_frame.id} does not have a representative.')
148+
return [_get_mid_framenum(mmif, time_frame)]
141149
timeunit = time_frame.get_property('timeUnit')
142150
video_document = mmif[time_frame.get_property('document')]
143151
fps = get_framerate(video_document)
144152
representatives = time_frame.get_property('representatives')
145-
top_representative_id = representatives[0]
153+
ref_frams = []
154+
for rep in representatives:
155+
if Mmif.id_delimiter in rep:
156+
rep_long_id = rep
157+
else:
158+
rep_long_id = time_frame._parent_view_id+time_frame.id_delimiter+rep
159+
try:
160+
rep_anno = mmif[rep_long_id]
161+
except KeyError:
162+
raise ValueError(f'Representative timepoint {rep_long_id} not found in any view.')
163+
ref_frams.append(int(convert(rep_anno.get_property('timePoint'), timeunit, 'frame', fps)))
164+
return ref_frams
165+
166+
167+
def get_representative_framenum(mmif: Mmif, time_frame: Annotation) -> int:
168+
"""
169+
A thin wrapper around :py:func:`get_representative_framenums` to return a single representative frame number. Always
170+
return the first frame number found.
171+
"""
146172
try:
147-
representative_timepoint_anno = mmif[time_frame._parent_view_id+time_frame.id_delimiter+top_representative_id]
148-
except KeyError:
149-
raise ValueError(f'Representative timepoint {top_representative_id} not found in any view.')
150-
return convert(representative_timepoint_anno.get_property('timePoint'), timeunit, 'frame', fps)
173+
return get_representative_framenums(mmif, time_frame)[0]
174+
except IndexError:
175+
raise ValueError(f'No representative frame found in the TimeFrame annotation {time_frame.id}.')
151176

152177

153-
def extract_representative_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
178+
def extract_representative_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False, first_only: bool = True):
154179
"""
155180
Extracts the representative frame of an annotation as a numpy ndarray or PIL Image.
156181
157182
:param mmif: :py:class:`~mmif.serialize.mmif.Mmif` instance
158183
:param time_frame: :py:class:`~mmif.serialize.annotation.Annotation` instance that holds a time interval annotation (``"@type": ".../TimeFrame/..."``)
159184
:param as_PIL: return :py:class:`~PIL.Image.Image` instead of :py:class:`~numpy.ndarray`
185+
:param first_only: return the first representative frame only
160186
:return: frame as a :py:class:`numpy.ndarray` or :py:class:`PIL.Image.Image`
161187
"""
162188
video_document = mmif[time_frame.get_property('document')]
163-
rep_frame_num = get_representative_framenum(mmif, time_frame)
164-
return extract_frames_as_images(video_document, [rep_frame_num], as_PIL=as_PIL)[0]
189+
rep_frame_num = [get_representative_framenum(mmif, time_frame)] if first_only else get_representative_framenums(mmif, time_frame)
190+
return extract_frames_as_images(video_document, rep_frame_num, as_PIL=as_PIL)[0]
165191

166192

167193
def sample_frames(start_frame: int, end_frame: int, sample_rate: float = 1) -> List[int]:

tests/test_utils.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,10 @@ def test_extract_representative_frame(self):
5353
rep_frame_num = vdh.get_representative_framenum(self.mmif_obj, tf)
5454
expected_frame_num = vdh.millisecond_to_framenum(self.video_doc, tp.get_property('timePoint'))
5555
self.assertEqual(expected_frame_num, rep_frame_num)
56-
# check there is an error if no representatives
56+
# and should work even if no representatives are provided
5757
tf = self.a_view.new_annotation(AnnotationTypes.TimeFrame, start=1000, end=2000, timeUnit='milliseconds', document='d1')
58-
with pytest.raises(ValueError):
59-
vdh.get_representative_framenum(self.mmif_obj, tf)
60-
# check there is an error if there is a representative referencing a timepoint that
61-
# does not exist
58+
self.assertEqual(vdh.get_representative_framenum(self.mmif_obj, tf), vdh.get_mid_framenum(self.mmif_obj, tf))
59+
# check there is an error if there is a representative referencing a timepoint that does not exist
6260
tf.add_property('representatives', ['fake_tp_id'])
6361
with pytest.raises(ValueError):
6462
vdh.get_representative_framenum(self.mmif_obj, tf)

0 commit comments

Comments
 (0)