/* * Class DigiGraph * * Class to digitize a graph presented as a gif, jpg or png * * WRITTEN BY: Dr Michael Thomas Flanagan * * DATE: September 2006 * UPDATE: 8 October 2006, 2 November 2006, 12 May 2008, 5 July 2008, 3 December 2008 * * DOCUMENTATION: * See Michael T Flanagan's Java library on-line web pages: * http://www.ee.ucl.ac.uk/~mflanaga/java/ * http://www.ee.ucl.ac.uk/~mflanaga/java/DigiGraph.html * * Copyright (c) 2006 - 2008 * * PERMISSION TO COPY: * * Permission to use, copy and modify this software and its documentation for NON-COMMERCIAL purposes is granted, without fee, * provided that an acknowledgement to the author, Dr Michael Thomas Flanagan at www.ee.ucl.ac.uk/~mflanaga, appears in all copies * and associated documentation or publications. * * Redistributions of the source code of this source code, or parts of the source codes, must retain the above copyright notice, * this list of conditions and the following disclaimer and requires written permission from the Michael Thomas Flanagan: * * Redistribution in binary form of all or parts of this class 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 and requires written permission * from the Michael Thomas Flanagan: * * Dr Michael Thomas Flanagan makes no representations about the suitability or fitness of the software for any or for a particular purpose. * Dr Michael Thomas Flanagan shall not be liable for any damages suffered as a result of using, modifying or distributing this software * or its derivatives. * *****************************************************************************************************************************************************/ package flanagan.io; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.util.ArrayList; import java.io.*; import java.net.*; import javax.swing.*; import javax.swing.JFrame; import flanagan.io.*; import flanagan.interpolation.CubicSpline; import flanagan.math.Fmath; import flanagan.plot.PlotGraph; public class DigiGraph extends Canvas implements MouseListener{ private Image pic = null; // image containing graph to be digitised private String imagePath = null; // path, i.e. address\name, of the .png, .gif or .jpg containing graph private String imageName = null; // name of the .gif, .png or .jpg file containing graph private String extension = null; // extension of file name, e.g. gif, png or jpg private String outputFile = null; // output file (containing digitisation values) name private FileOutput fout = null; // output file (containing digitisation values) reference private int trunc = 16; // number of decimal places in output data private String path = "C:"; // path for file selection window private int windowWidth = 0; // width of the window for the graph in pixels private int windowHeight = 0; // height of the window for the graph in pixels private int closeChoice = 1; // =1 clicking on close icon causes window to close // and the the program is exited. // =2 clicking on close icon causes window to close // leaving the program running. private int xPos = 0; // mouse x-axis position (in pixels)on last click private int yPos = 0; // mouse y-axis position (in pixels)on last click private int button = 0; // mouse button last clicked // = 0; no button clicked // = 1; left mouse button last clicked // (= 2; middle mouse button last clicked) // = 3; right mouse button last clicked private int sumX = 0; // sum of xPos in calculation of a calibration point private int sumY = 0; // sum of yPos in calculation of a calibration point private int iSum = 0; // number of xPos and yPos in calculation of a calibration point private boolean mouseEntered = false; // = true when mouse enters object // = false when mouse leaves object private double lowYvalue = 0.0; // Y-axis value (entered as double) of the clicked low Y-axis value private double lowYaxisXpixel = 0.0; // X-axis pixel number of the clicked known low Y-axis value private double lowYaxisYpixel = 0.0; // Y-axis pixel number of the clicked known low Y-axis value private double highYvalue = 0.0; // Y-axis value (entered as double) of the clicked high Y-axis value private double highYaxisXpixel = 0.0; // X-axis pixel number of the clicked known high Y-axis value private double highYaxisYpixel = 0.0; // Y-axis pixel number of the clicked known high Y-axis value private double lowXvalue = 0.0; // X-axis value (entered as double) of the clicked low X-axis value private double lowXaxisXpixel = 0.0; // X-axis pixel number of the clicked known low X-axis value private double lowXaxisYpixel = 0.0; // Y-axis pixel number of the clicked known low X-axis value private double highXvalue = 0.0; // X-axis value (entered as double) of the clicked high X-axis value private double highXaxisXpixel = 0.0; // X-axis pixel number of the clicked known high X-axis value private double highXaxisYpixel = 0.0; // Y-axis pixel number of the clicked known high X-axis value private ArrayList xAndYvalues = new ArrayList(); // ArrayList holding clicked point xPos and yPos values private int iCounter = 0; // counter for clicks or sum of clicks on first for calibration points private double angleXaxis = 0.0; // clockwise angle from normal of x-axis (degrees) private double angleYaxis = 0.0; // clockwise angle from normal of y-axis (degrees) private double angleMean = 0.0; // mean clockwise angle of axes from normal (degrees) private double angleTolerance = 0.0; // tolerance in above angle before a rotation of all points performed // default option is to rotate if angle is not zero private boolean rotationDone = false; // = false: no rotation of points performed // = true: all points have been rotated private double[] xPosPixel = null; // x pixel values converted to double private double[] yPosPixel = null; // y pixel values converted to double private double[] xPositions = null; // Digitized and scaled x values private double[] yPositions = null; // Digitized and scaled y values private int nData = 0; // Number of points digitized (excluding calibration points) private int nInterpPoints = 0; // Nnumber of interpolation points private boolean interpOpt = false; // = true if interpolation requested private double[] xInterp = null; // Interpolated x values private double[] yInterp = null; // Interpolated y values private boolean plotOpt = true; // = false if plot of interpolated data not required private boolean noIdentical = true; // = true - all identical points stripped to one instance of the identical points // = false - all identical points retained private int imageFormat = 0; // = 0 no image file loaded // = 1 GIF format // = 2 JPEG format // = 3 PNG format private boolean digitizationDone = false; // = true when digitization complete private boolean noYlow = true; // = false when lower y-axis calibration point has been entered private boolean noXlow = true; // = false when lower x-axis calibration point has been entered private boolean noYhigh = true; // = false when higher y-axis calibration point has been entered private boolean noXhigh = true; // = false when higher x-axis calibration point has been entered private boolean resize = false; // = true if image is resized // Create the window object private JFrame window = new JFrame("Michael T Flanagan's digitizing program - DigiGraph"); // Constructors // image to be selected from a file select window // window opens on default setting public DigiGraph(){ super(); // Set graph digitizing window size setWindowSize(); // select image selectImage(); // set image setImage(); // Name outputfile outputFileChoice(); // Add the MouseListener addMouseListener(this); } // image to be selected from a file select window // window opens on path (windowPath) provided public DigiGraph(String windowPath){ super(); // Set graph digitizing window size setWindowSize(); // Set window path this.path = windowPath; // select image selectImage(); // set image setImage(); // Name outputfile outputFileChoice(); // Add the MouseListener addMouseListener(this); } // Set graph digitizing window size private void setWindowSize(){ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // get computer screen size this.windowWidth = screenSize.width - 30; // width of the window for the graph in pixels this.windowHeight = screenSize.height - 40; // height of the window for the graph in pixels } // Select graph image private void selectImage(){ // Identify computer String computerName = null; try{ InetAddress localaddress = InetAddress.getLocalHost(); computerName = localaddress.getHostName(); } catch(UnknownHostException e){ System.err.println("Cannot detect local host : " + e); } // Set path to file selection window // Replace "name" by your "computer's name" and C:\\DigiGraphDirectory by the path to the directory containing the image to be digitized // Default path is to the C:\ directory or system default directory if no C drive present if(computerName.equals("name"))this.path = "C:\\DigiGraphDirectory"; // select image file FileChooser fc = new FileChooser(this.path); this.imageName = fc.selectFile(); if(!fc.fileFound()){ System.out.println("Class DigiGraph: No successful selection of an image file occurred"); System.exit(0); } this.imagePath = fc.getPathName(); int lastDot = this.imagePath.lastIndexOf('.'); this.extension = this.imagePath.substring(lastDot+1); if(this.extension.equalsIgnoreCase("gif"))imageFormat=1; if(this.extension.equalsIgnoreCase("jpg"))imageFormat=2; if(this.extension.equalsIgnoreCase("jpeg"))imageFormat=2; if(this.extension.equalsIgnoreCase("jpe"))imageFormat=2; if(this.extension.equalsIgnoreCase("jfif"))imageFormat=2; if(this.extension.equalsIgnoreCase("png"))imageFormat=3; } // Set graph image private void setImage(){ this.pic = Toolkit.getDefaultToolkit().getImage(this.imagePath); } // Name outputfile and set number of decimal places in the output data private void outputFileChoice(){ int posdot = this.imagePath.lastIndexOf('.'); this.outputFile = this.imagePath.substring(0, posdot) + "_digitized.txt"; this.outputFile = Db.readLine("Enter output file name ", this.outputFile); this.fout = new FileOutput(this.outputFile); this.trunc = Db.readInt("Enter number of decimal places required in output data ", this.trunc); } // Reset the number of decimal places in the output data public void setTruncation(int trunc){ this.trunc = trunc; } // Reset tolerance in axis rotation before applying rotation (degrees) public void setRotationTolerance(double tol){ this.angleTolerance = tol; } // Reset option of plotting the data // Prevents a plot of the digitized data and the interpolated data, if interpolation optiion chosen, // from being displayed public void noPlot(){ this.plotOpt = false;; } // Reset path for selection window public void setPath(String path){ this.path = path; } // Reset height of graph window (pixels) public void setWindowHeight(int windowHeight){ this.windowHeight = windowHeight; } // Reset width of graph window (pixels) public void setWindowWidth(int windowWidth){ this.windowWidth = windowWidth; } // Reset close choice public void setCloseChoice(int choice){ this.closeChoice = choice; } // Reset stripping of identical points option // Keep all identical points public void keepIdenticalPoints(){ this.noIdentical = false; } // The paint method to display the graph. public void paint(Graphics g){ // Call graphing method graph(g); } // Set up the window, show graph and digitize public void digitize(){ // Set the initial size of the graph window this.window.setSize(this.windowWidth, this.windowHeight); // Set background colour this.window.getContentPane().setBackground(Color.white); // Choose close box if(this.closeChoice==1){ this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } else{ this.window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); } // Add graph canvas this.window.getContentPane().add("Center", this); // Set the window up this.window.pack(); this.window.setResizable(true); this.window.toFront(); // Show the window this.window.setVisible(true); } // Set up the window, show graph and digitize (alternate spelling) public void digitise(){ this.digitize(); } // Display graph and get coordinates private void graph(Graphics g){ // Display graph to be digitized g.drawImage(this.pic, 10, 30, this); if(!this.resize){ g.drawString("RIGHT click anywhere on the screen", 5, 10); int width = this.pic.getWidth(null); int height = this.pic.getHeight(null); System.out.println(width + " xxx " + height); g.drawString(" ", 5, 10); double factor = (double)(windowHeight-30)/(double)height; if((int)(width*factor)>(windowWidth-10))factor = (double)(windowWidth-10)/(double)width; height = (int)((height-30)*factor*0.95); width = (int)((width-10)*factor+0.95); this.pic = this.pic.getScaledInstance(width, height, Image.SCALE_DEFAULT); g.drawImage(this.pic, 10, 30, this); this.resize=true; } // Displays cross at pixel coordinates clicked boolean test=true; if(this.xPos==0 && this.yPos==0)test=false; if(test)cursorDoneSign(g, xPos, yPos); // Shows action required at top left of window // and opens dialog box to input calibration points if(!this.digitizationDone){ switch(this.iCounter){ case 0: g.drawString("RIGHT click on lower Y-axis calibration point", 5, 10); break; case 1: if(this.noYlow){ this.lowYvalue = Db.readDouble("Enter lower Y-axis calibration value"); this.noYlow = false; } g.drawString("RIGHT click on higher Y-axis calibration point", 5, 10); break; case 2: if(this.noYhigh){ this.highYvalue = Db.readDouble("Enter higher Y-axis calibration value"); this.noYhigh = false; } g.drawString("RIGHT click on lower X-axis calibration point", 5, 10); break; case 3: if(this.noXlow){ this.lowXvalue = Db.readDouble("Enter lower X-axis calibration value"); this.noXlow = false; } g.drawString("RIGHT click on higher X-axis calibration point", 5, 10); break; case 4: if(this.noXhigh){ this.highXvalue = Db.readDouble("Enter higher X-axis calibration value"); this.noXhigh = false; } g.drawString("LEFT click on points to be digitized [right click when finished digitizing]", 5, 10); break; default:g.drawString("LEFT click on points to be digitized [right click when finished digitizing]", 5, 10); } } else{ g.drawString("You may now close this window", 5, 10); } } private void cursorDoneSign(Graphics g, int x, int y){ g.drawLine(x-5, y, x+5, y); g.drawLine(x, y-5, x, y+5); g.fillOval(x-3, y-3, 7, 7); } // This method will be called when the mouse has been clicked. public void mouseClicked(MouseEvent me) { if(!this.digitizationDone){ switch(this.iCounter){ // Low y-axis calibration point case 0: this.xPos = me.getX(); this.yPos = me.getY(); // identify left (1) or right (3) hand mouse click this.button = me.getButton(); // add to sum if(this.button==1){ this.sumX += this.xPos; this.sumY += this.yPos; this.iSum++; } else if(this.button==3){ this.sumX += this.xPos; this.sumY += this.yPos; this.iSum++; this.lowYaxisXpixel = (double)this.sumX/(double)this.iSum; this.lowYaxisYpixel = (double)this.windowHeight - (double)this.sumY/(double)this.iSum; this.iCounter++; this.sumX = 0; this.sumY = 0; this.iSum = 0; } break; // High y-axis calibration point case 1: this.xPos = me.getX(); this.yPos = me.getY(); // identify left (1) or right (3) hand mouse click this.button = me.getButton(); // add to sum if(this.button==1){ this.sumX += this.xPos; this.sumY += this.yPos; this.iSum++; } else if(this.button==3){ this.sumX += this.xPos; this.sumY += this.yPos; this.iSum++; this.highYaxisXpixel = (double)this.sumX/(double)this.iSum; this.highYaxisYpixel = (double)this.windowHeight - (double)this.sumY/(double)this.iSum; this.iCounter++; this.sumX = 0; this.sumY = 0; this.iSum = 0; } break; // Low x-axis calibration point case 2: this.xPos = me.getX(); this.yPos = me.getY(); // identify left (1) or right (3) hand mouse click this.button = me.getButton(); // add to sum if(this.button==1){ this.sumX += this.xPos; this.sumY += this.yPos; this.iSum++; } else if(this.button==3){ this.sumX += this.xPos; this.sumY += this.yPos; this.iSum++; this.lowXaxisXpixel = (double)this.sumX/(double)this.iSum; this.lowXaxisYpixel = (double)this.windowHeight - (double)this.sumY/(double)this.iSum; this.iCounter++; this.sumX = 0; this.sumY = 0; this.iSum = 0; } break; // High x-axis calibration point case 3: this.xPos = me.getX(); this.yPos = me.getY(); // identify left (1) or right (3) hand mouse click this.button = me.getButton(); // add to sum PixelGrabber pixelGrabber=new PixelGrabber(pic, this.xPos, this.yPos, 1, 1, false); if(this.button==1){ this.sumX += this.xPos; this.sumY += this.yPos; this.iSum++; } else if(this.button==3){ this.sumX += this.xPos; this.sumY += this.yPos; this.iSum++; this.highXaxisXpixel = (double)this.sumX/(double)this.iSum; this.highXaxisYpixel = (double)this.windowHeight - (double)this.sumY/(double)this.iSum; this.iCounter++; this.sumX = 0; this.sumY = 0; this.iSum = 0; } break; // Data points default: this.xPos = me.getX(); this.yPos = me.getY(); // identify left (1) or right (3) hand mouse click this.button = me.getButton(); if(this.button==1){ this.xAndYvalues.add(new Integer(this.xPos)); this.xAndYvalues.add(new Integer(this.yPos)); } // close file if right button clicked if(this.button==3 && this.xAndYvalues.size()/2!=0){ this.outputData(); this.digitizationDone = true; } } } //show the results of the click repaint(); } // Output data to file and to graph private void outputData(){ // dimension arrays this.nData = this.xAndYvalues.size()/2; System.out.println("nData " + this.nData); this.xPositions = new double[this.nData]; this.yPositions = new double[this.nData]; this.xPosPixel = new double[this.nData]; this.yPosPixel = new double[this.nData]; int ii = 0; // Convert pixel values to doubles for(int i=0; ithis.angleTolerance)performRotation(); } // Rotate axes and all points private void performRotation(){ // Find pixel zero-zero origin double tangentX = (this.highXaxisYpixel - this.lowXaxisYpixel)/(this.highXaxisXpixel - this.lowXaxisXpixel); double interceptX = this.highXaxisYpixel - tangentX*this.highXaxisXpixel; double tangentY = (this.highYaxisYpixel - this.lowYaxisYpixel)/(this.highYaxisXpixel - this.lowYaxisXpixel); double interceptY = this.highYaxisYpixel - tangentY*this.highYaxisXpixel; double originX = (interceptX - interceptY)/(tangentY - tangentX); double originY = tangentY*originX + interceptY; // Rotate axes calibration points double angleMeanRad = Math.toRadians(this.angleMean); double cosphi = Math.cos(-angleMeanRad); double sinphi = Math.sin(-angleMeanRad); double highXaxisXpixelR = (this.highXaxisXpixel-originX)*cosphi + (this.highXaxisYpixel-originY)*sinphi + originX; double highXaxisYpixelR = -(this.highXaxisXpixel-originX)*sinphi + (this.highXaxisYpixel-originY)*cosphi + originY; double lowXaxisXpixelR = (this.lowXaxisXpixel-originX)*cosphi + (this.lowXaxisYpixel-originY)*sinphi + originX; double lowXaxisYpixelR = -(this.lowXaxisXpixel-originX)*sinphi + (this.lowXaxisYpixel-originY)*cosphi + originY; double highYaxisXpixelR = (this.highYaxisXpixel-originX)*cosphi + (this.highYaxisYpixel-originY)*sinphi + originX; double highYaxisYpixelR = -(this.highYaxisXpixel-originX)*sinphi + (this.highYaxisYpixel-originY)*cosphi + originY; double lowYaxisXpixelR = -(this.lowYaxisXpixel-originX)*cosphi + (this.lowYaxisYpixel-originY)*sinphi + originX; double lowYaxisYpixelR = (this.lowYaxisXpixel-originX)*sinphi + (this.lowYaxisYpixel-originY)*cosphi + originY; this.highXaxisXpixel = highXaxisXpixelR; this.highXaxisYpixel = highXaxisYpixelR; this.lowXaxisXpixel = lowXaxisXpixelR; this.lowXaxisYpixel = lowXaxisYpixelR; this.highYaxisXpixel = highYaxisXpixelR; this.highYaxisYpixel = highYaxisYpixelR; this.lowYaxisXpixel = lowYaxisXpixelR; this.lowYaxisYpixel = lowYaxisYpixelR; // Rotate data points for(int i=0; i=nP)test2 = false; } } ii++; if(ii>=nP-1)test1 = false; } // Repack arrays if points deleted if(nP!=this.nData){ double[] holdX = new double[nP]; double[] holdY = new double[nP]; for(int i=0; i