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}