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.demos.faces;
034
035import java.awt.Color;
036import java.awt.Dimension;
037import java.awt.Graphics;
038import java.awt.GridBagConstraints;
039import java.awt.GridBagLayout;
040import java.awt.GridLayout;
041import java.awt.Insets;
042import java.awt.event.ActionEvent;
043import java.awt.event.ActionListener;
044import java.io.IOException;
045import java.util.ArrayList;
046import java.util.HashMap;
047import java.util.Iterator;
048import java.util.List;
049import java.util.Map;
050
051import javax.swing.AbstractButton;
052import javax.swing.BorderFactory;
053import javax.swing.ImageIcon;
054import javax.swing.JButton;
055import javax.swing.JFileChooser;
056import javax.swing.JFrame;
057import javax.swing.JOptionPane;
058import javax.swing.JPanel;
059import javax.swing.JSlider;
060import javax.swing.JToggleButton;
061import javax.swing.event.ChangeEvent;
062import javax.swing.event.ChangeListener;
063
064import org.openimaj.demos.Demo;
065import org.openimaj.image.ImageUtilities;
066import org.openimaj.image.MBFImage;
067import org.openimaj.image.colour.RGBColour;
068import org.openimaj.image.processing.face.tracking.clm.CLMFaceTracker;
069import org.openimaj.image.processing.face.tracking.clm.MultiTracker.TrackedFace;
070import org.openimaj.math.geometry.shape.Rectangle;
071import org.openimaj.video.Video;
072import org.openimaj.video.VideoDisplay;
073import org.openimaj.video.VideoDisplay.Mode;
074import org.openimaj.video.VideoDisplayListener;
075import org.openimaj.video.capture.VideoCapture;
076import org.openimaj.video.processing.shotdetector.HistogramVideoShotDetector;
077import org.openimaj.video.xuggle.XuggleVideo;
078
079/**
080 * Provides a user interface for interacting with the parameters of the trained
081 * CLM Model.
082 * 
083 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
084 * @created 9 Jul 2012
085 * @version $Author$, $Revision$, $Date$
086 */
087@Demo(
088                author = "David Dupplaw",
089                description = "Interface for playing with the parameters of a " +
090                                "trained Constrained Local Model (CLM) based facial expression tracker",
091                keywords = { "Constrained Local Model", "Point Distribution Model", "face", "webcam" },
092                title = "CLM Face Model Playground")
093public class ModelManipulatorGUI extends JPanel {
094        private static final long serialVersionUID = -3302684225273947693L;
095
096        /**
097         * Provides a panel which draws a particular model to itself.
098         * 
099         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
100         * @created 9 Jul 2012
101         * @version $Author$, $Revision$, $Date$
102         */
103        public class ModelView extends JPanel {
104                /** */
105                private static final long serialVersionUID = 1L;
106
107                /** The image of the model */
108                private final MBFImage vis = new MBFImage(600, 600, 3);
109
110                /** The face being drawn */
111                private TrackedFace face = null;
112
113                /** Reference triangles */
114                private final int[][] triangles;
115
116                /** Reference connections */
117                private final int[][] connections;
118
119                /** Colour to draw the connections */
120                private final Float[] connectionColour = RGBColour.WHITE;
121
122                /** Colour to draw the points */
123                private final Float[] pointColour = RGBColour.RED;
124
125                /** Colour to draw the mesh */
126                private final Float[] meshColour = new Float[] { 0.3f, 0.3f, 0.3f };
127
128                /** Colour to draw the bounding box */
129                private final Float[] boundingBoxColour = RGBColour.RED;
130
131                /** Whether to show mesh */
132                private final boolean showMesh = true;
133
134                /** Whether to show connections */
135                private final boolean showConnections = true;
136
137                /**
138                 * Constructor
139                 */
140                public ModelView() {
141                        this.setPreferredSize(new Dimension(600, 600));
142
143                        final CLMFaceTracker t = new CLMFaceTracker();
144                        this.triangles = t.getReferenceTriangles();
145                        this.connections = t.getReferenceConnections();
146                        this.face = new TrackedFace(
147                                        new Rectangle(50, -50, 500, 500),
148                                        t.getInitialVars());
149                        t.initialiseFaceModel(face);
150
151                        // Centre the face in the view
152                        setGlobalParam(0, 10);
153                        setGlobalParam(4, 300);
154                        setGlobalParam(5, 300);
155
156                        setBackground(new Color(60, 60, 60));
157                }
158
159                /**
160                 * {@inheritDoc}
161                 * 
162                 * @see javax.swing.JComponent#paint(java.awt.Graphics)
163                 */
164                @Override
165                public void paint(Graphics g) {
166                        super.paint(g);
167
168                        vis.zero();
169
170                        // Draw the model to the image.
171                        CLMFaceTracker.drawFaceModel(vis, face, showMesh, showConnections,
172                                        true, true, true, triangles, connections, 1, boundingBoxColour,
173                                        meshColour, connectionColour, pointColour);
174
175                        // Draw the image to the panel
176                        g.drawImage(ImageUtilities.createBufferedImage(vis), 0, 0, null);
177                }
178
179                /**
180                 * Number of global parameters
181                 * 
182                 * @return The number of global parameters
183                 */
184                public int getNumGlobalParams() {
185                        return face.clm._pglobl.getRowDimension();
186                }
187
188                /**
189                 * Get the value of the given global parameter.
190                 * 
191                 * @param indx
192                 *            The index to get
193                 * @return The value
194                 */
195                public double getGlobalParam(int indx) {
196                        return face.clm._pglobl.get(indx, 0);
197                }
198
199                /**
200                 * Set the given index to the value in the global params and update the
201                 * face model.
202                 * 
203                 * @param indx
204                 *            The index
205                 * @param val
206                 *            The new value
207                 */
208                public void setGlobalParam(int indx, double val) {
209                        // Set the parameter
210                        face.clm._pglobl.set(indx, 0, val);
211
212                        // Recalculate the shape.
213                        face.clm._pdm.calcShape2D(face.shape,
214                                        face.clm._plocal, face.clm._pglobl);
215
216                        repaint();
217                }
218
219                /**
220                 * Number of local parameters
221                 * 
222                 * @return The number of local parameters
223                 */
224                public int getNumLocalParams() {
225                        return face.clm._plocal.getRowDimension();
226                }
227
228                /**
229                 * Get the value of the given local parameter.
230                 * 
231                 * @param indx
232                 *            The index to get
233                 * @return The value
234                 */
235                public double getLocalParam(int indx) {
236                        return face.clm._plocal.get(indx, 0);
237                }
238
239                /**
240                 * Set the given index to the value in the local params and update the
241                 * face model.
242                 * 
243                 * @param indx
244                 *            The index
245                 * @param val
246                 *            The new value
247                 */
248                public void setLocalParam(int indx, double val) {
249                        // Set the parameter
250                        face.clm._plocal.set(indx, 0, val);
251
252                        // Recalculate the shape.
253                        face.clm._pdm.calcShape2D(face.shape,
254                                        face.clm._plocal, face.clm._pglobl);
255
256                        repaint();
257                }
258        }
259
260        /**
261         * A class that provides a display of the information that the tracker is
262         * tracking.
263         * 
264         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
265         * @created 17 Jul 2012
266         * @version $Author$, $Revision$, $Date$
267         */
268        protected class TrackerInfo extends JPanel {
269                /** */
270                private static final long serialVersionUID = 1L;
271
272                /** The list of faces being tracked */
273                private final JPanel faceList = new JPanel();
274
275                /** Map */
276                private final Map<TrackedFace, AbstractButton> map =
277                                new HashMap<TrackedFace, AbstractButton>();
278
279                /** Only allow one face to be tracked */
280                private final ButtonGroup faceGroup = new ButtonGroup();
281
282                /**
283                 * Default constructor
284                 */
285                public TrackerInfo() {
286                        super.setLayout(new GridBagLayout());
287                        super.setPreferredSize(new Dimension(600, 300));
288                        super.setSize(600, 300);
289                        init();
290                }
291
292                /**
293                 * Initialises the widgets.
294                 */
295                private void init() {
296                        final GridBagConstraints gbc = new GridBagConstraints();
297                        gbc.gridx = gbc.gridy = 1;
298                        gbc.weightx = gbc.weighty = 1;
299                        gbc.fill = GridBagConstraints.BOTH;
300
301                        // Add the list of faces
302                        faceList.setLayout(new GridLayout(-1, 1));
303                        faceList.setBackground(Color.black);
304                        this.add(faceList, gbc);
305
306                        // Add a button to force redetection
307                        final JButton b = new JButton("Force Redetection");
308                        b.addActionListener(new ActionListener()
309                        {
310                                @Override
311                                public void actionPerformed(ActionEvent e)
312                                {
313                                        needRedetect = true;
314                                }
315                        });
316                        gbc.gridy++;
317                        gbc.weighty = 0;
318                        this.add(b, gbc);
319                }
320
321                /**
322                 * Set the list of faces being tracked.
323                 * 
324                 * @param faces
325                 *            The face list
326                 */
327                public void setFaceList(List<TrackedFace> faces) {
328                        final ArrayList<TrackedFace> toRemove = new ArrayList<TrackedFace>();
329                        toRemove.addAll(map.keySet());
330
331                        // Add new faces
332                        for (final TrackedFace face : faces) {
333                                if (!map.keySet().contains(face)) {
334                                        // Add the face to the list as a toggle button
335                                        final JToggleButton b = new JToggleButton(face.toString(),
336                                                        new ImageIcon(ImageUtilities.createBufferedImage(
337                                                                        face.templateImage)));
338
339                                        // Store the map from the face to the button
340                                        map.put(face, b);
341
342                                        // Add the button to the panel
343                                        faceGroup.add(b);
344                                        faceList.add(b);
345                                        faceList.revalidate();
346                                }
347
348                                // Either the face is new or it's existing, so we
349                                // don't want to remove it - so we remove it from the
350                                // 'to remove' list
351                                toRemove.remove(face);
352                        }
353
354                        // Remove all the faces that have disappeared.
355                        for (final TrackedFace face : toRemove) {
356                                faceList.remove(map.get(face));
357                                faceGroup.remove(map.get(face));
358                                map.remove(face);
359                        }
360
361                        // If nothing's selected, select the first one.
362                        if (faceGroup.getSelected() == null && map.keySet().size() > 0)
363                                faceGroup.setSelected(map.values().iterator().next());
364                }
365
366                /**
367                 * Returns the face that is selected.
368                 * 
369                 * @return The selected face
370                 */
371                public TrackedFace getSelectedFace() {
372                        final Iterator<TrackedFace> faces = map.keySet().iterator();
373                        TrackedFace f = null;
374                        while (faces.hasNext())
375                                if (map.get(f = faces.next()) == faceGroup.getSelected())
376                                        return f;
377                        return null;
378                }
379        }
380
381        /**
382         * A replacement for the AWT ButtonGroup class.
383         * 
384         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
385         * @created 17 Jul 2012
386         * @version $Author$, $Revision$, $Date$
387         */
388        protected class ButtonGroup {
389                /** The buttons */
390                private final List<AbstractButton> buttons = new ArrayList<AbstractButton>();
391
392                /**
393                 * Add a button
394                 * 
395                 * @param b
396                 */
397                public void add(final AbstractButton b) {
398                        this.buttons.add(b);
399                        b.addActionListener(new ActionListener()
400                        {
401                                @Override
402                                public void actionPerformed(ActionEvent e)
403                                {
404                                        updateButtons(b);
405                                }
406                        });
407                }
408
409                /**
410                 * Remove the given button from the group.
411                 * 
412                 * @param b
413                 *            The button to remove
414                 */
415                public void remove(final AbstractButton b) {
416                        this.buttons.remove(b);
417                }
418
419                /**
420                 * Make sure only the given button is selected.
421                 * 
422                 * @param b
423                 *            The button to select.
424                 */
425                private void updateButtons(AbstractButton b) {
426                        for (final AbstractButton button : buttons)
427                                button.setSelected(button == b);
428                }
429
430                /**
431                 * Returns the selected button in the group.
432                 * 
433                 * @return The selected button in the group or null if no buttons are
434                 *         selected.
435                 */
436                public AbstractButton getSelected() {
437                        for (final AbstractButton button : buttons)
438                                if (button.isSelected())
439                                        return button;
440                        return null;
441                }
442
443                /**
444                 * Sets all buttons in the group to unselected.
445                 */
446                public void selectNone() {
447                        for (final AbstractButton button : buttons)
448                                button.setSelected(false);
449                }
450
451                /**
452                 * Set the selected button to the given one. Note that this method will
453                 * select the button whether or not the button is in the button group.
454                 * 
455                 * @param b
456                 *            The button to select
457                 */
458                public void setSelected(AbstractButton b) {
459                        b.setSelected(true);
460                        updateButtons(b);
461                }
462        }
463
464        /** The view of the model */
465        private ModelView modelView = null;
466
467        /** The video view */
468        private JFrame videoFrame = null;
469
470        /** The tracker info */
471        private TrackerInfo trackerInfo = null;
472
473        /** The video displayer */
474        private VideoDisplay<MBFImage> videoDisplay = null;
475
476        /** The tracker used to track faces in the videos */
477        private final CLMFaceTracker tracker = new CLMFaceTracker();
478
479        /** Shot detector used to force redetects on shot changes */
480        // Note that the fps isn't used, so we just give 25 as anything will do
481        private final HistogramVideoShotDetector shotDetector = new HistogramVideoShotDetector(25);
482
483        /** The global sliders */
484        private final List<JSlider> globalSliders = new ArrayList<JSlider>();
485
486        /** The local sliders */
487        private final List<JSlider> localSliders = new ArrayList<JSlider>();
488
489        // -------------------------------------------------------
490        // Note that all the slider values need to be 1,000 times
491        // the size of the actual value as the sliders only work
492        // in integer, so we divide the slider value by 1000 to get
493        // the actual value.
494        // -------------------------------------------------------
495
496        /** Tool tip label text for each of the global sliders */
497        private final String[] globalLabels = new String[] {
498                        "Scale", "X Rotation", "Y Rotation", "Z Rotation",
499                        "Translate X", "Translate Y"
500        };
501
502        /** Tool tip label text for each of the local sliders */
503        private final String[] localLabels = new String[] {
504        };
505
506        /** Maximum values for each of the global sliders */
507        private final int[] globalMaxs = new int[] {
508                        20000, (int) (Math.PI * 2000), (int) (Math.PI * 2000),
509                        (int) (Math.PI * 2000), 1000000, 1000000
510        };
511
512        /** Minimum values for each of the global sliders */
513        private final int[] globalMins = new int[] {
514                        0, -(int) (Math.PI * 2000), -(int) (Math.PI * 2000),
515                        -(int) (Math.PI * 2000), 0, 0
516        };
517
518        /** Maximum values for each of the local sliders */
519        private final int[] localMaxs = new int[] {
520        };
521
522        /** Minimum values for each of the local sliders */
523        private final int[] localMins = new int[] {
524        };
525
526        /** Whether to force redetect on next track */
527        private boolean needRedetect = false;
528
529        /**
530         * Default constructor
531         */
532        public ModelManipulatorGUI() {
533                init();
534        }
535
536        /**
537         * Display (or hide) the video frame, creating it if necessary.
538         * 
539         * @param showNotHide
540         *            TRUE for show, FALSE for hide
541         */
542        private void displayVideoFrame(Video<MBFImage> video, boolean showNotHide) {
543                System.out.println("displayVideoFrame( " + video + ", " + showNotHide + " )");
544
545                // If the button was selected...
546                if (showNotHide) {
547                        // ..and we don't yet have a video frame
548                        if (videoFrame == null) {
549                                videoFrame = new JFrame();
550                                videoDisplay = VideoDisplay.createVideoDisplay(video, videoFrame);
551                                videoDisplay.addVideoListener(new VideoDisplayListener<MBFImage>()
552                                {
553                                        @Override
554                                        public void beforeUpdate(MBFImage frame)
555                                        {
556                                                // Reset the tracker if the last frame was a boundary
557                                                shotDetector.processFrame(frame);
558                                                if (shotDetector.wasLastFrameBoundary() || needRedetect)
559                                                {
560                                                        tracker.reset();
561                                                        needRedetect = false;
562                                                }
563
564                                                // Track the faces
565                                                tracker.track(frame);
566                                                final List<TrackedFace> t = tracker.getModelTracker().trackedFaces;
567                                                if (t != null && t.size() > 0 && trackerInfo != null)
568                                                {
569                                                        final int indx = t.indexOf(trackerInfo.getSelectedFace());
570                                                        if (indx != -1)
571                                                                trackFace(t.get(indx));
572                                                }
573
574                                                // Draw the faces onto the frame
575                                                tracker.drawModel(frame, true, true, true, true, true);
576
577                                                // Update the track info screen
578                                                if (trackerInfo != null)
579                                                        trackerInfo.setFaceList(t);
580                                        }
581
582                                        @Override
583                                        public void afterUpdate(VideoDisplay<MBFImage> display)
584                                        {
585                                        }
586                                });
587                                videoFrame.setLocation(getLocation().x, getLocation().y + getHeight());
588                                videoFrame.setVisible(true);
589
590                                // Create information about the face tracking
591                                final JFrame trackerFrame = new JFrame();
592                                trackerInfo = new TrackerInfo();
593                                trackerFrame.getContentPane().add(trackerInfo);
594                                trackerFrame.setLocation(videoFrame.getLocation().x + videoFrame.getWidth(),
595                                                getLocation().y + getHeight());
596                                trackerFrame.pack();
597                                trackerFrame.setVisible(true);
598                        } else {
599                                if (videoDisplay.getVideo() != video)
600                                        videoDisplay.changeVideo(video);
601                                videoFrame.setVisible(true);
602                        }
603                } else {
604                        // Not selected.
605                        if (videoDisplay != null) {
606                                videoDisplay.getVideo().close();
607                                videoDisplay.setMode(Mode.STOP);
608                                videoDisplay.close();
609                                videoDisplay = null;
610                        }
611
612                        videoFrame.setVisible(false);
613                }
614        }
615
616        /**
617         * Initialise the widgets
618         */
619        private void init() {
620                super.setLayout(new GridBagLayout());
621
622                modelView = new ModelView();
623
624                final GridBagConstraints gbc = new GridBagConstraints();
625                gbc.fill = GridBagConstraints.BOTH;
626                gbc.weightx = gbc.weighty = 1;
627                gbc.gridx = gbc.gridy = 1;
628
629                this.add(modelView, gbc); // 1,1
630
631                // Add a panel to put the sliders on.
632                final JPanel slidersPanel = new JPanel(new GridBagLayout());
633
634                // Add a panel that allows us to select the source of the model.
635                gbc.gridx = gbc.gridy = 1;
636                gbc.insets = new Insets(2, 2, 2, 2);
637                final JPanel sourcePanel = new JPanel(new GridBagLayout());
638                final ButtonGroup sourceGroup = new ButtonGroup();
639
640                // Button that sets the source to model only
641                final JToggleButton modelOnlyButton = new JToggleButton("Model");
642                sourceGroup.add(modelOnlyButton);
643                sourcePanel.add(modelOnlyButton, gbc);
644                sourceGroup.setSelected(modelOnlyButton);
645                modelOnlyButton.addChangeListener(new ChangeListener()
646                {
647                        @Override
648                        public void stateChanged(ChangeEvent e)
649                        {
650                                if (modelOnlyButton.isSelected())
651                                        displayVideoFrame(null, false);
652                        }
653                });
654
655                // Button that sets the source to webcam
656                gbc.gridy++;
657                final JToggleButton webcamButton = new JToggleButton("Webcam");
658                sourceGroup.add(webcamButton);
659                sourcePanel.add(webcamButton, gbc);
660                webcamButton.addChangeListener(new ChangeListener()
661                {
662                        @Override
663                        public void stateChanged(ChangeEvent e)
664                        {
665                                if (webcamButton.isSelected())
666                                {
667                                        if (videoDisplay == null ||
668                                                        !(videoDisplay.getVideo() instanceof VideoCapture))
669                                        {
670                                                try
671                                                {
672                                                        final VideoCapture vc = new VideoCapture(640, 480);
673                                                        displayVideoFrame(vc, true);
674                                                }
675                                                catch (final IOException e1)
676                                                {
677                                                        JOptionPane.showMessageDialog(ModelManipulatorGUI.this,
678                                                                        "Unable to instantiate the webcam");
679                                                        e1.printStackTrace();
680                                                }
681                                        }
682                                }
683                        }
684                });
685
686                // Button that set the source to a video file
687                gbc.gridy++;
688                final JToggleButton videoFileButton = new JToggleButton("Video File");
689                sourceGroup.add(videoFileButton);
690                sourcePanel.add(videoFileButton, gbc);
691                videoFileButton.addChangeListener(new ChangeListener()
692                {
693                        @Override
694                        public void stateChanged(ChangeEvent e)
695                        {
696                                if (videoFileButton.isSelected())
697                                {
698                                        final JFileChooser jfc = new JFileChooser();
699                                        final int returnVal = jfc.showOpenDialog(ModelManipulatorGUI.this);
700
701                                        if (returnVal == JFileChooser.APPROVE_OPTION)
702                                        {
703                                                final XuggleVideo xv = new XuggleVideo(jfc.getSelectedFile());
704                                                displayVideoFrame(xv, true);
705                                        }
706                                }
707                        }
708                });
709
710                // Add the source panel to the main panel
711                gbc.gridx = gbc.gridy = 1;
712                gbc.insets = new Insets(0, 0, 0, 0);
713                slidersPanel.add(sourcePanel, gbc);
714
715                // Add the global settings.
716                gbc.gridx = gbc.gridy = 1;
717                final JPanel pGlobalSliders = new JPanel(new GridBagLayout());
718                pGlobalSliders.setBorder(BorderFactory.createCompoundBorder(
719                                BorderFactory.createEmptyBorder(4, 4, 4, 4),
720                                BorderFactory.createTitledBorder("Pose")));
721                for (int i = 0; i < modelView.getNumGlobalParams(); i++) {
722                        final int j = i;
723                        int min = 0, max = 20000;
724                        final int val = (int) (modelView.getGlobalParam(i) * 1000d);
725
726                        if (j < globalMins.length)
727                                min = globalMins[j];
728                        if (j < globalMaxs.length)
729                                max = globalMaxs[j];
730
731                        final JSlider s = new JSlider(min, max, val);
732                        globalSliders.add(s);
733                        s.addChangeListener(new ChangeListener()
734                        {
735                                @Override
736                                public void stateChanged(ChangeEvent e)
737                                {
738                                        modelView.setGlobalParam(j, s.getValue() / 1000d);
739                                }
740                        });
741
742                        // Add a tooltip if we have one
743                        if (i < globalLabels.length && globalLabels[i] != null)
744                                s.setToolTipText(globalLabels[i]);
745
746                        pGlobalSliders.add(s, gbc);
747                        gbc.gridy++;
748                }
749
750                gbc.gridy = 2;
751                slidersPanel.add(pGlobalSliders, gbc);
752
753                // Add the local sliders
754                gbc.gridy = 1;
755                final JPanel pLocalSliders = new JPanel(new GridBagLayout());
756                pLocalSliders.setBorder(BorderFactory.createCompoundBorder(
757                                BorderFactory.createEmptyBorder(4, 4, 4, 4),
758                                BorderFactory.createTitledBorder("Local")));
759                for (int i = 0; i < modelView.getNumLocalParams(); i++) {
760                        final int j = i;
761                        int min = -20000, max = 20000;
762                        final int val = (int) (modelView.getLocalParam(i) * 1000d);
763
764                        if (j < localMins.length)
765                                min = localMins[j];
766                        if (j < localMaxs.length)
767                                max = localMaxs[j];
768
769                        final JSlider s = new JSlider(min, max, val);
770                        localSliders.add(s);
771                        s.addChangeListener(new ChangeListener()
772                        {
773                                @Override
774                                public void stateChanged(ChangeEvent e)
775                                {
776                                        modelView.setLocalParam(j, s.getValue() / 1000d);
777                                }
778                        });
779
780                        // Add a tooltip if we have one
781                        if (i < localLabels.length && localLabels[i] != null)
782                                s.setToolTipText(localLabels[i]);
783
784                        pLocalSliders.add(s, gbc);
785                        gbc.gridy++;
786                }
787
788                gbc.gridy = 3;
789                gbc.gridx = 1;
790                slidersPanel.add(pLocalSliders, gbc);
791
792                gbc.gridx = 2;
793                gbc.gridy = 1;
794                gbc.weightx = 0.5;
795                this.add(slidersPanel, gbc); // 2,1
796        }
797
798        /**
799         * Makes the model track the face
800         */
801        private void trackFace(TrackedFace face) {
802                for (int i = 0; i < modelView.getNumGlobalParams(); i++)
803                        globalSliders.get(i).setValue((int) (face.clm._pglobl.get(i, 0) * 1000));
804
805                for (int i = 0; i < modelView.getNumLocalParams(); i++)
806                        localSliders.get(i).setValue((int) (face.clm._plocal.get(i, 0) * 1000));
807        }
808
809        /**
810         * Main
811         * 
812         * @param args
813         *            Command-line arguments
814         */
815        public static void main(String[] args) {
816                final JFrame f = new JFrame("CLM Model Manipulator");
817                final ModelManipulatorGUI gui = new ModelManipulatorGUI();
818                f.getContentPane().add(gui);
819                f.setSize(1000, gui.getPreferredSize().height);
820                f.setVisible(true);
821        }
822}