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}