001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.vis.video; 034 035import java.awt.Dimension; 036import java.util.ArrayList; 037import java.util.List; 038 039import org.openimaj.image.MBFImage; 040import org.openimaj.image.colour.RGBColour; 041import org.openimaj.time.Timecode; 042import org.openimaj.video.Video; 043import org.openimaj.video.timecode.FrameNumberVideoTimecode; 044import org.openimaj.vis.AnimatedVisualisationListener; 045import org.openimaj.vis.AnimatedVisualisationProvider; 046import org.openimaj.vis.timeline.Timeline.TimelineMarker; 047import org.openimaj.vis.timeline.Timeline.TimelineMarkerType; 048import org.openimaj.vis.timeline.TimelineObjectAdapter; 049 050/** 051 * Displays a block, or bar, which represents the data. The block will be scaled 052 * to fit the JPanel in which its drawn. The block will contain a visImage of 053 * the data content. The visImage of the content is determined by one of the 054 * subclasses of this class. 055 * <p> 056 * This class will process the data in a separate thread. Obviously, it's not 057 * sensible to call this class with a "live" data stream, such as from a 058 * VideoCapture object. 059 * 060 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 061 * @created 3 Jul 2012 062 * @version $Author$, $Revision$, $Date$ 063 */ 064public abstract class VideoBarVisualisation extends TimelineObjectAdapter<Video<MBFImage>> 065 implements AnimatedVisualisationProvider 066{ 067 /** */ 068 private static final long serialVersionUID = 1L; 069 070 /** 071 * A marker for marking data frames within the data bar 072 * 073 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 074 * @created 6 Jul 2012 075 * @version $Author$, $Revision$, $Date$ 076 */ 077 public class VideoTimelineMarker extends TimelineMarker { 078 /** The frame number in the data */ 079 public int frameNumber = 0; 080 } 081 082 /** 083 * Process a particular frame of the data. The frame and timecode of the 084 * frame are provided. 085 * 086 * @param frame 087 * The frame to process 088 * @param t 089 * The timecode. 090 */ 091 public abstract void processFrame(MBFImage frame, Timecode t); 092 093 /** The background colour of the bar */ 094 protected Float[] barColour = new Float[] { 0.3f, 0.5f, 0.7f }; 095 096 /** Whether to also show the audio waveform. */ 097 private final boolean showAudio = false; 098 099 /** The height to plot the audio */ 100 protected final int audioHeight = 50; 101 102 /** Number of frames in the data in total */ 103 protected long nFrames = -1; 104 105 /** The marker that's used for processing progress */ 106 protected VideoTimelineMarker processingMarker = new VideoTimelineMarker(); 107 108 /** The frame being processed */ 109 protected int nFrame = 0; 110 111 /** Animated vis listeners */ 112 private final List<AnimatedVisualisationListener> listeners = new ArrayList<AnimatedVisualisationListener>(); 113 114 /** 115 * 116 * @param data 117 */ 118 protected VideoBarVisualisation(final Video<MBFImage> video) { 119 this.data = video; 120 121 this.nFrames = this.data.countFrames(); 122 this.setPreferredSize(new Dimension(1, 120 + (this.showAudio ? this.audioHeight : 0))); 123 } 124 125 /** 126 * Begin processing the data in a separate thread. The data will be reset 127 * after processing is complete. 128 */ 129 public void processVideo() { 130 new Thread(new Runnable() 131 { 132 @Override 133 public void run() 134 { 135 VideoBarVisualisation.this.processVideoThread(); 136 VideoBarVisualisation.this.data.reset(); 137 } 138 }).start(); 139 } 140 141 /** 142 * The processing method used in the processing thread. 143 */ 144 private void processVideoThread() 145 { 146 this.processingMarker = new VideoTimelineMarker(); 147 this.processingMarker.type = TimelineMarkerType.LABEL; 148 149 this.setRequiredSize( new Dimension( 150 this.pixelTransformer.calculatePosition( new double[]{0d,0d} )[0], 151 this.getRequiredSize().height ) ); 152 153 // Iterate through the data to get each frame. 154 this.nFrame = 0; 155 for (final MBFImage frame : this.data) 156 { 157 this.processingMarker.frameNumber = this.nFrame; 158 159 // Process the frame 160 this.processFrame(frame, new FrameNumberVideoTimecode( 161 this.nFrame, this.data.getFPS())); 162 this.nFrame++; 163// System.out.println( this.nFrame ); 164 165 this.fireAnimationEvent(); 166 } 167 168 this.processingMarker = null; 169 } 170 171 /** 172 * Fire an event to say a new vis update is available 173 */ 174 protected void fireAnimationEvent() 175 { 176 for( final AnimatedVisualisationListener l : this.listeners ) 177 l.newVisualisationAvailable( this ); 178 } 179 180 /** 181 * @return the barColour 182 */ 183 public Float[] getBarColour() { 184 return this.barColour; 185 } 186 187 /** 188 * @param barColour 189 * the barColour to set 190 */ 191 public void setBarColour(final Float[] barColour) { 192 this.barColour = barColour; 193 } 194 195 /** 196 * Return the data being shown by this bar. 197 * 198 * @return The data. 199 */ 200 public Video<MBFImage> getVideo() { 201 return this.data; 202 } 203 204 /** 205 * Returns the position of the given timecode at the scale of the current 206 * display. The position is given in pixels from the start of the bar. 207 * 208 * @param t 209 * the timecode for which to give the position. 210 * @return The position in pixels of the timecode. 211 */ 212 protected double getTimePosition(final Timecode t) 213 { 214 return this.pixelTransformer.calculatePosition( 215 new double[]{ t.getTimecodeInMilliseconds(), 0 } )[0]; 216 } 217 218 /** 219 * Returns the position of the given frame at the scale of the current 220 * display. The position is given in pixel from the start of the bar. 221 * 222 * @param nFrame 223 * The frame index 224 * @return The position in pixels of the frame. 225 */ 226 protected double getTimePosition(final int nFrame) 227 { 228 return this.pixelTransformer.calculatePosition( 229 new double[] { nFrame*1000/this.data.getFPS(), 0 } )[0]; 230 } 231 232 /** 233 * {@inheritDoc} 234 * 235 * @see org.openimaj.vis.timeline.TimelineObjectAdapter#getEndTimeMilliseconds() 236 */ 237 @Override 238 public long getEndTimeMilliseconds() { 239 return this.startTime + (long) (this.nFrames / this.data.getFPS() * 1000); 240 } 241 242 /** 243 * {@inheritDoc} 244 * @see org.openimaj.vis.AnimatedVisualisationProvider#addAnimatedVisualisationListener(org.openimaj.vis.AnimatedVisualisationListener) 245 */ 246 @Override 247 public void addAnimatedVisualisationListener( final AnimatedVisualisationListener avl ) 248 { 249 this.listeners.add( avl ); 250 } 251 252 /** 253 * {@inheritDoc} 254 * @see org.openimaj.vis.AnimatedVisualisationProvider#removeAnimatedVisualisationListener(org.openimaj.vis.AnimatedVisualisationListener) 255 */ 256 @Override 257 public void removeAnimatedVisualisationListener( final AnimatedVisualisationListener avl ) 258 { 259 this.listeners.remove( avl ); 260 } 261 262 /** 263 * If you override this method, you should call it at the end of your 264 * own implementation. 265 * 266 * {@inheritDoc} 267 * @see org.openimaj.vis.VisualisationImpl#update() 268 */ 269 @Override 270 public void update() 271 { 272 final MBFImage v = this.getVisualisationImage(); 273 final int x = (int)this.getTimePosition( this.processingMarker.frameNumber ); 274 v.drawLine( x, 0, x, v.getHeight(), 2, RGBColour.WHITE ); 275 } 276}