Skip to content

Commit 6001bde

Browse files
authored
Merge pull request #762 from danforthcenter/visualize_time_lapse_video
Create time lapse video (visualize sub package)
2 parents 0c8b614 + db10f72 commit 6001bde

10 files changed

Lines changed: 191 additions & 7 deletions

File tree

docs/updating.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,11 @@ pages for more details on the input and output variable types.
10661066
* pre v4.0: NA
10671067
* post v4.0: fig, ax = **pcv.visualize.pixel_scatter_plot**(*paths_to_imgs, x_channel, y_channel*)
10681068

1069+
#### plantcv.visualize.time_lapse_video
1070+
1071+
* pre v4.0: NA
1072+
* post v4.0: frame_size = **pcv.visualize.time_lapse_video**(*img_list, out_filename='./time_lapse_video.mp4', fps=29.97, display=True*)
1073+
10691074
#### plantcv.watershed_segmentation
10701075

10711076
* pre v3.0dev2: device, watershed_header, watershed_data, analysis_images = **plantcv.watershed_segmentation**(*device, img, mask, distance=10, filename=False, debug=None*)

docs/visualize_time_lapse_video.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
## Automatically Generate a Time-Lapse Video given A Directory of Images
2+
3+
This function generates and saves the time-lapse video based on a list of paths to the images.
4+
5+
**plantcv.visualize.time_lapse_video**(*img_list, out_filename='./time_lapse_video.mp4', fps=29.97, display=True*)
6+
7+
**returns** frame_size
8+
9+
- **Parameters:**
10+
- list_img - List of paths to the images to create the video.
11+
- out_filename - Name of file to save the generated video to.
12+
- 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
13+
- display - if True (default), displays the path to the generated video.
14+
15+
- **Context:**
16+
- Used to generate time-lapse video given a list of images.
17+
- List of image paths can be generated with [`plantcv.io.read_dataset`](io_read_dataset.md).
18+
19+
- **Example Use:**
20+
- Below
21+
22+
23+
```python
24+
from plantcv import plantcv as pcv
25+
# Note you will have to change this part on your own path
26+
img_directory = './path_to_images_directory/'
27+
img_paths_list = pcv.io.read_dataset(source_path=img_directory, sort=True)
28+
29+
fps = 29.97
30+
name_video = './eg_time_lapse'
31+
display = True
32+
33+
frame_size = pcv.visualize.time_lapse_video(img_list=img_paths_list,
34+
out_filename=name_video,
35+
fps=fps, display=display)
36+
```
37+
38+
**Video generated**
39+
40+
The generated video is saved automatically in the user-specified directory name_video. The user defined directory must already exist. The generated video should look similar to the one below:
41+
<iframe src="https://player.vimeo.com/video/436453444" width="640" height="640" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>
42+
43+
44+
**Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/master/plantcv/plantcv/visualize/time_lapse_video.py)

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ nav:
183183
- 'Object Sizes': visualize_obj_sizes.md
184184
- 'Pixel Scatter Plot': 'visualize_pixel_scatter_vis.md'
185185
- 'Pseudocolor': visualize_pseudocolor.md
186+
- 'Time Lapse Video': visualize_time_lapse_video.md
186187
- 'Watershed Segmentation': watershed.md
187188
- 'White balance': white_balance.md
188189
- 'Within Frame': within_frame.md

plantcv/plantcv/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
outputs = Outputs()
1313

1414
from plantcv.plantcv.deprecation_warning import deprecation_warning
15+
from plantcv.plantcv.warn import warn
1516
from plantcv.plantcv.print_image import print_image
1617
from plantcv.plantcv.plot_image import plot_image
1718
from plantcv.plantcv.color_palette import color_palette

plantcv/plantcv/deprecation_warning.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from plantcv.plantcv import _version
55
from plantcv.plantcv import params
66

7+
78
def deprecation_warning(warning):
89
"""Print out deprecation warning
910

plantcv/plantcv/visualize/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
from plantcv.plantcv.visualize.auto_threshold_methods import auto_threshold_methods
77
from plantcv.plantcv.visualize.overlay_two_imgs import overlay_two_imgs
88
from plantcv.plantcv.visualize.colorize_label_img import colorize_label_img
9+
from plantcv.plantcv.visualize.time_lapse_video import time_lapse_video
910
from plantcv.plantcv.visualize.obj_sizes import obj_sizes
1011
from plantcv.plantcv.visualize.obj_size_ecdf import obj_size_ecdf
1112
from plantcv.plantcv.visualize.hyper_histogram import hyper_histogram
1213
from plantcv.plantcv.visualize.pixel_scatter_vis import pixel_scatter_plot
1314

1415
__all__ = ["pseudocolor", "colorize_masks", "histogram", "clustered_contours", "colorspaces", "auto_threshold_methods",
1516
"overlay_two_imgs", "colorize_label_img", "obj_size_ecdf", "obj_sizes", "hyper_histogram",
16-
"pixel_scatter_plot"]
17+
"pixel_scatter_plot", "time_lapse_video"]

plantcv/plantcv/visualize/overlay_two_imgs.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
# Overlay two input images
22

3-
"""
4-
Created on Tue. September 01 21:00:01 2020
5-
A function
6-
@author: hudanyunsheng
7-
"""
8-
93
import os
104
import cv2
115
import numpy as np
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Create time-lapse videos with input directory of images
2+
3+
import os
4+
import cv2
5+
import numpy as np
6+
from plantcv.plantcv import params
7+
from plantcv.plantcv import fatal_error
8+
from plantcv.plantcv.transform import resize
9+
# import warnings
10+
from plantcv.plantcv import warn
11+
12+
13+
def time_lapse_video(img_list, out_filename='./time_lapse_video.mp4', fps=29.97, display=True):
14+
"""Generate time-lapse video given a list of paths to the images
15+
16+
Inputs:
17+
img_list = the desired list of paths to the images to create the video
18+
out_filename = name of file to save the generated video to
19+
fps = frame rate (frames per second)
20+
display = if True (default), displays the path to the generated video
21+
22+
Outputs:
23+
frame_size = the frame size of the generated video
24+
25+
:param img_list: list
26+
:param fps: float
27+
:param out_filename: string
28+
:param display: boolean
29+
:return frame_size: tuple
30+
"""
31+
# debug = params.debug
32+
params.debug = None
33+
34+
if len(img_list) <= 0:
35+
fatal_error("Image list is empty")
36+
37+
imgs = []
38+
list_r = []
39+
list_c = []
40+
for file in img_list:
41+
img = cv2.imread(file)
42+
if img is None:
43+
fatal_error(f"Unable to read {file}")
44+
list_r.append(img.shape[0])
45+
list_c.append(img.shape[1])
46+
imgs.append(img)
47+
max_c, max_r = np.max(list_c), np.max(list_r)
48+
49+
# use the largest size of the images as the frame size
50+
# frame_size = frame_size or (max_c, max_r)
51+
frame_size = (max_c, max_r)
52+
53+
if not (len(np.unique(list_r)) == 1 and len(np.unique(list_c)) == 1):
54+
warn("The sizes of images are not the same, an image resizing (cropping or zero-padding) will be done "
55+
f"to make all images the same size ({frame_size[0]}x{frame_size[1]}) before creating the video! ")
56+
57+
out_path, _ = os.path.splitext(out_filename)
58+
out_filename = out_path + '.mp4'
59+
60+
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # Be sure to use lower case
61+
out = cv2.VideoWriter(out_filename, fourcc, fps, frame_size)
62+
63+
for img in imgs:
64+
out.write(resize(img, frame_size, interpolation=None))
65+
out.release()
66+
cv2.destroyAllWindows()
67+
if display is True:
68+
print(f'Path to generated video: \n{out_filename}')
69+
70+
return frame_size

plantcv/plantcv/warn.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Warnings handling
2+
3+
import sys
4+
5+
6+
def warn(warning):
7+
"""Print out warning message
8+
9+
Inputs:
10+
warning = warning message text
11+
12+
:param warning: str
13+
"""
14+
print(f"Warning: {warning}", file=sys.stderr)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pytest
2+
import os
3+
import cv2
4+
import numpy as np
5+
6+
from plantcv.plantcv.visualize.time_lapse_video import time_lapse_video
7+
8+
@pytest.mark.parametrize("display",[False,True])
9+
def test_plantcv_visualize_time_lapse_video_passes(display, tmpdir):
10+
"""Test for PlantCV."""
11+
# Generate 3 test images and saved in tmpdir
12+
list_im = []
13+
for i in range(3):
14+
temp_img = np.random.rand(3,3)
15+
min_, max_ = np.nanmin(temp_img), np.nanmax(temp_img)
16+
temp_img = np.interp(temp_img, (min_, max_), (0, 255)).astype('uint8')
17+
img_i_path = os.path.join(tmpdir, f"img{i}.png")
18+
cv2.imwrite(img_i_path, temp_img)
19+
list_im.append(img_i_path)
20+
21+
# list_im = [os.path.join(tmpdir, img) for img in os.listdir(tmpdir) if img.endswith('.png')]
22+
23+
vid_name = os.path.join(tmpdir, 'test_time_lapse_video.mp4')
24+
_ = time_lapse_video(img_list=list_im, out_filename=vid_name, fps=29.97, display=display)
25+
assert os.path.exists(vid_name)
26+
27+
@pytest.mark.parametrize("list_im_f",
28+
[([]), # empty list
29+
(['./this_img_does_not_exist.png'])]) # non existent image
30+
def test_plantcv_visualize_time_lapse_video_errors(list_im_f, tmpdir):
31+
"""Test for PlantCV."""
32+
with pytest.raises(RuntimeError):
33+
_ = time_lapse_video(img_list=list_im_f, fps=29.97)
34+
35+
36+
# not all images have the same size (essential to generate a video)
37+
def test_plantcv_visualize_time_lapse_video_different_img_sizes_warns(tmpdir, capsys):
38+
"""Test for PlantCV."""
39+
# Generate 3 test images of different size and save in tmpdir
40+
list_im = []
41+
for i in range(2):
42+
temp_img = np.random.rand(i+2, 3)
43+
min_, max_ = np.nanmin(temp_img), np.nanmax(temp_img)
44+
temp_img = np.interp(temp_img, (min_, max_), (0, 255)).astype('uint8')
45+
img_i_path = os.path.join(tmpdir, f"img{i}.png")
46+
cv2.imwrite(img_i_path, temp_img)
47+
list_im.append(img_i_path)
48+
49+
vid_name = os.path.join(tmpdir, 'test_time_lapse_video.mp4')
50+
_ = time_lapse_video(img_list=list_im, out_filename=vid_name, fps=29.97, display=True)
51+
_, err = capsys.readouterr()
52+
53+
assert "Warning" in err and os.path.exists(vid_name)

0 commit comments

Comments
 (0)