Skip to content

Instantly share code, notes, and snippets.

@jonhare
Created May 26, 2017 16:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonhare/83a714728e611feecadbbb31766f8cfd to your computer and use it in GitHub Desktop.
Save jonhare/83a714728e611feecadbbb31766f8cfd to your computer and use it in GitHub Desktop.
ModelManipulatorGUI with a real image rather than the model
/**
* Copyright (c) 2011, The University of Southampton and the individual contributors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the University of Southampton nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
*/
package org.openimaj.demos.faces;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JToggleButton;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openimaj.demos.Demo;
import org.openimaj.image.ImageUtilities;
import org.openimaj.image.MBFImage;
import org.openimaj.image.colour.RGBColour;
import org.openimaj.image.processing.face.tracking.clm.CLMFaceTracker;
import org.openimaj.image.processing.face.tracking.clm.MultiTracker.TrackedFace;
import org.openimaj.image.processing.transform.PiecewiseMeshWarp;
import org.openimaj.math.geometry.shape.Rectangle;
import org.openimaj.math.geometry.shape.Shape;
import org.openimaj.math.geometry.shape.Triangle;
import org.openimaj.util.pair.IndependentPair;
import org.openimaj.util.pair.Pair;
import org.openimaj.video.Video;
import org.openimaj.video.VideoDisplay;
import org.openimaj.video.VideoDisplay.Mode;
import org.openimaj.video.VideoDisplayListener;
import org.openimaj.video.capture.VideoCapture;
import org.openimaj.video.processing.shotdetector.HistogramVideoShotDetector;
import org.openimaj.video.xuggle.XuggleVideo;
/**
* Provides a user interface for interacting with the parameters of the trained
* CLM Model.
*
* @author David Dupplaw (dpd@ecs.soton.ac.uk)
* @created 9 Jul 2012
* @version $Author$, $Revision$, $Date$
*/
@Demo(
author = "David Dupplaw",
description = "Interface for playing with the parameters of a " +
"trained Constrained Local Model (CLM) based facial expression tracker",
keywords = { "Constrained Local Model", "Point Distribution Model", "face", "webcam" },
title = "CLM Face Model Playground")
public class ModelManipulatorGUI2 extends JPanel {
private static final long serialVersionUID = -3302684225273947693L;
/**
* Provides a panel which draws a particular model to itself.
*
* @author David Dupplaw (dpd@ecs.soton.ac.uk)
* @created 9 Jul 2012
* @version $Author$, $Revision$, $Date$
*/
public class ModelView extends JPanel {
/** */
private static final long serialVersionUID = 1L;
/** The image of the model */
private final MBFImage vis = new MBFImage(600, 600, 3);
/** The face being drawn */
private TrackedFace face = null;
/** Reference triangles */
private final int[][] triangles;
/** Reference connections */
private final int[][] connections;
/** Colour to draw the connections */
private final Float[] connectionColour = RGBColour.WHITE;
/** Colour to draw the points */
private final Float[] pointColour = RGBColour.RED;
/** Colour to draw the mesh */
private final Float[] meshColour = new Float[] { 0.3f, 0.3f, 0.3f };
/** Colour to draw the bounding box */
private final Float[] boundingBoxColour = RGBColour.RED;
/** Whether to show mesh */
private final boolean showMesh = true;
/** Whether to show connections */
private final boolean showConnections = true;
/**
* Constructor
*/
public ModelView() {
this.setPreferredSize(new Dimension(600, 600));
final CLMFaceTracker t = new CLMFaceTracker();
this.triangles = t.getReferenceTriangles();
this.connections = t.getReferenceConnections();
this.face = new TrackedFace(
new Rectangle(50, -50, 500, 500),
t.getInitialVars());
t.initialiseFaceModel(face);
// Centre the face in the view
setGlobalParam(0, 10);
setGlobalParam(4, 300);
setGlobalParam(5, 300);
setBackground(new Color(60, 60, 60));
}
/**
* {@inheritDoc}
*
* @see javax.swing.JComponent#paint(java.awt.Graphics)
*/
@Override
public void paint(Graphics g) {
super.paint(g);
vis.zero();
// Draw the model to the image.
CLMFaceTracker.drawFaceModel(vis, face, showMesh, showConnections,
true, true, true, triangles, connections, 1, boundingBoxColour,
meshColour, connectionColour, pointColour);
final List<Triangle> triangles = tracker.getTriangles(face);
final IndependentPair<MBFImage, List<Triangle>> puppetData = ModelManipulatorGUI2.this.puppet;
final List<Triangle> puppetTriangles = puppetData.secondObject();
final List<Pair<Shape>> matches = computeMatches(puppetTriangles, triangles);
final PiecewiseMeshWarp<Float[], MBFImage> pmw = new PiecewiseMeshWarp<Float[], MBFImage>(matches);
final MBFImage puppet = puppetData.firstObject().clone();
puppet.processInplace(pmw);
vis.drawImage(puppet, 0, 0);
// Draw the image to the panel
g.drawImage(ImageUtilities.createBufferedImage(vis), 0, 0, null);
}
private List<Pair<Shape>> computeMatches(List<Triangle> from, List<Triangle> to) {
final List<Pair<Shape>> mtris = new ArrayList<Pair<Shape>>();
for (int i = 0; i < from.size(); i++) {
final Triangle t1 = from.get(i);
Triangle t2 = to.get(i);
if (t1 != null && t2 != null) {
t2 = t2.clone();
t2.scale((float) (1.0 / tracker.scale));
mtris.add(new Pair<Shape>(t1, t2));
}
}
return mtris;
}
/**
* Number of global parameters
*
* @return The number of global parameters
*/
public int getNumGlobalParams() {
return face.clm._pglobl.getRowDimension();
}
/**
* Get the value of the given global parameter.
*
* @param indx
* The index to get
* @return The value
*/
public double getGlobalParam(int indx) {
return face.clm._pglobl.get(indx, 0);
}
/**
* Set the given index to the value in the global params and update the
* face model.
*
* @param indx
* The index
* @param val
* The new value
*/
public void setGlobalParam(int indx, double val) {
// Set the parameter
face.clm._pglobl.set(indx, 0, val);
// Recalculate the shape.
face.clm._pdm.calcShape2D(face.shape,
face.clm._plocal, face.clm._pglobl);
repaint();
}
/**
* Number of local parameters
*
* @return The number of local parameters
*/
public int getNumLocalParams() {
return face.clm._plocal.getRowDimension();
}
/**
* Get the value of the given local parameter.
*
* @param indx
* The index to get
* @return The value
*/
public double getLocalParam(int indx) {
return face.clm._plocal.get(indx, 0);
}
/**
* Set the given index to the value in the local params and update the
* face model.
*
* @param indx
* The index
* @param val
* The new value
*/
public void setLocalParam(int indx, double val) {
// Set the parameter
face.clm._plocal.set(indx, 0, val);
// Recalculate the shape.
face.clm._pdm.calcShape2D(face.shape,
face.clm._plocal, face.clm._pglobl);
repaint();
}
}
/**
* A class that provides a display of the information that the tracker is
* tracking.
*
* @author David Dupplaw (dpd@ecs.soton.ac.uk)
* @created 17 Jul 2012
* @version $Author$, $Revision$, $Date$
*/
protected class TrackerInfo extends JPanel {
/** */
private static final long serialVersionUID = 1L;
/** The list of faces being tracked */
private final JPanel faceList = new JPanel();
/** Map */
private final Map<TrackedFace, AbstractButton> map = new HashMap<TrackedFace, AbstractButton>();
/** Only allow one face to be tracked */
private final ButtonGroup faceGroup = new ButtonGroup();
/**
* Default constructor
*/
public TrackerInfo() {
super.setLayout(new GridBagLayout());
super.setPreferredSize(new Dimension(600, 300));
super.setSize(600, 300);
init();
}
/**
* Initialises the widgets.
*/
private void init() {
final GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = gbc.gridy = 1;
gbc.weightx = gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
// Add the list of faces
faceList.setLayout(new GridLayout(-1, 1));
faceList.setBackground(Color.black);
this.add(faceList, gbc);
// Add a button to force redetection
final JButton b = new JButton("Force Redetection");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
needRedetect = true;
}
});
gbc.gridy++;
gbc.weighty = 0;
this.add(b, gbc);
}
/**
* Set the list of faces being tracked.
*
* @param faces
* The face list
*/
public void setFaceList(List<TrackedFace> faces) {
final ArrayList<TrackedFace> toRemove = new ArrayList<TrackedFace>();
toRemove.addAll(map.keySet());
// Add new faces
for (final TrackedFace face : faces) {
if (!map.keySet().contains(face)) {
// Add the face to the list as a toggle button
final JToggleButton b = new JToggleButton(face.toString(),
new ImageIcon(ImageUtilities.createBufferedImage(
face.templateImage)));
// Store the map from the face to the button
map.put(face, b);
// Add the button to the panel
faceGroup.add(b);
faceList.add(b);
faceList.revalidate();
}
// Either the face is new or it's existing, so we
// don't want to remove it - so we remove it from the
// 'to remove' list
toRemove.remove(face);
}
// Remove all the faces that have disappeared.
for (final TrackedFace face : toRemove) {
faceList.remove(map.get(face));
faceGroup.remove(map.get(face));
map.remove(face);
}
// If nothing's selected, select the first one.
if (faceGroup.getSelected() == null && map.keySet().size() > 0)
faceGroup.setSelected(map.values().iterator().next());
}
/**
* Returns the face that is selected.
*
* @return The selected face
*/
public TrackedFace getSelectedFace() {
final Iterator<TrackedFace> faces = map.keySet().iterator();
TrackedFace f = null;
while (faces.hasNext())
if (map.get(f = faces.next()) == faceGroup.getSelected())
return f;
return null;
}
}
/**
* A replacement for the AWT ButtonGroup class.
*
* @author David Dupplaw (dpd@ecs.soton.ac.uk)
* @created 17 Jul 2012
* @version $Author$, $Revision$, $Date$
*/
protected class ButtonGroup {
/** The buttons */
private final List<AbstractButton> buttons = new ArrayList<AbstractButton>();
/**
* Add a button
*
* @param b
*/
public void add(final AbstractButton b) {
this.buttons.add(b);
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateButtons(b);
}
});
}
/**
* Remove the given button from the group.
*
* @param b
* The button to remove
*/
public void remove(final AbstractButton b) {
this.buttons.remove(b);
}
/**
* Make sure only the given button is selected.
*
* @param b
* The button to select.
*/
private void updateButtons(AbstractButton b) {
for (final AbstractButton button : buttons)
button.setSelected(button == b);
}
/**
* Returns the selected button in the group.
*
* @return The selected button in the group or null if no buttons are
* selected.
*/
public AbstractButton getSelected() {
for (final AbstractButton button : buttons)
if (button.isSelected())
return button;
return null;
}
/**
* Sets all buttons in the group to unselected.
*/
public void selectNone() {
for (final AbstractButton button : buttons)
button.setSelected(false);
}
/**
* Set the selected button to the given one. Note that this method will
* select the button whether or not the button is in the button group.
*
* @param b
* The button to select
*/
public void setSelected(AbstractButton b) {
b.setSelected(true);
updateButtons(b);
}
}
/** The view of the model */
private ModelView modelView = null;
/** The video view */
private JFrame videoFrame = null;
/** The tracker info */
private TrackerInfo trackerInfo = null;
/** The video displayer */
private VideoDisplay<MBFImage> videoDisplay = null;
/** The tracker used to track faces in the videos */
private final CLMFaceTracker tracker = new CLMFaceTracker();
/** Shot detector used to force redetects on shot changes */
// Note that the fps isn't used, so we just give 25 as anything will do
private final HistogramVideoShotDetector shotDetector = new HistogramVideoShotDetector(25);
/** The global sliders */
private final List<JSlider> globalSliders = new ArrayList<JSlider>();
/** The local sliders */
private final List<JSlider> localSliders = new ArrayList<JSlider>();
// -------------------------------------------------------
// Note that all the slider values need to be 1,000 times
// the size of the actual value as the sliders only work
// in integer, so we divide the slider value by 1000 to get
// the actual value.
// -------------------------------------------------------
/** Tool tip label text for each of the global sliders */
private final String[] globalLabels = new String[] {
"Scale", "X Rotation", "Y Rotation", "Z Rotation",
"Translate X", "Translate Y"
};
/** Tool tip label text for each of the local sliders */
private final String[] localLabels = new String[] {
};
/** Maximum values for each of the global sliders */
private final int[] globalMaxs = new int[] {
20000, (int) (Math.PI * 2000), (int) (Math.PI * 2000),
(int) (Math.PI * 2000), 1000000, 1000000
};
/** Minimum values for each of the global sliders */
private final int[] globalMins = new int[] {
0, -(int) (Math.PI * 2000), -(int) (Math.PI * 2000),
-(int) (Math.PI * 2000), 0, 0
};
/** Maximum values for each of the local sliders */
private final int[] localMaxs = new int[] {
};
/** Minimum values for each of the local sliders */
private final int[] localMins = new int[] {
};
/** Whether to force redetect on next track */
private boolean needRedetect = false;
private IndependentPair<MBFImage, List<Triangle>> puppet;
/**
* Default constructor
*
* @throws IOException
*/
public ModelManipulatorGUI2() throws IOException {
final CLMFaceTracker ptracker = new CLMFaceTracker();
final URL url = MultiPuppeteer.class.getResource("nigel.jpg");
MBFImage image = ImageUtilities.readMBF(url);
final int paddingWidth = Math.max(image.getWidth(), 640);
final int paddingHeight = Math.max(image.getHeight(), 480);
image = image.padding(paddingWidth, paddingHeight);
ptracker.track(image);
final TrackedFace face = ptracker.getTrackedFaces().get(0);
puppet = IndependentPair.pair(image, ptracker.getTriangles(face));
ptracker.reset();
init();
}
/**
* Display (or hide) the video frame, creating it if necessary.
*
* @param showNotHide
* TRUE for show, FALSE for hide
*/
private void displayVideoFrame(Video<MBFImage> video, boolean showNotHide) {
System.out.println("displayVideoFrame( " + video + ", " + showNotHide + " )");
// If the button was selected...
if (showNotHide) {
// ..and we don't yet have a video frame
if (videoFrame == null) {
videoFrame = new JFrame();
videoDisplay = VideoDisplay.createVideoDisplay(video, videoFrame);
videoDisplay.addVideoListener(new VideoDisplayListener<MBFImage>() {
@Override
public void beforeUpdate(MBFImage frame) {
// Reset the tracker if the last frame was a boundary
shotDetector.processFrame(frame);
if (shotDetector.wasLastFrameBoundary() || needRedetect) {
tracker.reset();
needRedetect = false;
}
// Track the faces
tracker.track(frame);
final List<TrackedFace> t = tracker.getModelTracker().trackedFaces;
if (t != null && t.size() > 0 && trackerInfo != null) {
final int indx = t.indexOf(trackerInfo.getSelectedFace());
if (indx != -1)
trackFace(t.get(indx));
}
// Draw the faces onto the frame
tracker.drawModel(frame, true, true, true, true, true);
// Update the track info screen
if (trackerInfo != null)
trackerInfo.setFaceList(t);
}
@Override
public void afterUpdate(VideoDisplay<MBFImage> display) {
}
});
videoFrame.setLocation(getLocation().x, getLocation().y + getHeight());
videoFrame.setVisible(true);
// Create information about the face tracking
final JFrame trackerFrame = new JFrame();
trackerInfo = new TrackerInfo();
trackerFrame.getContentPane().add(trackerInfo);
trackerFrame.setLocation(videoFrame.getLocation().x + videoFrame.getWidth(),
getLocation().y + getHeight());
trackerFrame.pack();
trackerFrame.setVisible(true);
} else {
if (videoDisplay.getVideo() != video)
videoDisplay.changeVideo(video);
videoFrame.setVisible(true);
}
} else {
// Not selected.
if (videoDisplay != null) {
videoDisplay.getVideo().close();
videoDisplay.setMode(Mode.STOP);
videoDisplay.close();
videoDisplay = null;
}
videoFrame.setVisible(false);
}
}
/**
* Initialise the widgets
*/
private void init() {
super.setLayout(new GridBagLayout());
modelView = new ModelView();
final GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = gbc.weighty = 1;
gbc.gridx = gbc.gridy = 1;
this.add(modelView, gbc); // 1,1
// Add a panel to put the sliders on.
final JPanel slidersPanel = new JPanel(new GridBagLayout());
// Add a panel that allows us to select the source of the model.
gbc.gridx = gbc.gridy = 1;
gbc.insets = new Insets(2, 2, 2, 2);
final JPanel sourcePanel = new JPanel(new GridBagLayout());
final ButtonGroup sourceGroup = new ButtonGroup();
// Button that sets the source to model only
final JToggleButton modelOnlyButton = new JToggleButton("Model");
sourceGroup.add(modelOnlyButton);
sourcePanel.add(modelOnlyButton, gbc);
sourceGroup.setSelected(modelOnlyButton);
modelOnlyButton.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (modelOnlyButton.isSelected())
displayVideoFrame(null, false);
}
});
// Button that sets the source to webcam
gbc.gridy++;
final JToggleButton webcamButton = new JToggleButton("Webcam");
sourceGroup.add(webcamButton);
sourcePanel.add(webcamButton, gbc);
webcamButton.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (webcamButton.isSelected()) {
if (videoDisplay == null ||
!(videoDisplay.getVideo() instanceof VideoCapture))
{
try {
final VideoCapture vc = new VideoCapture(640, 480);
displayVideoFrame(vc, true);
} catch (final IOException e1) {
JOptionPane.showMessageDialog(ModelManipulatorGUI2.this,
"Unable to instantiate the webcam");
e1.printStackTrace();
}
}
}
}
});
// Button that set the source to a video file
gbc.gridy++;
final JToggleButton videoFileButton = new JToggleButton("Video File");
sourceGroup.add(videoFileButton);
sourcePanel.add(videoFileButton, gbc);
videoFileButton.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (videoFileButton.isSelected()) {
final JFileChooser jfc = new JFileChooser();
final int returnVal = jfc.showOpenDialog(ModelManipulatorGUI2.this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
final XuggleVideo xv = new XuggleVideo(jfc.getSelectedFile());
displayVideoFrame(xv, true);
}
}
}
});
// Add the source panel to the main panel
gbc.gridx = gbc.gridy = 1;
gbc.insets = new Insets(0, 0, 0, 0);
slidersPanel.add(sourcePanel, gbc);
// Add the global settings.
gbc.gridx = gbc.gridy = 1;
final JPanel pGlobalSliders = new JPanel(new GridBagLayout());
pGlobalSliders.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(4, 4, 4, 4),
BorderFactory.createTitledBorder("Pose")));
for (int i = 0; i < modelView.getNumGlobalParams(); i++) {
final int j = i;
int min = 0, max = 20000;
final int val = (int) (modelView.getGlobalParam(i) * 1000d);
if (j < globalMins.length)
min = globalMins[j];
if (j < globalMaxs.length)
max = globalMaxs[j];
final JSlider s = new JSlider(min, max, val);
globalSliders.add(s);
s.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
modelView.setGlobalParam(j, s.getValue() / 1000d);
}
});
// Add a tooltip if we have one
if (i < globalLabels.length && globalLabels[i] != null)
s.setToolTipText(globalLabels[i]);
pGlobalSliders.add(s, gbc);
gbc.gridy++;
}
gbc.gridy = 2;
slidersPanel.add(pGlobalSliders, gbc);
// Add the local sliders
gbc.gridy = 1;
final JPanel pLocalSliders = new JPanel(new GridBagLayout());
pLocalSliders.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(4, 4, 4, 4),
BorderFactory.createTitledBorder("Local")));
for (int i = 0; i < modelView.getNumLocalParams(); i++) {
final int j = i;
int min = -20000, max = 20000;
final int val = (int) (modelView.getLocalParam(i) * 1000d);
if (j < localMins.length)
min = localMins[j];
if (j < localMaxs.length)
max = localMaxs[j];
final JSlider s = new JSlider(min, max, val);
localSliders.add(s);
s.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
modelView.setLocalParam(j, s.getValue() / 1000d);
}
});
// Add a tooltip if we have one
if (i < localLabels.length && localLabels[i] != null)
s.setToolTipText(localLabels[i]);
pLocalSliders.add(s, gbc);
gbc.gridy++;
}
gbc.gridy = 3;
gbc.gridx = 1;
slidersPanel.add(pLocalSliders, gbc);
gbc.gridx = 2;
gbc.gridy = 1;
gbc.weightx = 0.5;
this.add(slidersPanel, gbc); // 2,1
}
/**
* Makes the model track the face
*/
private void trackFace(TrackedFace face) {
for (int i = 0; i < modelView.getNumGlobalParams(); i++)
globalSliders.get(i).setValue((int) (face.clm._pglobl.get(i, 0) * 1000));
for (int i = 0; i < modelView.getNumLocalParams(); i++)
localSliders.get(i).setValue((int) (face.clm._plocal.get(i, 0) * 1000));
}
/**
* Main
*
* @param args
* Command-line arguments
* @throws IOException
*/
public static void main(String[] args) throws IOException {
final JFrame f = new JFrame("CLM Model Manipulator");
final ModelManipulatorGUI2 gui = new ModelManipulatorGUI2();
f.getContentPane().add(gui);
f.setSize(1000, gui.getPreferredSize().height);
f.setVisible(true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment