Package ij.plugin

Source Code of ij.plugin.Selection

package ij.plugin;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.*;
import ij.plugin.frame.*;
import ij.macro.Interpreter;
import ij.plugin.filter.GaussianBlur;
import ij.plugin.filter.ThresholdToSelection;
import ij.util.Tools;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.Vector;


/** This plugin implements the commands in the Edit/Section submenu. */
public class Selection implements PlugIn, Measurements {
  private ImagePlus imp;
  private float[] kernel = {1f, 1f, 1f, 1f, 1f};
  private float[] kernel3 = {1f, 1f, 1f};
  private static String angle = "15"; // degrees
  private static String enlarge = "15"; // pixels
  private static int bandSize = 15; // pixels
  private static boolean nonScalable;
  private static Color linec, fillc;
  private static int lineWidth = 1;
 

  public void run(String arg) {
    imp = WindowManager.getCurrentImage();
      if (arg.equals("add"))
        {addToRoiManager(imp); return;}
    if (imp==null)
      {IJ.noImage(); return;}
      if (arg.equals("all"))
        imp.setRoi(0,0,imp.getWidth(),imp.getHeight());
      else if (arg.equals("none"))
        imp.killRoi();
      else if (arg.equals("restore"))
        imp.restoreRoi();
      else if (arg.equals("spline"))
        fitSpline();
      else if (arg.equals("circle"))
        fitCircle(imp);
      else if (arg.equals("ellipse"))
        createEllipse(imp);
      else if (arg.equals("hull"))
        convexHull(imp);
      else if (arg.equals("mask"))
        createMask(imp);     
       else if (arg.equals("from"))
        createSelectionFromMask(imp);     
      else if (arg.equals("inverse"))
        invert(imp);
      else if (arg.equals("tobox"))
        toBoundingBox(imp);
       else if (arg.equals("toarea"))
        lineToArea(imp);
       else if (arg.equals("toline"))
        areaToLine(imp);
       else if (arg.equals("properties"))
        {setProperties("Properties", imp.getRoi()); imp.draw();}
     else if (arg.equals("band"))
      makeBand(imp);
    else
      runMacro(arg);
  }
 
  void runMacro(String arg) {
    Roi roi = imp.getRoi();
    if (IJ.macroRunning()) {
      String options = Macro.getOptions();
      if (options!=null && (options.indexOf("grid=")!=-1||options.indexOf("interpolat")!=-1)) {
        IJ.run("Rotate... ", options); // run Image>Transform>Rotate
        return;
      }
    }
    if (roi==null) {
      IJ.error("Rotate>Selection", "This command requires a selection");
      return;
    }
    roi = (Roi)roi.clone();
    if (arg.equals("rotate")) {
      double d = Tools.parseDouble(angle);
      if (Double.isNaN(d)) angle = "15";
      String value = IJ.runMacroFile("ij.jar:RotateSelection", angle);
      if (value!=null) angle = value;     
    } else if (arg.equals("enlarge")) {
      String value = IJ.runMacroFile("ij.jar:EnlargeSelection", enlarge);
      if (value!=null) enlarge = value;
      Roi.previousRoi = roi;
    }
  }
 
  /*
  if selection is closed shape, create a circle with the same area and centroid, otherwise use<br>
  the Pratt method to fit a circle to the points that define the line or multi-point selection.<br>
  Reference: Pratt V., Direct least-squares fitting of algebraic surfaces", Computer Graphics, Vol. 21, pages 145-152 (1987).<br>
  Original code: Nikolai Chernov's MATLAB script for Newton-based Pratt fit.<br>
  (http://www.math.uab.edu/~chernov/cl/MATLABcircle.html)<br>
  Java version: https://github.com/mdoube/BoneJ/blob/master/src/org/doube/geometry/FitCircle.java<br>
  @authors Nikolai Chernov, Michael Doube, Ved Sharma
  */
  void fitCircle(ImagePlus imp) {
    Roi roi = imp.getRoi();
    if (roi==null) {
      IJ.error("Fit Circle", "Selection required");
      return;
    }
   
    if (roi.isArea()) {   //create circle with the same area and centroid
      ImageProcessor ip = imp.getProcessor();
      ip.setRoi(roi);
      ImageStatistics stats = ImageStatistics.getStatistics(ip, Measurements.AREA+Measurements.CENTROID, null);
      double r = Math.sqrt(stats.pixelCount/Math.PI);
      imp.killRoi();
      int d = (int)Math.round(2.0*r);
      IJ.makeOval((int)Math.round(stats.xCentroid-r), (int)Math.round(stats.yCentroid-r), d, d);
      return;
    }
   
    Polygon poly = roi.getPolygon();
    int n=poly.npoints;
    int[] x = poly.xpoints;
    int[] y = poly.ypoints;
    if (n<3) {
      IJ.error("Fit Circle", "At least 3 points are required to fit a circle.");
      return;
    }
   
    // calculate point centroid
    double sumx = 0, sumy = 0;
    for (int i=0; i<n; i++) {
      sumx = sumx + poly.xpoints[i];
      sumy = sumy + poly.ypoints[i];
    }
    double meanx = sumx/n;
    double meany = sumy/n;
   
    // calculate moments
    double[] X = new double[n], Y = new double[n];
    double Mxx=0, Myy=0, Mxy=0, Mxz=0, Myz=0, Mzz=0;
    for (int i=0; i<n; i++) {
      X[i] = x[i] - meanx;
      Y[i] = y[i] - meany;
      double Zi = X[i]*X[i] + Y[i]*Y[i];
      Mxy = Mxy + X[i]*Y[i];
      Mxx = Mxx + X[i]*X[i];
      Myy = Myy + Y[i]*Y[i];
      Mxz = Mxz + X[i]*Zi;
      Myz = Myz + Y[i]*Zi;
      Mzz = Mzz + Zi*Zi;
    }
    Mxx = Mxx/n;
    Myy = Myy/n;
    Mxy = Mxy/n;
    Mxz = Mxz/n;
    Myz = Myz/n;
    Mzz = Mzz/n;
   
    // calculate the coefficients of the characteristic polynomial
    double Mz = Mxx + Myy;
    double Cov_xy = Mxx*Myy - Mxy*Mxy;
    double Mxz2 = Mxz*Mxz;
    double Myz2 = Myz*Myz;
    double A2 = 4*Cov_xy - 3*Mz*Mz - Mzz;
    double A1 = Mzz*Mz + 4*Cov_xy*Mz - Mxz2 - Myz2 - Mz*Mz*Mz;
    double A0 = Mxz2*Myy + Myz2*Mxx - Mzz*Cov_xy - 2*Mxz*Myz*Mxy + Mz*Mz*Cov_xy;
    double A22 = A2 + A2;
    double epsilon = 1e-12;
    double ynew = 1e+20;
    int IterMax= 20;
    double xnew = 0;
    int iterations = 0;
   
    // Newton's method starting at x=0
    for (int iter=1; iter<=IterMax; iter++) {
      iterations = iter;
      double yold = ynew;
      ynew = A0 + xnew*(A1 + xnew*(A2 + 4.*xnew*xnew));
      if (Math.abs(ynew)>Math.abs(yold)) {
        if (IJ.debugMode) IJ.log("Fit Circle: wrong direction: |ynew| > |yold|");
        xnew = 0;
        break;
      }
      double Dy = A1 + xnew*(A22 + 16*xnew*xnew);
      double xold = xnew;
      xnew = xold - ynew/Dy;
      if (Math.abs((xnew-xold)/xnew) < epsilon)
        break;
      if (iter >= IterMax) {
        if (IJ.debugMode) IJ.log("Fit Circle: will not converge");
        xnew = 0;
      }
      if (xnew<0) {
        if (IJ.debugMode) IJ.log("Fit Circle: negative root:  x = "+xnew);
        xnew = 0;
      }
    }
    if (IJ.debugMode) IJ.log("Fit Circle: n="+n+", xnew="+IJ.d2s(xnew,2)+", iterations="+iterations);
   
    // calculate the circle parameters
    double DET = xnew*xnew - xnew*Mz + Cov_xy;
    double CenterX = (Mxz*(Myy-xnew)-Myz*Mxy)/(2*DET);
    double CenterY = (Myz*(Mxx-xnew)-Mxz*Mxy)/(2*DET);
    double radius = Math.sqrt(CenterX*CenterX + CenterY*CenterY + Mz + 2*xnew);
    if (Double.isNaN(radius)) {
      IJ.error("Fit Circle", "Points are collinear.");
      return;
    }
    CenterX = CenterX + meanx;
    CenterY = CenterY + meany;
    imp.killRoi();
    IJ.makeOval((int)Math.round(CenterX-radius), (int)Math.round(CenterY-radius), (int)Math.round(2*radius), (int)Math.round(2*radius));
  }

  void fitSpline() {
    Roi roi = imp.getRoi();
    if (roi==null)
      {IJ.error("Spline", "Selection required"); return;}
    int type = roi.getType();
    boolean segmentedSelection = type==Roi.POLYGON||type==Roi.POLYLINE;
    if (!(segmentedSelection||type==Roi.FREEROI||type==Roi.TRACED_ROI||type==Roi.FREELINE))
      {IJ.error("Spline", "Polygon or polyline selection required"); return;}
    if (roi instanceof EllipseRoi)
      return;
    PolygonRoi p = (PolygonRoi)roi;
    if (!segmentedSelection)
      p = trimPolygon(p, p.getUncalibratedLength());
    String options = Macro.getOptions();
    if (options!=null && options.indexOf("straighten")!=-1)
      p.fitSplineForStraightening();
    else if (options!=null && options.indexOf("remove")!=-1)
      p.removeSplineFit();
    else
      p.fitSpline();
    imp.draw();
    LineWidthAdjuster.update()
  }
 
  PolygonRoi trimPolygon(PolygonRoi roi, double length) {
    int[] x = roi.getXCoordinates();
    int[] y = roi.getYCoordinates();
    int n = roi.getNCoordinates();
    x = smooth(x, n);
    y = smooth(y, n);
    float[] curvature = getCurvature(x, y, n);
    Rectangle r = roi.getBounds();
    double threshold = rodbard(length);
    //IJ.log("trim: "+length+" "+threshold);
    double distance = Math.sqrt((x[1]-x[0])*(x[1]-x[0])+(y[1]-y[0])*(y[1]-y[0]));
    x[0] += r.x; y[0]+=r.y;
    int i2 = 1;
    int x1,y1,x2=0,y2=0;
    for (int i=1; i<n-1; i++) {
      x1=x[i]; y1=y[i]; x2=x[i+1]; y2=y[i+1];
      distance += Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)) + 1;
      distance += curvature[i]*2;
      if (distance>=threshold) {
        x[i2] = x2 + r.x;
        y[i2] = y2 + r.y;
        i2++;
        distance = 0.0;
      }
    }
    int type = roi.getType()==Roi.FREELINE?Roi.POLYLINE:Roi.POLYGON;
    if (type==Roi.POLYLINE && distance>0.0) {
      x[i2] = x2 + r.x;
      y[i2] = y2 + r.y;
      i2++;
    }   
    PolygonRoi p = new PolygonRoi(x, y, i2, type);
    imp.setRoi(p);
    return p;
  }
 
    double rodbard(double x) {
      // y = c*((a-x/(x-d))^(1/b)
      // a=3.9, b=.88, c=712, d=44
    double ex;
    if (x == 0.0)
      ex = 5.0;
    else
      ex = Math.exp(Math.log(x/700.0)*0.88);
    double y = 3.9-44.0;
    y = y/(1.0+ex);
    return y+44.0;
    }

  int[] smooth(int[] a, int n) {
    FloatProcessor fp = new FloatProcessor(n, 1);
    for (int i=0; i<n; i++)
      fp.putPixelValue(i, 0, a[i]);
    GaussianBlur gb = new GaussianBlur();
    gb.blur1Direction(fp, 2.0, 0.01, true, 0);
    for (int i=0; i<n; i++)
      a[i] = (int)Math.round(fp.getPixelValue(i, 0));
    return a;
  }
 
  float[] getCurvature(int[] x, int[] y, int n) {
    float[] x2 = new float[n];
    float[] y2 = new float[n];
    for (int i=0; i<n; i++) {
      x2[i] = x[i];
      y2[i] = y[i];
    }
    ImageProcessor ipx = new FloatProcessor(n, 1, x2, null);
    ImageProcessor ipy = new FloatProcessor(n, 1, y2, null);
    ipx.convolve(kernel, kernel.length, 1);
    ipy.convolve(kernel, kernel.length, 1);
    float[] indexes = new float[n];
    float[] curvature = new float[n];
    for (int i=0; i<n; i++) {
      indexes[i] = i;
      curvature[i] = (float)Math.sqrt((x2[i]-x[i])*(x2[i]-x[i])+(y2[i]-y[i])*(y2[i]-y[i]));
    }
    return curvature;
  }
 
  void createEllipse(ImagePlus imp) {
    IJ.showStatus("Fitting ellipse");
    Roi roi = imp.getRoi();
    if (roi==null)
      {IJ.error("Fit Ellipse", "Selection required"); return;}
    if (roi.isLine())
      {IJ.error("Fit Ellipse", "\"Fit Ellipse\" does not work with line selections"); return;}
    ImageStatistics stats = imp.getStatistics(Measurements.CENTROID+Measurements.ELLIPSE);
    double dx = stats.major*Math.cos(stats.angle/180.0*Math.PI)/2.0;
    double dy = - stats.major*Math.sin(stats.angle/180.0*Math.PI)/2.0;
    double x1 = stats.xCentroid - dx;
    double x2 = stats.xCentroid + dx;
    double y1 = stats.yCentroid - dy;
    double y2 = stats.yCentroid + dy;
    double aspectRatio = stats.minor/stats.major;
    imp.killRoi();
    imp.setRoi(new EllipseRoi(x1,y1,x2,y2,aspectRatio));
  }

  void convexHull(ImagePlus imp) {
    Roi roi = imp.getRoi();
    int type = roi!=null?roi.getType():-1;
    if (!(type==Roi.FREEROI||type==Roi.TRACED_ROI||type==Roi.POLYGON||type==Roi.POINT))
      {IJ.error("Convex Hull", "Polygonal or point selection required"); return;}
    if (roi instanceof EllipseRoi)
      return;
    Polygon p = roi.getConvexHull();
    if (p!=null)
      imp.setRoi(new PolygonRoi(p.xpoints, p.ypoints, p.npoints, roi.POLYGON));
  }
 
  // Finds the index of the upper right point that is guaranteed to be on convex hull
  int findFirstPoint(int[] xCoordinates, int[] yCoordinates, int n, ImagePlus imp) {
    int smallestY = imp.getHeight();
    int x, y;
    for (int i=0; i<n; i++) {
      y = yCoordinates[i];
      if (y<smallestY)
      smallestY = y;
    }
    int smallestX = imp.getWidth();
    int p1 = 0;
    for (int i=0; i<n; i++) {
      x = xCoordinates[i];
      y = yCoordinates[i];
      if (y==smallestY && x<smallestX) {
        smallestX = x;
        p1 = i;
      }
    }
    return p1;
  }
 
  void createMask(ImagePlus imp) {
    Roi roi = imp.getRoi();
    boolean useInvertingLut = Prefs.useInvertingLut;
    Prefs.useInvertingLut = false;
    if (roi==null || !(roi.isArea()||roi.getType()==Roi.POINT)) {
      createMaskFromThreshold(imp);
      Prefs.useInvertingLut = useInvertingLut;
      return;
    }
    ImagePlus maskImp = null;
    Frame frame = WindowManager.getFrame("Mask");
    if (frame!=null && (frame instanceof ImageWindow))
      maskImp = ((ImageWindow)frame).getImagePlus();
    if (maskImp==null) {
      ImageProcessor ip = new ByteProcessor(imp.getWidth(), imp.getHeight());
      if (!Prefs.blackBackground)
        ip.invertLut();
      maskImp = new ImagePlus("Mask", ip);
      maskImp.show();
    }
    ImageProcessor ip = maskImp.getProcessor();
    ip.setRoi(roi);
    ip.setValue(255);
    ip.fill(ip.getMask());
    maskImp.updateAndDraw();
    Prefs.useInvertingLut = useInvertingLut;
  }
 
  void createMaskFromThreshold(ImagePlus imp) {
    ImageProcessor ip = imp.getProcessor();
    if (ip.getMinThreshold()==ImageProcessor.NO_THRESHOLD)
      {IJ.error("Create Mask", "Area selection or thresholded image required"); return;}
    double t1 = ip.getMinThreshold();
    double t2 = ip.getMaxThreshold();
    IJ.run("Duplicate...", "title=mask");
    ImagePlus imp2 = WindowManager.getCurrentImage();
    ImageProcessor ip2 = imp2.getProcessor();
    ip2.setThreshold(t1, t2, ImageProcessor.NO_LUT_UPDATE);
    IJ.run("Convert to Mask");
  }

  void createSelectionFromMask(ImagePlus imp) {
    ImageProcessor ip = imp.getProcessor();
    if (ip.getMinThreshold()!=ImageProcessor.NO_THRESHOLD) {
      IJ.runPlugIn("ij.plugin.filter.ThresholdToSelection", "");
      return;
    }
    if (!ip.isBinary()) {
      IJ.error("Create Selection",
        "This command creates a composite selection from\n"+
        "a mask (8-bit binary image with white background)\n"+
        "or from an image that has been thresholded using\n"+
        "the Image>Adjust>Threshold tool. The current\n"+
        "image is not a mask and has not been thresholded.");
      return;
    }
    int threshold = ip.isInvertedLut()?255:0;
    ip.setThreshold(threshold, threshold, ImageProcessor.NO_LUT_UPDATE);
    IJ.runPlugIn("ij.plugin.filter.ThresholdToSelection", "");
  }

  void invert(ImagePlus imp) {
    Roi roi = imp.getRoi();
    if (roi==null || !roi.isArea())
      {IJ.error("Inverse", "Area selection required"); return;}
    ShapeRoi s1, s2;
    if (roi instanceof ShapeRoi)
      s1 = (ShapeRoi)roi;
    else
      s1 = new ShapeRoi(roi);
    s2 = new ShapeRoi(new Roi(0,0, imp.getWidth(), imp.getHeight()));
    imp.setRoi(s1.xor(s2));
  }
 
  void lineToArea(ImagePlus imp) {
    Roi roi = imp.getRoi();
    if (roi==null || !roi.isLine())
      {IJ.error("Line to Area", "Line selection required"); return;}
    if (roi.getType()==Roi.LINE && roi.getStrokeWidth()==1)
      {IJ.error("Line to Area", "Straight line width must be > 1"); return;}
    ImageProcessor ip2 = new ByteProcessor(imp.getWidth(), imp.getHeight());
    ip2.setColor(255);
    if (roi.getType()==Roi.LINE)
      ip2.fillPolygon(roi.getPolygon());
    else {
      roi.drawPixels(ip2);
      //BufferedImage bi = new BufferedImage(imp.getWidth(), imp.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
      //Graphics g = bi.getGraphics();
      //Roi roi2 = (Roi)roi.clone();
      //roi2.setStrokeColor(Color.white);
      //roi2.drawOverlay(g);
      //ip2 = new ByteProcessor(bi);
    }
    //new ImagePlus("ip2", ip2.duplicate()).show();
    ip2.setThreshold(255, 255, ImageProcessor.NO_LUT_UPDATE);
    ThresholdToSelection tts = new ThresholdToSelection();
    Roi roi2 = tts.convert(ip2);
    imp.setRoi(roi2);
    Roi.previousRoi = (Roi)roi.clone();
  }
 
  void areaToLine(ImagePlus imp) {
    Roi roi = imp.getRoi();
    if (roi==null || !roi.isArea()) {
      IJ.error("Area to Line", "Area selection required");
      return;
    }
    Polygon p = roi.getPolygon();
    if (p==null) return;
    int type1 = roi.getType();
    if (type1==Roi.COMPOSITE) {
      IJ.error("Area to Line", "Composite selections cannot be converted to lines.");
      return;
    }
    int type2 = Roi.POLYLINE;
    if (type1==Roi.OVAL||type1==Roi.FREEROI||type1==Roi.TRACED_ROI
    ||((roi instanceof PolygonRoi)&&((PolygonRoi)roi).isSplineFit()))
      type2 = Roi.FREELINE;
    Roi roi2 = new PolygonRoi(p.xpoints, p.ypoints, p.npoints, type2);
    imp.setRoi(roi2);
  }

  void toBoundingBox(ImagePlus imp) {
    Roi roi = imp.getRoi();
    Rectangle r = roi.getBounds();
    imp.killRoi();
    imp.setRoi(new Roi(r.x, r.y, r.width, r.height));
  }

  void addToRoiManager(ImagePlus imp) {
    if (IJ.macroRunning() &&  Interpreter.isBatchModeRoiManager())
      IJ.error("run(\"Add to Manager\") may not work in batch mode macros");
    Frame frame = WindowManager.getFrame("ROI Manager");
    if (frame==null)
      IJ.run("ROI Manager...");
    if (imp==null) return;
    Roi roi = imp.getRoi();
    if (roi==null) return;
    frame = WindowManager.getFrame("ROI Manager");
    if (frame==null || !(frame instanceof RoiManager))
      IJ.error("ROI Manager not found");
    RoiManager rm = (RoiManager)frame;
    boolean altDown= IJ.altKeyDown();
    IJ.setKeyUp(IJ.ALL_KEYS);
    if (altDown && !IJ.macroRunning())
      IJ.setKeyDown(KeyEvent.VK_SHIFT);
    rm.runCommand("add");
    IJ.setKeyUp(IJ.ALL_KEYS);
  }
 
  boolean setProperties(String title, Roi roi) {
    Frame f = WindowManager.getFrontWindow();
    if (f!=null && f.getTitle().indexOf("3D Viewer")!=-1)
      return false;
    if (roi==null) {
      IJ.error("This command requires a selection.");
      return false;
    }
    RoiProperties rp = new RoiProperties(title, roi);
    return rp.showDialog();
  }
 
  private void makeBand(ImagePlus imp) {
    Roi roi = imp.getRoi();
    if (roi==null) {
      IJ.error("Make Band", "Selection required");
      return;
    }
    if (!roi.isArea()) {
      IJ.error("Make Band", "Area selection required");
      return;
    }
    Calibration cal = imp.getCalibration();
    double pixels = bandSize;
    double size = pixels*cal.pixelWidth;
    int decimalPlaces = 0;
    if ((int)size!=size)
      decimalPlaces = 2;
    GenericDialog gd = new GenericDialog("Make Band");
    gd.addNumericField("Band Size:", size, decimalPlaces, 4, cal.getUnits());
    gd.showDialog();
    if (gd.wasCanceled())
          return;
    size = gd.getNextNumber();
    if (Double.isNaN(size)) {
      IJ.error("Make Band", "invalid number");
      return;
    }
    int n = (int)Math.round(size/cal.pixelWidth);
    if (n >255) {
      IJ.error("Make Band", "Cannot make bands wider that 255 pixels");
      return;
    }
    int width = imp.getWidth();
    int height = imp.getHeight();
    Rectangle r = roi.getBounds();
    ImageProcessor ip = roi.getMask();
    if (ip==null) {
      ip = new ByteProcessor(r.width, r.height);
      ip.invert();
    }
    ImageProcessor mask = new ByteProcessor(width, height);
    mask.insert(ip, r.x, r.y);
    ImagePlus edm = new ImagePlus("mask", mask);
    boolean saveBlackBackground = Prefs.blackBackground;
    Prefs.blackBackground = false;
    IJ.run(edm, "Distance Map", "");
    Prefs.blackBackground = saveBlackBackground;
    ip = edm.getProcessor();
    ip.setThreshold(0, n, ImageProcessor.NO_LUT_UPDATE);
    int xx=-1, yy=-1;
    for (int x=r.x; x<r.x+r.width; x++) {
      for (int y=r.y; y<r.y+r.height; y++) {
        if (ip.getPixel(x, y)<n) {
          xx=x; yy=y;
          break;
        }
      }
      if (xx>=0||yy>=0)
        break;
    }
    int count = IJ.doWand(edm, xx, yy, 0, null);
    if (count<=0) {
      IJ.error("Make Band", "Unable to make band");
      return;
    }
    ShapeRoi roi2 = new ShapeRoi(edm.getRoi());
    if (!(roi instanceof ShapeRoi))
      roi = new ShapeRoi(roi);
    ShapeRoi roi1 = (ShapeRoi)roi;
    roi2 = roi2.not(roi1);
    imp.setRoi(roi2);
    bandSize = n;
  }

}
TOP

Related Classes of ij.plugin.Selection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.