Skip to content

Commit

Permalink
Store libVLC stats in debug mode
Browse files Browse the repository at this point in the history
  • Loading branch information
k1o0 committed Nov 22, 2024
1 parent ec37eb1 commit 8f10ce9
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 4 deletions.
Empty file.
47 changes: 44 additions & 3 deletions iblrig_custom_tasks/_sp_passiveVideo/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import time
from pathlib import Path
from collections import defaultdict
from functools import partial
import logging
import warnings

Expand All @@ -27,6 +28,18 @@
'git+https://github.com/int-brain-lab/project_extraction.git"', RuntimeWarning)


class MediaStats(vlc.MediaStats):
"""A class to store media stats."""

def fieldnames(self):
"""Return the field names."""
return zip(*self._fields_)[0]

def as_tuple(self):
"""Return all attribute values as a tuple."""
return tuple(map(partial(getattr, self), self.fieldnames()))


class Player:
"""A VLC player."""
def __init__(self, rate=1):
Expand All @@ -35,6 +48,8 @@ def __init__(self, rate=1):
self._player.set_fullscreen(True)
self._player.set_rate(rate)
self._media = None
self._media_stats = MediaStats()
self._stats = []
self.events = defaultdict(list)
em = self._player.event_manager()
for event in (vlc.EventType.MediaPlayerPlaying, vlc.EventType.MediaPlayerEndReached):
Expand All @@ -46,6 +61,27 @@ def _record_event(self, event):
# Have to convert to str as object pointer may change
self.events[str(event.type).split('.')[-1]].append(time.time())

def update_media_stats(self):
"""Update media stats.
Returns
-------
bool
True if the stats have changed since the last update.
"""
if not vlc.libvlc_media_get_stats(self._player.get_media(), self._media_stats):
return False
stats = tuple((time.time(), *self._media_stats.as_tuple()))
if not any(self._stats) or stats[1:] != self._stats[-1][1:]:
self._stats.append(stats)
return True
return False

@property
def stats(self):
"""Return media stats."""
return pd.DataFrame(self._stats, columns=['time', *self._media_stats.fieldnames()])

def play(self, path):
"""Play a video.
Expand Down Expand Up @@ -112,8 +148,10 @@ def __init__(self, **kwargs):
if self.hardware_settings.get('MAIN_SYNC', False):
raise NotImplementedError('Recording frame2ttl on Bpod not yet implemented')
self.paths.DATA_FILE_PATH = self.paths.DATA_FILE_PATH.with_name('_sp_taskData.raw.pqt')
self.paths.STATS_FILE_PATH = self.paths.DATA_FILE_PATH.with_name('_sp_videoData.stats.pqt')
self.video = None
self.trial_num = -1
self._log_level = logging.getLevelNamesMapping()[kwargs.get('log_level', 'INFO')]
columns = ['intervals_0', 'intervals_1']
self.data = pd.DataFrame(pd.NA, index=range(self.task_params.NREPEATS), columns=columns)

Expand All @@ -122,10 +160,13 @@ def save(self):
if self.video:
data = pd.concat([self.data, pd.DataFrame.from_dict(self.video.events)], axis=1)
data.to_parquet(self.paths.DATA_FILE_PATH)
if 20 > self._log_level > 0:
stats = self.video.stats
stats.to_parquet(self.paths.STATS_FILE_PATH)
self.paths.SESSION_FOLDER.joinpath('transfer_me.flag').touch()

def start_hardware(self):
self.start_mixin_bpod() # used for protocol spacer only
self.start_mixin_bpod()
self.video = Player()

def next_trial(self):
Expand All @@ -150,15 +191,15 @@ def _set_bpod_out(self, val):

def _run(self):
"""This is the method that runs the video."""
# make the bpod send spacer signals to the main sync clock for protocol discovery
self.send_spacers()
for rep in range(self.task_params.NREPEATS): # Main loop
self.next_trial()
self._set_bpod_out(True)
# TODO c.f. MediaListPlayerPlayed event
while not self.video.is_started:
... # takes time to actually start playback
while self.video.is_playing or (end_time := self.video.get_ended_time(rep)) is None:
if 20 > self._log_level > 0:
self.video.update_media_stats()
time.sleep(0.05)
# trial finishes when playback finishes
self._set_bpod_out(False)
Expand Down
31 changes: 31 additions & 0 deletions iblrig_custom_tasks/_sp_passiveVideo/test_sp_videoPassive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import unittest
from unittest.mock import Mock
from iblrig_custom_tasks._sp_passiveVideo.task import Session, Player
from iblrig.test.base import TaskArgsMixin


class TestPassiveVideo(TaskArgsMixin, unittest.TestCase):

def setUp(self):
self.get_task_kwargs()

def test_next_trial(self):
self.assertRaises(NotImplementedError, Session, **self.task_kwargs)
self.task_kwargs['hardware_settings']['MAIN_SYNC'] = False
task = Session(log_level='DEBUG', **self.task_kwargs)
task.video = Mock(auto_spec=Player)
task.task_params.VIDEO = r'C:\Users\Work\Downloads\ONE\perlin-xyscale2-tscale50-comb08-5min.mp4'
task.task_params.VIDEO = r'C:\Users\Work\Downloads\SampleVideo_1280x720_1mb.mp4'
task.next_trial()
task.video.play.assert_called_once_with(task.task_params.VIDEO)
task.video.replay.assert_not_called()
task.video.reset_mock()
task.next_trial()
task.video.replay.assert_called_once()
# task.bpod = MagicMock()
# with patch.object(task, 'start_mixin_bpod'):
# task.run()


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "project_extraction"
version = "0.4.2"
version = "0.5.0"
description = "Custom extractors for satellite tasks"
dynamic = [ "readme" ]
keywords = [ "IBL", "neuro-science" ]
Expand Down

0 comments on commit 8f10ce9

Please sign in to comment.