Video

In ByoTrack videos are expected to be Sequences of frames. Currently only 2D frames are supported but this may change in the future.

We use numpy to represent frames: each frame is a numpy array of shape (H, W, C). The data type can be floating or integer but most of the codes of ByoTrack will expect the frames to be normalized into [0, 1] and we strongly advise to normalize videos.

ByoTrack have its own Video object (byotrack.Video) that enables you to read, slice and normalize videos without loading the full video in RAM. In this notebok, we explain how to read, slice, normalize and visualize such Video object in ByoTrack.

NOTE: In ByoTrack, the Video object can always be directly replaced by a 4D array (T, H ,W, C) or a Sequence of array [(H, W, C), …].

[1]:
import matplotlib.pyplot as plt
import numpy as np

import byotrack
import byotrack.visualize

Loading videos

A Video can be loaded from a single file (typically mp4 or avi). We support standard format (All those supported by OpenCV) and TIFF stacks.

Loading videos from multiple files (one file by frame) is not supported yet. The reading of such files should be done manually and then provided to ByoTrack as a Sequence of frames

[2]:
# Loading a video from a file:

video = byotrack.Video("path/to/video.ext")

print("Video shape: T={}, H={}, W={}, C={}".format(*video.shape, video.channels))
Video shape: T=250, H=1024, W=1024, C=1
[3]:
# You can also load example videos provided by ByoTrack (See `byotrack.example_data`)
# Videos are downloaded in a user data folder and then read.

import byotrack.example_data

video = byotrack.example_data.hydra_neurons()

print("Video shape: T={}, H={}, W={}, C={}".format(*video.shape, video.channels))
Video shape: T=1000, H=848, W=1024, C=3
[4]:
# Let's see the frame shape and dtype

print(video[0].shape, video[0].dtype)
(848, 1024, 3) uint8
[5]:
# Display the first frame of a video.
# This may not work for uint16 videos where normalization should be apply before visualization

plt.figure(figsize=(24, 16), dpi=100)
plt.imshow(video[0])
plt.show()
../_images/run_examples_Video_6_0.png

Channel Selection / Normalization

ByoTrack provide some helpers to select and normalize channels in Video objects. It is done on the fly (when frames are required) and it never loads the full video at once.

If your video is not a byotrack.Video but a sequence of arrays (or directly a 4D array), you have to handle normalization on your own.

[6]:
# See the doc of the Video transformation configuration

byotrack.VideoTransformConfig?
[7]:
# Define the VideoTransformConfig

transform = byotrack.VideoTransformConfig(
    aggregate=True,  # Aggregate channels into a single one
    normalize=True,  # Normalize the video into [0, 1]
    selected_channel=None,  # None: Average channels, if int, it selects this channel
    q_min=0.02,  # We do not normalize using min and max but rather quantile of the intensity distribution
    q_max=0.999,  # It enforces q_min to go to 0.0 and q_max to go around 1.0 (depending on smooth_clip)
    smooth_clip=0.0,  # Log clipping smoothness for the values that are above q_max (0.0: hard clipping)
    compute_stats_on=10  # Number of frames to read to compute the quantiles. The larger the longer it takes.
)
[8]:
# Apply the transformation. It may take quite a long time to compute the quantiles.

video.set_transform(transform)
[9]:
# Let's see the frame shape and dtype. Notice that channel dimension is kept.

print(video[0].shape, video[0].dtype)
(848, 1024, 1) float64

Temporal and spatial slicing

Video objects allows you to slice temporally and spatially the video. Slicing is data intensive, it just creates a new view on the data without modifying it (it does not even load the data).

[10]:
# Check the length of the video (number of frames)

len(video)
[10]:
1000
[11]:
# Byotrack supports any temporal slicing

# For instance, we slice the first axis (time) using a negative step (the video will be loaded in the reverse order)
# from frame 50 to 0. (51 frames)

len(video[50::-1])
[11]:
51
[12]:
# You can also add any positional slicing on the height/width to extract a constant square ROI on the video

# Let's take the frames from 150 to 250 and centered on the middle of the animal

v = video[150:250, 200:-200, 200:-200]
v.shape  # 100 frames of shape (448, 624)
[12]:
(100, 448, 624)
[13]:
# Display the first frame of this sliced video

plt.figure(figsize=(24, 16), dpi=100)
plt.imshow(v[0])
plt.show()
../_images/run_examples_Video_16_0.png
[14]:
# Compare with frame 150 of the original video

(v[0] == video[150][200:-200,200:-200]).all()
[14]:
True

Visualization

We provide an interactive visualization code to go through video, detections and tracks. It was developped using open-cv and tested on Linux. Depending on the backend opencv uses, it may have different functionnalities (zooming, screenshots, …)

[15]:
# Display the video with opencv
# Use w/x to move forward in time (or space to run/pause the video)
# Use v to switch on/off the display of the video

byotrack.visualize.InteractiveVisualizer(video).run()
[16]:
# You can display a sliced video
# First focus on the 300 first frames, then go backward in time (5 frames at a time) and flip the vertical axis

byotrack.visualize.InteractiveVisualizer(video[:300][::-5, ::-1]).run()