Source code for moten.io

'''
'''
# Anwar O. Nunez-Elizalde (Jan, 2016)
#
# Updates:
#  Anwar O. Nunez-Elizalde (Apr, 2020)
#
#
from PIL import Image
import numpy as np

from moten.utils import iterator_func
from moten.colorspace import rgb2lab

ANTIALIAS = Image.Resampling.LANCZOS if hasattr(Image, "Resampling") else Image.ANTIALIAS


[docs] def video_buffer(video_file, nimages=np.inf): '''Generator for a video file. Yields individual uint8 images from a video file. The video is loaded into memory one frame at a time. Parameters ---------- video_file : str Full path to the video file. This can be a video file on disk or from a website. nimages : optional, int If specified, only `nimages` frames are loaded. Yields ------ video_frame : uint8 3D np.ndarray, (vdim, hdim, color) Each ``next()`` call yields an uint8 RGB frame from video. Example ------- >>> video_file = 'http://anwarnunez.github.io/downloads/avsnr150s24fps_tiny.mp4' >>> image_buffer = video_buffer(video_file, nimages=50) >>> movie = np.asarray([frame for frame in image_buffer]) >>> print(movie.shape) # (nimages, vdim, hdim, color) (50, 144, 256, 3) ''' import cv2 cap = cv2.VideoCapture(video_file) frameidx = 0 while True: if frameidx >= nimages: break flag, im = cap.read() frameidx += 1 if flag: yield im[...,::-1] # flip to RGB else: break
[docs] def generate_frames_from_greyvideo(video_file, size=None, nimages=np.inf): '''Yields one frame from the greyscale video file. Notes ----- The video is assumed to be greyscale. Parameters ---------- video_file : str Full path to the video file. This can be a video file on disk or from a website. size : optional, tuple (vdim, hdim) The desired output image size. If specified, the image is scaled or shrunk to this size. If not specified, the original size is kept. nimages : optional, int If specified, only `nimages` frames are loaded. Yields ------ greyscale_image : 2D np.ndarray, (vdim, hdim) Pixel values are in the 0-1 range. ''' vbuffer = video_buffer(video_file, nimages=nimages) grey_video = [] for imageidx, image_rgb in enumerate(vbuffer): if size is not None: image_rgb = resize_image(image_rgb, size=size) grey_image = image_rgb[...,0]/255.0 yield grey_image
[docs] def generate_frame_difference_from_greyvideo(video_file, size=None, nimages=np.inf, dtype='float32'): '''Generates the difference between the current frame and the previous frame. Notes ----- The video is assumed to be greyscale. Parameters ---------- video_file : str Full path to the video file. This can be a video file on disk or from a website. size : optional, tuple (vdim, hdim) The desired output image size. If specified, the image is scaled or shrunk to this size. If not specified, the original size is kept. nimages : optional, int If specified, only `nimages` frames are loaded. Yields ------ greyscale_image_difference : 2D np.ndarray, (vdim, hdim) The difference image: (current_frame - previous_frame). Pixel values are in the (-1, 1) range. ''' vbuffer = video_buffer(video_file, nimages=nimages) previous_frame = 0 for frame_index, image_rgb in enumerate(vbuffer): if size is not None: image_rgb = resize_image(image_rgb, size=size) current_image = np.asarray(image_rgb[...,0]/255.0, dtype=dtype) frame_difference = current_image - previous_frame previous_frame = current_image yield frame_difference
[docs] def video2luminance(video_file, size=None, nimages=np.inf): '''Convert the video frames to luminance images. Internally, this function loads one video frame into memory at a time. Tt converts the RGB pixel values from one frame to CIE-LAB pixel values. It then keeps the luminance channel only. This process is performed for all frames requested or until we reach the end of the video file. Parameters ---------- video_file : str Full path to the video file. This can be a video file on disk or from a website. size : optional, tuple (vdim, hdim) The desired output image size. If specified, the image is scaled or shrunk to this size. If not specified, the original size is kept. nimages : optional, int If specified, only `nimages` frames are converted to luminance. Returns ------- luminance_images : 3D np.ndarray, (nimages, vdim, hdim) Pixel values are in the 0-100 range. ''' vbuffer = video_buffer(video_file, nimages=nimages) luminance_video = [] for imageidx, image_rgb in iterator_func(enumerate(vbuffer), '%s.video2luminance'%__name__): luminance_image = imagearray2luminance(image_rgb, size=size).squeeze() luminance_video.append(luminance_image) return np.asarray(luminance_video)
[docs] def video2grey(video_file, size=None, nimages=np.inf): '''Convert the video frames to greyscale images. This function computes the mean across RGB color channels. Parameters ---------- video_file : str Full path to the video file. This can be a video file on disk or from a website. size : optional, tuple (vdim, hdim) The desired output image size. If specified, the image is scaled or shrunk to this size. If not specified, the original size is kept. nimages : optional, int If specified, only `nimages` frames are converted to greyscale. Returns ------- greyscale_images : 3D np.ndarray, (nimages, vdim, hdim) Pixel values are in the 0-1 range. ''' vbuffer = video_buffer(video_file, nimages=nimages) grey_video = [] for imageidx, image_rgb in iterator_func(enumerate(vbuffer), '%s.video2grey'%__name__): if size is not None: image_rgb = resize_image(image_rgb, size=size) grey_image = image_rgb.mean(-1)/255.0 grey_video.append(grey_image) return np.asarray(grey_video)
[docs] def imagearray2luminance(uint8arr, size=None, filter=ANTIALIAS, dtype=np.float64): '''Convert an array of uint8 RGB images to a luminance image Parameters ---------- uint8arr : 4D np.ndarray, (nimages, vdim, hdim, color) The uint8 RGB frames. size : optional, tuple (vdim, hdim) The desired output image size. filter: to be passed to PIL Returns ------- luminance_array : 3D np.ndarray, (nimages, vdim, hdim) The luminance image representation. Pixel values are in the 0-100 range. ''' from scipy import misc from moten.colorspace import rgb2lab if uint8arr.ndim == 3: # handle single image case uint8arr = np.asarray([uint8arr]) nimages, vdim, hdim, cdim = uint8arr.shape outshape = (nimages, vdim, hdim) if size is None \ else (nimages, size[0], size[1]) luminance = np.zeros(outshape, dtype=dtype) for imdx in range(nimages): im = uint8arr[imdx] if size is not None: im = Image.fromarray(im) im = resize_image(im, size=size, filter=filter) im = rgb2lab(im/255.)[...,0] luminance[imdx] = im return luminance
[docs] def resize_image(im, size=(96,96), filter=ANTIALIAS): '''Resize an image and return its array representation. Parameters ---------- im : str, np.ndarray(uint8), or PIL.Image object The path to the image, an image array, or a loaded PIL.Image. size : tuple, (vdim, hdim) The desired output image size. Returns ------- arr : uint8 np.array, (vdim, hdim, 3) The resized image array ''' if isinstance(im, str): im = Image.open(im) elif isinstance(im, np.ndarray): im = Image.fromarray(im) im.load() # flip to PIL.Image convention size = size[::-1] try: im = im._new(im.im.stretch(size, filter)) except AttributeError: # PIL 4.0.0 The stretch function on the core image object has been removed. # This used to be for enlarging the image, but has been aliased to resize recently. im = im._new(im.im.resize(size, filter)) im = np.asarray(im) return im
[docs] def load_image_luminance(image_files, hdim=None, vdim=None): '''Load a set of RGB images and return its luminance representation. Parameters ---------- image_files : list-like, (nimages,) A list of file names. The images should be in RGB uint8 format. vdim : int, optional hdim : int, optional Vertical and horizontal dimensions, respectively. If provided the images will be scaled to this size. Returns ------- arr : 3D np.array (nimages, vdim, hdim) The luminance representation of the images. ''' if (hdim and vdim): loader = lambda stim,sz: resize_image(stim,sz) else: loader = lambda stim,sz: np.asarray(stim) stimuli = [] for fdx, fl in iterator_func(enumerate(image_files), "load_image_luminance", total=len(image_files)): stimulus = Image.open(fl) stimulus = loader(stimulus,(vdim,hdim)) stimulus = rgb2lab(stimulus/255.)[...,0] stimuli.append(stimulus) return np.asarray(stimuli)
[docs] def apply_mask(mask, generator): ''' Parameters ---------- mask : 2D np.ndarray generator : generator Yields a video frame Yields ------ masked_image : 2D np.ndarray Masked image of each frame (i.e. ``original_image[mask]``) Examples -------- >>> import moten >>> video_file = 'http://anwarnunez.github.io/downloads/avsnr150s24fps_tiny.mp4' >>> small_size = (36, 64) # downsample to (vdim, hdim) 16:9 aspect ratio >>> oim = next(moten.io.generate_frame_difference_from_greyvideo(video_file, size=small_size)) >>> mask = np.zeros(small_size, dtype=np.bool) >>> mask[16:, :40] = True >>> nim = next(moten.io.apply_mask(mask, moten.io.generate_frame_difference_from_greyvideo(video_file, size=small_size))) >>> np.allclose(oim[16:, :40], nim) ''' assert mask.ndim == 2 vshape = np.unique(mask.sum(0)).max() hshape = np.unique(mask.sum(1)).max() shape = (vshape, hshape) print('mask size:', shape) for im in generator: yield im[mask].reshape(shape)