Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/updating.md
Original file line number Diff line number Diff line change
Expand Up @@ -1625,7 +1625,7 @@ pages for more details on the input and output variable types.

* pre v4.0: NA
* post v4.0: frame_size = **pcv.visualize.time_lapse_video**(*img_list, out_filename='./time_lapse_video.mp4', fps=29.97, display=True*)
* post v5.0: deprecated.
* post v5.0: frame_size = **pcv.visualize.time_lapse_video**(*source, out_filename='./time_lapse_video.mp4', fps=29.97*)

#### plantcv.watershed_segmentation

Expand Down
36 changes: 36 additions & 0 deletions docs/visualize_time_lapse_video.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Automatically Generate a Time-Lapse Video given A Directory of Images

This function generates and saves the time-lapse video based on a list of paths to the images.

**plantcv.visualize.time_lapse_video**(*source, out_filename='./time_lapse_video.mp4', fps=29.97*)

**returns** frame_size

- **Parameters:**
- list_img - List of paths to the images to create the video or file path to a directory of images to use.
- out_filename - Name of file to save the generated video to.
- fps - Frame rate (frames per second) By default fps=29.97. Commonly used values: 23.98, 24, 25, 29.97, 30, 50, 59.94, 60

- **Context:**
- Used to generate time-lapse video given a list of images.

- **Example Use:**
- Below


```python
from plantcv import plantcv as pcv
# Note you will have to change this part on your own path
img_directory = './path_to_images_directory/'

frame_size = pcv.visualize.time_lapse_video(source=img_directory,
out_filename='./eg_time_lapse.mp4')
```

**Video generated**

The generated video should look similar to the one below:
<iframe src="https://player.vimeo.com/video/436453444" width="640" height="640" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>


**Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/master/plantcv/plantcv/visualize/time_lapse_video.py)
2 changes: 2 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ dependencies:
- flyr
- nbconvert
- nbformat
- imageio >= 2.28
- imageio-ffmpeg

channels:
- conda-forge
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ nav:
- 'Pixel Scatter Plot': 'visualize_pixel_scatter_vis.md'
- 'Pseudocolor': visualize_pseudocolor.md
- 'Tile': visualize_tile.md
- 'Time Lapse Video': visualize_time_lapse_video.md
- 'Watershed Segmentation': watershed.md
- 'White balance': white_balance.md
- 'Within Frame': within_frame.md
Expand Down
3 changes: 2 additions & 1 deletion plantcv/plantcv/visualize/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from plantcv.plantcv.visualize.pixel_scatter_vis import pixel_scatter_plot
from plantcv.plantcv.visualize.chlorophyll_fluorescence import chlorophyll_fluorescence
from plantcv.plantcv.visualize.tile import tile
from plantcv.plantcv.visualize.time_lapse_video import time_lapse_video

__all__ = ["pseudocolor", "colorize_masks", "histogram", "colorspaces", "auto_threshold_methods",
"overlay_two_imgs", "colorize_label_img", "obj_size_ecdf", "obj_sizes", "hyper_histogram",
"pixel_scatter_plot", "chlorophyll_fluorescence", "tile"]
"pixel_scatter_plot", "chlorophyll_fluorescence", "tile", "time_lapse_video"]
58 changes: 58 additions & 0 deletions plantcv/plantcv/visualize/time_lapse_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Create time-lapse videos with input directory of images

import os
import imageio.v3 as iio
import numpy as np
from plantcv.plantcv._globals import params
from plantcv.plantcv.transform.resize import resize
from plantcv.plantcv.io.read_dataset import read_dataset
from plantcv.plantcv.warn import warn


def time_lapse_video(source, out_filename='./time_lapse_video.mp4', fps=29.97):
"""Generate time-lapse video given a list of paths to the images

Parameters:
-----------
source = string or list,
Path to a folder of images to use or list of paths to the images to create the video
out_filename = string,
name of file to save the generated video to
fps = float,
frame rate (frames per second)

Returns:
--------
frame_size = tuple,
the frame size of the generated video
"""
params.debug = None
img_list = source
if isinstance(source, str):
img_list = read_dataset(source, sort=True)

imgs = []
list_r = []
list_c = []
for file in img_list:
img = iio.imread(file)
list_r.append(img.shape[0])
list_c.append(img.shape[1])
imgs.append(img)
max_c, max_r = np.max(list_c), np.max(list_r)

# use the largest size of the images as the frame size
# frame_size = frame_size or (max_c, max_r)
frame_size = (max_c, max_r)

if not (len(np.unique(list_r)) == 1 and len(np.unique(list_c)) == 1):
warn("The sizes of images are not the same, an image resizing (cropping or zero-padding) will be done "
f"to make all images the same size ({frame_size[0]}x{frame_size[1]}) before creating the video! ")

out_path, _ = os.path.splitext(out_filename)
out_filename = out_path + '.mp4'

frames = [resize(img, frame_size, interpolation=None) for img in imgs]
iio.imwrite(out_filename, frames, plugin="FFMPEG", fps=fps, codec="libx264")

return frame_size
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ dependencies = [
"nd2",
"flyr",
"nbconvert",
"nbformat"
"nbformat",
"imageio >= 2.28",
"imageio-ffmpeg"
]
requires-python = ">=3.8"
authors = [
Expand Down
56 changes: 56 additions & 0 deletions tests/plantcv/visualize/test_time_lapse_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import cv2
import numpy as np
from plantcv.plantcv.visualize.time_lapse_video import time_lapse_video


def test_plantcv_visualize_time_lapse_video_list_input(tmpdir):
"""Test for PlantCV."""
# Generate 3 test images and saved in tmpdir
list_im = []
for i in range(3):
temp_img = np.random.rand(3, 3)
min_, max_ = np.nanmin(temp_img), np.nanmax(temp_img)
temp_img = np.interp(temp_img, (min_, max_), (0, 255)).astype('uint8')
img_i_path = os.path.join(tmpdir, f"img{i}.png")
cv2.imwrite(img_i_path, temp_img)
list_im.append(img_i_path)

vid_name = os.path.join(tmpdir, 'test_time_lapse_video.mp4')
_ = time_lapse_video(source=list_im, out_filename=vid_name, fps=29.97)
assert os.path.exists(vid_name) and os.path.getsize(vid_name) > 100


def test_plantcv_visualize_time_lapse_video_str_input(tmpdir):
"""Test for PlantCV."""
# Generate 3 test images and saved in tmpdir
for i in range(3):
temp_img = np.random.rand(3, 3)
min_, max_ = np.nanmin(temp_img), np.nanmax(temp_img)
temp_img = np.interp(temp_img, (min_, max_), (0, 255)).astype('uint8')
img_i_path = os.path.join(tmpdir, f"img{i}.png")
cv2.imwrite(img_i_path, temp_img)

vid_name = os.path.join(tmpdir, 'test_time_lapse_video.mp4')
_ = time_lapse_video(source=str(tmpdir), out_filename=vid_name, fps=29.97)
assert os.path.exists(vid_name) and os.path.getsize(vid_name) > 100


# not all images have the same size (essential to generate a video)
def test_plantcv_visualize_time_lapse_video_different_img_sizes_warns(tmpdir, capsys):
"""Test for PlantCV."""
# Generate 3 test images of different size and save in tmpdir
list_im = []
for i in range(2):
temp_img = np.random.rand(i+2, 3)
min_, max_ = np.nanmin(temp_img), np.nanmax(temp_img)
temp_img = np.interp(temp_img, (min_, max_), (0, 255)).astype('uint8')
img_i_path = os.path.join(tmpdir, f"img{i}.png")
cv2.imwrite(img_i_path, temp_img)
list_im.append(img_i_path)

vid_name = os.path.join(tmpdir, 'test_time_lapse_video.mp4')
_ = time_lapse_video(source=list_im, out_filename=vid_name, fps=29.97)
_, err = capsys.readouterr()

assert "Warning" in err and os.path.exists(vid_name)