diff --git a/GUI/src/main/java/cz/fidentis/analyst/gui/HistogramComponent.java b/GUI/src/main/java/cz/fidentis/analyst/gui/HistogramComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..280fc4ddcc65da58d918564dbc28faf83d0af410 --- /dev/null +++ b/GUI/src/main/java/cz/fidentis/analyst/gui/HistogramComponent.java @@ -0,0 +1,230 @@ +package cz.fidentis.analyst.gui; + +import cz.fidentis.analyst.mesh.core.MeshFacet; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JSpinner; +import javax.swing.SpinnerModel; +import javax.swing.SpinnerNumberModel; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + + +/** + * @author Daniel Sokol + * <p> + * Class for computing histogram and modifing distances + */ + +public class HistogramComponent extends JPanel { + private final DrawCanvas drawnHistogram = new DrawCanvas(); + private final JSpinner minSpinner = new JSpinner(); + private final JSpinner maxSpinner = new JSpinner(); + + private Map<MeshFacet, List<Double>> values; + private Double minValue = 1.0; + private Double maxValue = 10.0; + private Double oneStep = 1.0; + private final Map<Double, Integer> frequencies = new TreeMap<>(); + + private int maxFrequency = 1; + private int intervalsCount = 20; + + + + public HistogramComponent() { + setupComponents(); + } + + /** + * Add swing components to frame + */ + private void setupComponents() { + setBackground(new Color(0, 174, 163)); + setLayout(new BoxLayout(this,BoxLayout.PAGE_AXIS)); + + add(new JLabel("Number of intervals:")); + JSlider intervalSlider = new JSlider(JSlider.HORIZONTAL, 0, 200, intervalsCount); + intervalSlider.setMajorTickSpacing(20); + intervalSlider.setMinorTickSpacing(5); + intervalSlider.setPaintTicks(true); + intervalSlider.setPaintLabels(true); + intervalSlider.setBackground(new Color(0, 174, 163)); + add(intervalSlider); + + drawnHistogram.setMinimumSize(new Dimension(getMinimumSize().width, 330)); + drawnHistogram.setMaximumSize(new Dimension(getMaximumSize().width, 330)); + drawnHistogram.setPreferredSize(new Dimension(getPreferredSize().width, 330)); + add(drawnHistogram); + Box tempBox = new Box(BoxLayout.LINE_AXIS); + + tempBox.add(new JLabel("Select minimum value of distances")); + tempBox.add(minSpinner); + tempBox.add(new JLabel("Select maximum value of distances")); + tempBox.add(maxSpinner); + + JButton newButton = new JButton("Set and Compute"); + + newButton.addActionListener(e -> { + intervalsCount = intervalSlider.getValue(); + minValue = (Double) minSpinner.getValue(); + maxValue = (Double) maxSpinner.getValue(); + setSpinners(); + cutValues(); + computeFrequencies(); + drawnHistogram.repaint(); + + }); + tempBox.add(newButton); + add(tempBox); + + } + + /** + * Set spinners to new min a max values + */ + private void setSpinners() { + + SpinnerModel minModel = new SpinnerNumberModel((double) minValue, + (double) minValue, (double) maxValue, (double) oneStep); + minSpinner.setModel(minModel); + + SpinnerModel maxModel = new SpinnerNumberModel((double) maxValue, + (double) minValue, (double) maxValue, (double) oneStep); + maxSpinner.setModel(maxModel); + } + + + public Map<MeshFacet, List<Double>> getValues() { + return values; + } + + /** + * Set new distances and compute their frequencies + * + * @param values Values + */ + public void setValues(Map<MeshFacet, List<Double>> values) { + this.values = values; + findMinMax(); + computeFrequencies(); + drawnHistogram.repaint(); + setSpinners(); + } + + public void computeFrequencies() { + + oneStep = maxValue - minValue; + if (maxValue < 0) { + oneStep = Math.abs(maxValue) - Math.abs(minValue); + } + oneStep /= intervalsCount; + //Remove old values in frequencies + frequencies.clear(); + + for (Double i = minValue; i < maxValue; i += oneStep) { + int currFrequency = 0; //Number of values in current interval + for (List<Double> facetValues : values.values() ){ + for (Double temp : facetValues) { + if (temp >= i && temp < i + oneStep) { + currFrequency++; + } + } + } + if (currFrequency > maxFrequency) { + maxFrequency = currFrequency; + } + //Add beginning of interval and number of values in it + frequencies.put(i, currFrequency); + } + } + + private void findMinMax() { + minValue = Double.MAX_VALUE; + maxValue = Double.MIN_VALUE; + for (List<Double> facetValues : values.values() ){ + for (Double temp : facetValues) { + if (temp < minValue) { + minValue = temp; + } + if (temp > maxValue) { + maxValue = temp; + } + } + } + } + + /** + * Cut of distances that are before minimum or after maximum distance selected by user + */ + private void cutValues() { + for (List<Double> double_values : values.values() ){ + for (int i = 0; i < values.size(); ++i) { + if (double_values.get(i) < minValue) { + double_values.set(i, minValue); + } else if (double_values.get(i) > maxValue) { + double_values.set(i, maxValue); + } + } + } + + } + + public Double getMinValue() { + return minValue; + } + + public Double getMaxValue() { + return maxValue; + } + + /** + * @author Daniel Sokol + * <p> + * Internal class for drawing histogram + */ + private class DrawCanvas extends JPanel { + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + setBackground(new Color(0, 174, 163)); + + int place = 0; + + int moveStep = this.getWidth() / intervalsCount; + double heightModificator = (double) this.getHeight() / maxFrequency; + + Graphics2D g2 = (Graphics2D) g; + Font font = new Font("Arial Narrow", Font.BOLD, 12); + + AffineTransform affineTransform = new AffineTransform(); + affineTransform.rotate(Math.toRadians(90), 0, 0); + g2.setFont(font.deriveFont(affineTransform)); + + for (Map.Entry<Double, Integer> entry : frequencies.entrySet()) { + + g.setColor(Color.ORANGE); + int height = (int) (entry.getValue() * heightModificator); + g2.fillRect(place, 300 - height, moveStep - 2, height); + + g.setColor(Color.BLACK); + String value = String.format("%.4f", entry.getKey()); + g2.drawString(value, place, this.getHeight() - 50); + g2.drawString(entry.getValue().toString(), place, 50); + place += moveStep; + } + } + } +} diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/HeatMapsTestApp.java b/GUI/src/main/java/cz/fidentis/analyst/tests/HeatMapsTestApp.java index d1a22328ec7d4579e6d46c4a90021d4ae0d01645..f185a9102ebaa4c6ed83439f0b6c71b3aa71449b 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/tests/HeatMapsTestApp.java +++ b/GUI/src/main/java/cz/fidentis/analyst/tests/HeatMapsTestApp.java @@ -4,55 +4,54 @@ import com.jogamp.opengl.GL; import com.jogamp.opengl.GL2; import com.jogamp.opengl.GLAutoDrawable; import cz.fidentis.analyst.face.HumanFace; -import cz.fidentis.analyst.mesh.core.MeshFacet; -import cz.fidentis.analyst.mesh.core.MeshModel; - -import javax.vecmath.Vector3d; -import java.util.List; -import java.util.Map; - -import static com.jogamp.opengl.GL.GL_FRONT_AND_BACK; -import static com.jogamp.opengl.GL.GL_VIEWPORT; -import static com.jogamp.opengl.GL2GL3.GL_FILL; -import static com.jogamp.opengl.GL2GL3.GL_LINE; -import static com.jogamp.opengl.fixedfunc.GLMatrixFunc.GL_MODELVIEW_MATRIX; -import static com.jogamp.opengl.fixedfunc.GLMatrixFunc.GL_PROJECTION_MATRIX; import cz.fidentis.analyst.gui.Canvas; import cz.fidentis.analyst.gui.GeneralGLEventListener; +import cz.fidentis.analyst.gui.HistogramComponent; +import cz.fidentis.analyst.mesh.core.MeshFacet; +import cz.fidentis.analyst.mesh.core.MeshModel; import cz.fidentis.analyst.mesh.core.MeshPoint; import cz.fidentis.analyst.visitors.mesh.Curvature; import cz.fidentis.analyst.visitors.mesh.HausdorffDistance; import cz.fidentis.analyst.visitors.mesh.HausdorffDistance.Strategy; +import org.openide.util.Exceptions; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.vecmath.Point3d; +import javax.vecmath.Vector3d; import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.io.File; import java.io.IOException; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.vecmath.Point3d; -import org.openide.util.Exceptions; + +import static com.jogamp.opengl.GL.GL_FRONT_AND_BACK; +import static com.jogamp.opengl.GL.GL_VIEWPORT; +import static com.jogamp.opengl.GL2GL3.GL_FILL; +import static com.jogamp.opengl.GL2GL3.GL_LINE; +import static com.jogamp.opengl.fixedfunc.GLMatrixFunc.GL_MODELVIEW_MATRIX; +import static com.jogamp.opengl.fixedfunc.GLMatrixFunc.GL_PROJECTION_MATRIX; /** * Testing application displaying different heat maps in 3D. - * + * * @author Daniel Sokol * @author Radek Oslejsek */ public class HeatMapsTestApp extends GeneralGLEventListener { - + private static final JFrame TEST_FRAME = new JFrame(); - + public final HistogramComponent histogram = new HistogramComponent(); + private Map<MeshFacet, List<Double>> values; private final Color minColor = Color.BLUE; private final Color maxColor = Color.RED; - - private double minDistance; - private double maxDistance; - + /** * Constructor. * @param canvas Canvas @@ -60,10 +59,10 @@ public class HeatMapsTestApp extends GeneralGLEventListener { public HeatMapsTestApp(Canvas canvas) { super(canvas); } - + /** - * Main method - * @param args Input arguments + * Main method + * @param args Input arguments * @throws IOException on IO error */ public static void main(String[] args) { @@ -78,7 +77,7 @@ public class HeatMapsTestApp extends GeneralGLEventListener { Canvas testCanvas = new Canvas(); HeatMapsTestApp colorListener = new HeatMapsTestApp(testCanvas); testCanvas.setListener(colorListener); - + gaussButton.addActionListener(e -> colorListener.computeGaussian()); meanButton.addActionListener(e -> colorListener.computeMean()); maxButton.addActionListener(e -> colorListener.computeMaxPrincipal()); @@ -88,7 +87,8 @@ public class HeatMapsTestApp extends GeneralGLEventListener { TEST_FRAME.setLayout(new GridBagLayout()); TEST_FRAME.setVisible(true); - TEST_FRAME.setSize(1600, 900); + //TEST_FRAME.setSize(1920, 1080); + TEST_FRAME.setExtendedState(JFrame.MAXIMIZED_BOTH); // Adding GridBagConstraints c = new GridBagConstraints(); @@ -98,7 +98,7 @@ public class HeatMapsTestApp extends GeneralGLEventListener { c.weighty = 1; c.weightx = 1; - c.gridheight = 6; + c.gridheight = 8; c.gridwidth = 4; c.weightx = 4; TEST_FRAME.add(testCanvas, c); @@ -106,28 +106,34 @@ public class HeatMapsTestApp extends GeneralGLEventListener { c.gridwidth = 2; c.gridx = 4; c.weightx = 2; - //testFrame.add(addButton, c); c.gridy = 1; + TEST_FRAME.add(colorListener.histogram,c); + c.weightx = 1; + c.gridwidth = 1; + c.gridy++; TEST_FRAME.add(gaussButton, c); - c.gridy = 2; + c.gridx++; TEST_FRAME.add(meanButton, c); - c.gridy = 3; + c.gridy++; + c.gridx--; TEST_FRAME.add(maxButton, c); - c.gridy = 4; + c.gridx++; TEST_FRAME.add(minButton, c); - c.gridy = 5; + c.gridy++; + c.gridx--; TEST_FRAME.add(hdButton1, c); - c.gridy = 6; + c.gridx++; TEST_FRAME.add(hdButton2, c); + } - + @Override public void setModel(MeshModel model) { super.setModel(model); - + System.err.println(); System.err.println("MODEL: " + getModel()); - + Set<Point3d> unique = new HashSet<>(); for (MeshPoint p: getModel().getFacets().get(0).getVertices()) { unique.add(p.getPosition()); @@ -152,18 +158,17 @@ public class HeatMapsTestApp extends GeneralGLEventListener { if (this.getModel() == null) { return; } - + long startTime = System.currentTimeMillis(); Curvature vis = new Curvature(); this.getModel().compute(vis); values = vis.getGaussianCurvatures(); long duration = System.currentTimeMillis() - startTime; System.err.println(duration + "\tmsec: Gaussian curvature"); - - minDistance = -0.02; - maxDistance = 0.008; + + histogram.setValues(values); } - + /** * Computes curvature */ @@ -171,18 +176,17 @@ public class HeatMapsTestApp extends GeneralGLEventListener { if (this.getModel() == null) { return; } - + long startTime = System.currentTimeMillis(); Curvature vis = new Curvature(); this.getModel().compute(vis); values = vis.getMeanCurvatures(); long duration = System.currentTimeMillis() - startTime; System.err.println(duration + "\tmsec: Mean curvature"); - - minDistance = -0.001; - maxDistance = 0.1; + + histogram.setValues(values); } - + /** * Computes curvature */ @@ -190,18 +194,17 @@ public class HeatMapsTestApp extends GeneralGLEventListener { if (this.getModel() == null) { return; } - + long startTime = System.currentTimeMillis(); Curvature vis = new Curvature(); this.getModel().compute(vis); values = vis.getMinPrincipalCurvatures(); long duration = System.currentTimeMillis() - startTime; System.err.println(duration + "\tmsec: Minimum principal curvature"); - - minDistance = -0.05; - maxDistance = 0.03; + + histogram.setValues(values); } - + /** * Computes curvature */ @@ -209,18 +212,16 @@ public class HeatMapsTestApp extends GeneralGLEventListener { if (this.getModel() == null) { return; } - + long startTime = System.currentTimeMillis(); Curvature vis = new Curvature(); this.getModel().compute(vis); values = vis.getMaxPrincipalCurvatures(); long duration = System.currentTimeMillis() - startTime; System.err.println(duration + "\tmsec: Maximum principal curvature"); - - minDistance = -0.02; - maxDistance = 0.25; + histogram.setValues(values); } - + /** * Computes absolute HD */ @@ -239,11 +240,9 @@ public class HeatMapsTestApp extends GeneralGLEventListener { } catch (IOException ex) { Exceptions.printStackTrace(ex); } - List<Double> distanceList = values.get(getModel().getFacets().get(0)); - minDistance = distanceList.stream().mapToDouble(Double::doubleValue).min().getAsDouble(); - maxDistance = distanceList.stream().mapToDouble(Double::doubleValue).max().getAsDouble(); + histogram.setValues(values); } - + /** * Computes relative HD */ @@ -263,10 +262,9 @@ public class HeatMapsTestApp extends GeneralGLEventListener { Exceptions.printStackTrace(ex); } List<Double> distanceList = values.get(getModel().getFacets().get(0)); - minDistance = distanceList.stream().mapToDouble(Double::doubleValue).min().getAsDouble(); - maxDistance = distanceList.stream().mapToDouble(Double::doubleValue).max().getAsDouble(); + histogram.setValues(values); } - + @Override public void display(GLAutoDrawable glad) { wireModel = glCanvas.getDrawWired(); // is wire-frame or not @@ -306,7 +304,7 @@ public class HeatMapsTestApp extends GeneralGLEventListener { protected void drawHeatmap(MeshModel model) { for (int i = 0; i < model.getFacets().size(); i++) { - renderFaceWithHeatmap(model.getFacets().get(i), values.get(model.getFacets().get(i)), minDistance, maxDistance); + renderFaceWithHeatmap(model.getFacets().get(i), histogram.getValues().get(model.getFacets().get(i)), histogram.getMinValue(), histogram.getMaxValue()); } } @@ -334,13 +332,13 @@ public class HeatMapsTestApp extends GeneralGLEventListener { protected Color getColor(Double currentDistance, Double minDistance, Double maxDistance) { double currentParameter = ((currentDistance - minDistance) / (maxDistance - minDistance)); - + if (currentDistance > maxDistance) { currentParameter = 1.0; - } else if (currentDistance < minDistance || (maxDistance - minDistance) == 0) { + } else if (currentDistance < minDistance || (maxDistance - minDistance) == 0) { currentParameter = 0.0; } - + float[] hsb1 = Color.RGBtoHSB(minColor.getRed(), minColor.getGreen(), minColor.getBlue(), null); double h1 = hsb1[0]; double s1 = hsb1[1]; @@ -377,7 +375,7 @@ public class HeatMapsTestApp extends GeneralGLEventListener { if (hue > 1) { hue = hue - 1; } - + double saturation = (1 - currentParameter) * s1 + currentParameter * s2; double brightness = (1 - currentParameter) * b1 + currentParameter * b2;