Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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:**
- source - 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)