Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from __future__ import print_function 

2from builtins import str 

3# cumulative one dimensional movie slicer 

4import os 

5import warnings 

6import subprocess 

7from subprocess import CalledProcessError 

8import numpy as np 

9import matplotlib.pyplot as plt 

10from functools import wraps 

11 

12from lsst.sims.maf.utils import percentileClipping, optimalBins 

13from lsst.sims.maf.stackers import ColInfo 

14from .baseSlicer import BaseSlicer 

15 

16__all__ = ['MovieSlicer'] 

17 

18class MovieSlicer(BaseSlicer): 

19 """movie Slicer.""" 

20 def __init__(self, sliceColName=None, sliceColUnits=None, 

21 bins=None, binMin=None, binMax=None, binsize=None, 

22 verbose=True, badval=0, cumulative=True, forceNoFfmpeg=False): 

23 """ 

24 The 'movieSlicer' acts similarly to the OneDSlicer (slices on one data column). 

25 However, the data slices from the movieSlicer are intended to be fed to another slicer, which then 

26 (together with a set of Metrics) calculates metric values + plots at each slice created by the movieSlicer. 

27 The job of the movieSlicer is to track those slices and put them together into a movie. 

28 

29 'sliceColName' is the name of the data column to use for slicing. 

30 'sliceColUnits' lets the user set the units (for plotting purposes) of the slice column. 

31 'bins' can be a numpy array with the binpoints for sliceCol or a single integer value 

32 (if a single value, this will be used as the number of bins, together with data min/max or binMin/Max), 

33 as in numpy's histogram function. 

34 If 'binsize' is used, this will override the bins value and will be used together with the data min/max 

35 or binMin/Max to set the binpoint values. 

36 

37 Bins work like numpy histogram bins: the last 'bin' value is end value of last bin; 

38 all bins except for last bin are half-open ([a, b>), the last one is ([a, b]). 

39 

40 The movieSlicer stitches individual frames together into a movie using ffmpeg. Thus, on 

41 instantiation it checks that ffmpeg is available and will raise and exception if not. 

42 This behavior can be overriden using forceNoFfmpeg = True (in order to create a movie later perhaps). 

43 """ 

44 # Check for ffmpeg. 

45 if not forceNoFfmpeg: 

46 try: 

47 fnull = open(os.devnull, 'w') 

48 subprocess.check_call(['which', 'ffmpeg'], stdout=fnull) 

49 except CalledProcessError: 

50 raise Exception('Could not find ffmpeg on the system, so will not be able to create movie.' 

51 ' Use forceNoFfmpeg=True to override this error and create individual images.') 

52 super(MovieSlicer, self).__init__(verbose=verbose, badval=badval) 

53 self.sliceColName = sliceColName 

54 self.columnsNeeded = [sliceColName] 

55 self.bins = bins 

56 self.binMin = binMin 

57 self.binMax = binMax 

58 self.binsize = binsize 

59 self.cumulative = cumulative 

60 if sliceColUnits is None: 

61 co = ColInfo() 

62 self.sliceColUnits = co.getUnits(self.sliceColName) 

63 self.slicer_init = {'sliceColName':self.sliceColName, 'sliceColUnits':sliceColUnits, 

64 'badval':badval} 

65 

66 def setupSlicer(self, simData, maps=None): 

67 """ 

68 Set up bins in slicer. 

69 """ 

70 if self.sliceColName is None: 

71 raise Exception('sliceColName was not defined when slicer instantiated.') 

72 sliceCol = simData[self.sliceColName] 

73 # Set bin min/max values. 

74 if self.binMin is None: 

75 self.binMin = sliceCol.min() 

76 if self.binMax is None: 

77 self.binMax = sliceCol.max() 

78 # Give warning if binMin = binMax, and do something at least slightly reasonable. 

79 if self.binMin == self.binMax: 

80 warnings.warn('binMin = binMax (maybe your data is single-valued?). ' 

81 'Increasing binMax by 1 (or 2*binsize, if binsize set).') 

82 if self.binsize is not None: 

83 self.binMax = self.binMax + 2 * self.binsize 

84 else: 

85 self.binMax = self.binMax + 1 

86 # Set bins. 

87 # Using binsize. 

88 if self.binsize is not None: 

89 if self.bins is not None: 

90 warnings.warn('Both binsize and bins have been set; Using binsize %f only.' %(self.binsize)) 

91 self.bins = np.arange(self.binMin, self.binMax+self.binsize/2.0, float(self.binsize), 'float') 

92 # Using bins value. 

93 else: 

94 # Bins was a sequence (np array or list) 

95 if hasattr(self.bins, '__iter__'): 

96 self.bins = np.sort(self.bins) 

97 # Or bins was a single value. 

98 else: 

99 if self.bins is None: 

100 self.bins = optimalBins(sliceCol, self.binMin, self.binMax) 

101 nbins = int(self.bins) 

102 self.binsize = (self.binMax - self.binMin) / float(nbins) 

103 self.bins = np.arange(self.binMin, self.binMax+self.binsize/2.0, self.binsize, 'float') 

104 # Set nbins to be one less than # of bins because last binvalue is RH edge only 

105 self.nslice = len(self.bins) - 1 

106 # Set slicePoint metadata. 

107 self.slicePoints['sid'] = np.arange(self.nslice) 

108 self.slicePoints['bins'] = self.bins 

109 # Add metadata from maps. 

110 self._runMaps(maps) 

111 # Set up data slicing. 

112 self.simIdxs = np.argsort(simData[self.sliceColName]) 

113 simFieldsSorted = np.sort(simData[self.sliceColName]) 

114 # "left" values are location where simdata == bin value 

115 self.left = np.searchsorted(simFieldsSorted, self.bins[:-1], 'left') 

116 self.left = np.concatenate((self.left, np.array([len(self.simIdxs),]))) 

117 # Set up _sliceSimData method for this class. 

118 if self.cumulative: 

119 @wraps(self._sliceSimData) 

120 def _sliceSimData(islice): 

121 """ 

122 Slice simData on oneD sliceCol, to return relevant indexes for slicepoint. 

123 """ 

124 #this is the important part. The ids here define the pieces of data that get 

125 #passed on to subsequent slicers 

126 #cumulative version of 1D slicing 

127 idxs = self.simIdxs[0:self.left[islice+1]] 

128 return {'idxs':idxs, 

129 'slicePoint':{'sid':islice, 'binLeft':self.bins[0], 'binRight':self.bins[islice+1]}} 

130 setattr(self, '_sliceSimData', _sliceSimData) 

131 else: 

132 @wraps(self._sliceSimData) 

133 def _sliceSimData(islice): 

134 """ 

135 Slice simData on oneD sliceCol, to return relevant indexes for slicepoint. 

136 """ 

137 idxs = self.simIdxs[self.left[islice]:self.left[islice+1]] 

138 return {'idxs':idxs, 

139 'slicePoint':{'sid':islice, 'binLeft':self.bins[islice], 'binRight':self.bins[islice+1]}} 

140 setattr(self, '_sliceSimData', _sliceSimData) 

141 

142 def __eq__(self, otherSlicer): 

143 """ 

144 Evaluate if slicers are equivalent. 

145 """ 

146 if isinstance(otherSlicer, MovieSlicer): 

147 return np.all(otherSlicer.slicePoints['bins'] == self.slicePoints['bins']) 

148 else: 

149 return False 

150 

151 def makeMovie(self, outfileroot, sliceformat, plotType, figformat, outDir='Output', ips=10.0, fps=10.0): 

152 """ 

153 Takes in metric and slicer metadata and calls ffmpeg to stitch together output files. 

154 """ 

155 if not os.path.isdir(outDir): 

156 raise Exception('Cannot find output directory %s with movie input files.' %(outDir)) 

157 #make video 

158 # ffmpeg -r 30 -i [image names - FilterColors_SkyMap_%03d.png] -r 30 

159 # -pix_fmt yuv420p -crf 18 -preset slower outfile 

160 callList = ['ffmpeg', '-r', str(ips), '-i', 

161 os.path.join(outDir,'%s_%s_%s.%s'%(outfileroot, sliceformat, plotType, figformat)), 

162 '-r', str(fps), '-pix_fmt', 'yuv420p', '-crf', '18', '-preset', 'slower', 

163 os.path.join(outDir,'%s_%s_%s_%s.mp4' %(outfileroot, plotType, str(ips), str(fps)))] 

164 print('Attempting to call ffmpeg with:') 

165 print(' '.join(callList)) 

166 p = subprocess.check_call(callList) 

167 #make thumbnail gif 

168 callList = ['ffmpeg','-i',os.path.join(outDir,'%s_%s_%s_%s.mp4' %(outfileroot, plotType, str(ips), str(fps))), 

169 '-vf', 'scale=%s:%s' %(str(320),str(-1)), '-t', str(10), '-r', str(10), 

170 os.path.join(outDir,'%s_%s_%s_%s.gif' %(outfileroot, plotType, str(ips), str(fps)))] 

171 print('converting to animated gif with:') 

172 print(' '.join(callList)) 

173 p2 = subprocess.check_call(callList)