Source code for movies

"""
Module for generating movies from 2p images

(c) 2015 C. Schmidt-Hieber
GPLv3
"""

import os
import sys
if sys.version_info.major < 3:
    try:
        import subprocess32 as sp
    except ImportError:
        sys.stdout.write("Couldn't find subprocess32; using subprocess instead\n")
        import subprocess as sp
else:
    import subprocess as sp
import shlex
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from base64 import b64encode
try:
    import cv2
except:
    sys.stderr.write("cv2 module not found\n")
    HAS_CV2 = False
else:
    HAS_CV2 = True

FFMPEG = "ffmpeg"


[docs]def get_normbright(arr, mid=16.0): """ Create 3-point calibration curve. Returns 3 points of input brightness that will be mapped to 0, 0.5, and 1.0 output Parameters ---------- arr : numpy.ndarray Frame that will be used for calibration mid : float, optional Midpoint value scaling. Default: 16.0 Returns ------- normcurve : 3-tuple of floats 3 points of input brightness that will be mapped to 0, 0.5, and 1.0 output """ normtuple = ( float(np.min(arr))/2**16, mid*float(np.median(arr)-np.min(arr))/2**16, float(np.max(arr))/2**16) if normtuple[1] > normtuple[2]: normtuple = ( normtuple[0], normtuple[2]/2.0, normtuple[2]) if normtuple[0] > normtuple[1]: normtuple = ( 0, normtuple[1], normtuple[2]) return normtuple
[docs]def save_scale_bar(png_name, scale_length_int, scale_length_px, xpx, ypx): """ Save a png with transparent background showing a white scale bar with white label that can be added to a movie. Parameters ---------- png_name : str Full path to png file name scale_length_int : int Scale bar length in specimen dimensions (e.g. um) scale_length_px : int Scale bar length in pixels xpx : int Image width in pixels ypx : int Image height in pixels """ aa = 4 margin_x = xpx/50.0 scale_text = u"{0:0d} \u03BCm".format(int(scale_length_int)) im = Image.new("RGBA", (xpx*aa, ypx*aa)) draw = ImageDraw.Draw(im) draw.line([((xpx-margin_x-scale_length_px)*aa, (ypx*0.9)*aa), ((xpx-margin_x)*aa, (ypx*0.9)*aa)], width=int(ypx/125.0)*aa, fill="white") # get a font fnt = ImageFont.truetype( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/FreeSansBold.ttf'), 24*aa) w, h = draw.textsize(scale_text, font=fnt) draw.text(((xpx - margin_x - scale_length_px/2.0)*aa - w/2.0, (ypx*0.91)*aa), scale_text, fill="white", font=fnt) im = im.resize((xpx, ypx), Image.ANTIALIAS) im.save(png_name)
[docs]def make_movie(tiff_trunk, out_file, fps, normbright=None, scalebarframe=None, verbose=False, scale=None, crf=30, ffmpeg=FFMPEG): """ Produce a movie from a directory with individual tiffs at given frame rate Parameters ---------- tiff_trunk : str or np.ndarray File name filter (full path) used as input to ffmpeg (e.g. ``/home/cs/data/ChanA_0001_%04d.tif``); or movie as np.ndarray of shape (nframes, nx, ny). out_file : str Full path to movie file name fps : float Acquisition rate of tiffs in frames per second normbright : 3-tuple of floats, optional Brightness adjustment curve; see get_normbright() for format scalebarframe : str, optional Full path to png with scale bar verbose : bool, optional Print progress of ffmpeg scale : 2-tuple of ints, optional Rescale movie to given width and height in pixels. Default: None crf : int, optional crf value to be passed to ffmpeg. Default: 30 ffmpeg : str, optional Path to ffmpeg binary. Default: FFMPEG global variable Returns ------- html_movie : str An html tag containing the complete movie """ addin = "" if scale is not None or normbright is not None or \ scalebarframe is not None: sfilter = "-filter_complex " else: sfilter = "" prev = "[0:v]" if scale is not None: sfilter += prev + "scale={0}:{1}".format(scale[0], scale[1]) prev = "[scale];[scale]" if normbright is not None: if normbright[0] == normbright[1]: normbright1 = normbright[2]/2.0 else: normbright1 = normbright[1] sfilter += prev + "curves=all='{0}/0 {1}/0.5 {2}/1'".format( normbright[0], normbright1, normbright[2]) prev = "[bright];[bright]" if scalebarframe is not None: addin = "-i {0}".format(scalebarframe) sfilter += prev + "[1:v]overlay=0:0" tiff_input = tiff_trunk cmd = "{0} -y -r {1} ".format(ffmpeg, fps) if isinstance(tiff_input, str): if "?" in tiff_input: cmd += "-pattern_type glob " cmd += "-i {0} ".format(tiff_input) stdin = None else: assert(isinstance(tiff_input, np.ndarray)) assert(tiff_input.dtype == np.uint16 or tiff_input.dtype == np.int16 or tiff_input.dtype == np.int32) cmd += "-f image2pipe -i - " stdin = sp.PIPE cmd += "{0} {1} ".format( addin, sfilter) cmd += \ "-an -vcodec libx264 -preset slow -crf {0} -pix_fmt yuv420p ".format( crf) cmd += "-metadata author=\"(c) 2016 Christoph Schmidt-Hieber\" {0}".format( out_file) if os.name == 'nt': # Need to double-escape path separators on windows cmd = cmd.replace("\\", "\\\\") sys.stdout.write(cmd) cmd_split = shlex.split(cmd) sys.stdout.write("\nCreating movie...") sys.stdout.flush() if verbose: stdout = sp.PIPE else: stdout = None P = sp.Popen(cmd_split, stdout=stdout, stderr=sp.STDOUT, stdin=stdin) if isinstance(tiff_input, np.ndarray): for frame in tiff_input.squeeze(): im = Image.fromarray(frame.astype(np.int32)) im.save(P.stdin, 'PNG', compress_level=0, bits=32) P.stdin.close() if verbose: for line in iter(P.stdout.readline, b''): sys.stdout.write(line) sys.stdout.flush() P.wait() sys.stdout.write(" done\n") return html_movie(out_file)
[docs]def html_movie(fn): """ Converts an mp4 movie to an html tag containing the complete encoded movie Parameters ---------- fn : str Full path to movie file name Returns ------- html_movie : str An html tag containing the complete movie """ video = open(fn, "rb").read() video_encoded = b64encode(video) video_tag = \ '<video controls alt="test" src="data:video/mp4;base64,{0}">'.format( video_encoded) return video_tag
[docs]def numpy_movie(fn): """ Converts a movie to a numpy array Parameters ---------- fn : str Full path to movie file name Returns ------- array : numpy.ndarray Numpy array of shape (nframes, npixels_x, npixels_y) """ cap = cv2.VideoCapture(fn) np_movie = [] while True: ret, img = cap.read() if ret: np_movie.append(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) else: break cap.release() return np.array(np_movie)