ThreeB 1.1

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

three_B.java
Go to the documentation of this file.

    00001 import ij.*;
    00002 import ij.process.*;
    00003 import ij.gui.*;
    00004 import java.awt.*;
    00005 import ij.plugin.*;
    00006 import ij.plugin.frame.*;
    00007 import ij.plugin.filter.PlugInFilter;
    00008 import ij.*;
    00009 import ij.io.*;
    00010 import ij.plugin.*;
    00011 import ij.plugin.filter.*;
    00012
    00013 import java.awt.event.*;
    00014 import java.awt.geom.*;
    00015 import java.util.*;
    00016 import java.net.*;
    00017 import java.io.*;
    00018 import javax.swing.*;
    00019 import javax.swing.event.*;
    00020 import java.lang.InterruptedException;
    00021 import java.lang.System;
    00022 import java.lang.Math.*;
    00023 import java.lang.reflect.*;
    00024
    00025 //Please accept my apologies for poor Java code.
    00026 //This is my first ever Java program.
    00027
    00028
    00029 class ThreeBGlobalConstants
    00030 {
    00031     public static int critical_iterations=200;
    00032 };
    00033
    00034
    00035 ///Utility class to hold a pair of strings
    00036 ///
    00037 ///@ingroup gPlugin
    00038 class SPair
    00039 {
    00040     public String a, b;
    00041 }
    00042
    00043 ///Utility calss to hold a number of handy static functions.
    00044 ///@ingroup gPlugin
    00045 class Util
    00046 {
    00047     ///Read a file into a string. It simply sidcards errors
    00048     ///because if the file is missing from the JAR archive,
    00049     ///then extreme badness has happened and I have no idea how to
    00050     ///recover.
    00051     ///@param in File to be read
    00052     ///@returns file contents in a string
    00053     static String read(Reader in)
    00054     {
    00055         try {
    00056             final char[] buffer = new char[100];
    00057             StringBuilder out = new StringBuilder();
    00058             int read;
    00059             do
    00060             {
    00061                 read = in.read(buffer, 0, buffer.length);
    00062                 if (read>0)
    00063                     out.append(buffer, 0, read);
    00064             } while (read>=0);
    00065             return out.toString();
    00066         }
    00067         catch(java.io.IOException err)
    00068         {
    00069             return "";
    00070             //What do we do here?
    00071         }
    00072     }
    00073
    00074     ///The a file name for saving and complete path using ImageJ's file open dialog.
    00075     ///Kep re-querying user if the file will be overwritten. Windows already provides
    00076     ///the query built in.
    00077     ///@ingroup gPlugin
    00078     ///@param t Initial file name
    00079     ///@returns filename and full path
    00080     static SPair getFileName(String t)
    00081     {
    00082         //Get a filename to save as, with appropriate warnings for
    00083         //overwriting files.
    00084         String fname, fullname;
    00085         while(true)
    00086         {
    00087             SaveDialog save = new SaveDialog("Save 3B output", t, ".txt");
    00088             fname = save.getFileName();
    00089
    00090             fullname = save.getDirectory() + File.separator + fname;
    00091
    00092             if(fname == null)
    00093                 break;
    00094
    00095             File test = new File(fullname);
    00096             //Windows' open dialog seems to do overwrite confirmation automatically,
    00097             //so there is no need to do it here.
    00098             if(!ij.IJ.isWindows() && test.exists())
    00099             {
    00100                 GenericDialog g = new GenericDialog("Overwrite file?");
    00101                 g.addMessage("The file \"" + fname + "\" already exists. Continue and overwrite?");
    00102                 g.enableYesNoCancel("Yes", "No");
    00103                 g.showDialog();
    00104
    00105                 if(g.wasOKed())
    00106                     break;
    00107                 else if(g.wasCanceled())
    00108                 {
    00109                     fname = null;
    00110                     break;
    00111                 }
    00112             }
    00113             else
    00114                 break;
    00115         }
    00116
    00117         SPair r = new SPair();
    00118         r.a = fname;
    00119         r.b = fullname;
    00120         return r;
    00121     }
    00122 }
    00123
    00124
    00125
    00126
    00127
    00128 //ImageJ  plugins must have an _ in the name. lolwut?
    00129
    00130 ///ImageJ plugin class
    00131 ///@ingroup gPlugin
    00132 public class three_B implements PlugInFilter {
    00133
    00134     ImagePlus window;
    00135     ByteProcessor mask;
    00136     String arg;
    00137
    00138     public int setup(String arg_, ImagePlus img) {
    00139         window = img;
    00140         arg=arg_;
    00141
    00142         return ROI_REQUIRED + SUPPORTS_MASKING + STACK_REQUIRED +NO_CHANGES + DOES_16 + DOES_32 + DOES_8G;
    00143     }
    00144
    00145     public void run(ImageProcessor ip) {
    00146
    00147
    00148
    00149         //Load the config file contents
    00150         Reader cfgstream = new InputStreamReader(getClass().getClassLoader().getResourceAsStream("multispot5.cfg"));
    00151         String cfg = Util.read(cfgstream);
    00152
    00153         try{
    00154             cfgstream.close();
    00155         }
    00156         catch(IOException close_err){
    00157             Toolkit.getDefaultToolkit().beep();
    00158             ij.IJ.showStatus("Error reading config file.");
    00159             return;
    00160         }
    00161
    00162         //Some basic error checking if reading of the config file from
    00163         //the JAR archive fails.
    00164         if(cfg == "")
    00165         {
    00166             Toolkit.getDefaultToolkit().beep();
    00167             ij.IJ.showStatus("Error reading config file.");
    00168             return;
    00169         }
    00170
    00171
    00172         //The image from getMask() is only the size of the ROI
    00173         //We need it to be congruent with the original image, in order
    00174         //to work with the C++ code.
    00175         mask = new ByteProcessor(ip.getWidth(), ip.getHeight());
    00176
    00177         int x = ip.getRoi().x;
    00178         int y = ip.getRoi().y;
    00179
    00180         //Rectangular selections do not have a mask set, so we get
    00181         //a null pointer exception when we try to copy.
    00182         //So, we have to manually set the pixels, rather than just
    00183         //copy them
    00184         try{
    00185             mask.copyBits(ip.getMask(), x, y, Blitter.COPY);
    00186         }
    00187         catch(NullPointerException e)
    00188         {
    00189             for(int r=0; r < ip.getRoi().height; r++)
    00190                 for(int c=0; c < ip.getRoi().width; c++)
    00191                     mask.set(c+x, r+y, 255);
    00192         }
    00193
    00194         //Count the number of set pixels in the mask. This is used to
    00195         //warn the user if the number is not within a reasonable range.
    00196         int count=0;
    00197         for(int r=0; r < mask.getHeight(); r++)
    00198             for(int c=0; c < mask.getWidth(); c++)
    00199                 if(mask.get(c, r) != 0)
    00200                     count ++;
    00201
    00202
    00203         //The non-config file parameters are the range of frames to  operate on
    00204         //and the pixel size in nm (the config works in terms of FWHM in pixels).
    00205         //These have to be sepficied whether the basic or advanced dialog is used.
    00206         int  firstfr;
    00207         int lastfr;
    00208         double pixel_size_in_nm;
    00209
    00210         ImageStack s = window.getStack();
    00211
    00212         if(arg.equals("advanced"))
    00213         {
    00214             AdvancedDialog ad = new AdvancedDialog(cfg, s.getSize());
    00215             if(!ad.wasOKed())
    00216                 return;
    00217             cfg = ad.getTextArea1().getText();
    00218
    00219
    00220             /*pixel_size_in_nm = ad.getPixelSize();
    00221             firstfr = ad.getFirstFrame();
    00222             lastfr = ad.getLastFrame();*/
    00223
    00224
    00225             pixel_size_in_nm = ad.getNextNumber();
    00226             firstfr = (int)ad.getNextNumber();
    00227             lastfr = (int)ad.getNextNumber();
    00228         }
    00229         else
    00230         {
    00231             ThreeBDialog gd = new ThreeBDialog(count, mask.getWidth()*mask.getHeight(), s.getSize());
    00232
    00233             gd.showDialog();
    00234
    00235             if(!gd.wasOKed())
    00236                 return;
    00237
    00238             /*final double fwhm = gd.getFWHM();
    00239             pixel_size_in_nm = gd.getPixelSize();
    00240             final int initial_spots = gd.getSpots();
    00241             firstfr = gd.getFirstFrame();
    00242             lastfr = gd.getLastFrame();*/
    00243
    00244             //We have to use getNextNumber, otherwise macro recording does not work.
    00245             final double fwhm = gd.getNextNumber();
    00246             pixel_size_in_nm = gd.getNextNumber();
    00247             final int initial_spots = (int)gd.getNextNumber();
    00248             firstfr = (int)gd.getNextNumber();
    00249             lastfr = (int)gd.getNextNumber();
    00250
    00251
    00252             //Compute the parameters of the log-normal prior such that the mode
    00253             //matches the size of the spots.
    00254             final double sigma = (fwhm / pixel_size_in_nm) / (2*Math.sqrt(2*Math.log(2)));
    00255             final double blur_sigma=0.1;
    00256
    00257             //s = exp(mu-sig^2)
    00258             //ln s = mu - sig^2
    00259             //mu = ln s + sig^2
    00260
    00261             final double blur_mu = Math.log(sigma) + blur_sigma*blur_sigma;
    00262
    00263             //Initialized from the current time.
    00264             Random rng = new Random();
    00265             //
    00266
    00267
    00268             //Append stuff to the config file now. This will be parsed later in C++.
    00269             cfg = cfg + "placement.uniform.num_spots=" + Integer.toString(initial_spots) + "\n"
    00270                       + "blur.mu="    + Double.toString(blur_mu) + "\n"
    00271                       + "blur.sigma=" + Double.toString(blur_sigma) + "\n"
    00272                       + "seed=" + Integer.toString(rng.nextInt(16777216)) + "\n";
    00273
    00274         }
    00275
    00276         //Acquire a filename to save moderately safely.
    00277         SPair f = Util.getFileName(window.getTitle());
    00278         final String fname = f.a;
    00279         final String fullname = f.b;
    00280
    00281         if(fname!= null)
    00282         {
    00283             //Create the 3B runner and the control panel, then execute the control panel in the
    00284             //GUI thread.
    00285             final Rectangle roi = ip.getRoi();
    00286             final double pixel_size_in_nm_ = pixel_size_in_nm;
    00287             final ThreeBRunner tbr = new ThreeBRunner(mask, s, cfg, fullname, firstfr, lastfr);
    00288
    00289             SwingUtilities.invokeLater(
    00290                     new Runnable() {
    00291                             public void run() {
    00292                                     new EControlPanel(roi, pixel_size_in_nm_, fname, tbr);
    00293                             }
    00294                     }
    00295             );
    00296
    00297         }
    00298     }
    00299
    00300 };
    00301
    00302 ///Control panel which basically presents the .cfg file in a large edit box
    00303 ///along with additional necessary things (frame range and pixel size). Also
    00304 ///make the user acknowledge that they might be getting into trouble.
    00305 ///This is uglier than I would like.
    00306 ///@ingroup gPlugin
    00307 class AdvancedDialog extends GenericDialog implements DialogListener
    00308 {
    00309     boolean ok=true;
    00310     int nframes;
    00311     AdvancedDialog(String cfg, int nframes_)
    00312     {
    00313         super("3B Analysis");
    00314         nframes=nframes_;
    00315
    00316         addMessage("Advanced configuration");
    00317         addMessage("                                 ");
    00318         addCheckbox("I understand 3B enough to be editing the text below", false);
    00319         addTextAreas(cfg, null, 40, 100);
    00320
    00321         addNumericField("Pixel size (nm / pixel)", 100., 1, 10, "nm");
    00322         addNumericField("First frame", 0., 0, 10, "");
    00323         addNumericField("Last frame", nframes-1., 0, 10, "");
    00324         addDialogListener(this);
    00325
    00326         dialogItemChanged(null, null);
    00327
    00328         showDialog();
    00329     }
    00330
    00331     public boolean dialogItemChanged(GenericDialog gd, java.awt.AWTEvent e)
    00332     {
    00333         boolean v = ((Checkbox)(getCheckboxes().get(0))).getState();
    00334
    00335         if(v != ok)
    00336         {
    00337             ok=v;
    00338
    00339             if(ok)
    00340             {
    00341                 getTextArea1().setEditable(true);
    00342                 ((Label)getMessage()).setText("Warning: strange behaviour may result!");
    00343                 getPixelSizeField().setEditable(true);
    00344                 getFirstFrameField().setEditable(true);
    00345                 getLastFrameField().setEditable(true);
    00346
    00347             }
    00348             else
    00349             {
    00350                 getTextArea1().setEditable(false);
    00351                 ((Label)getMessage()).setText("                                             ");
    00352                 getPixelSizeField().setEditable(false);
    00353                 getFirstFrameField().setEditable(false);
    00354                 getLastFrameField().setEditable(false);
    00355             }
    00356         }
    00357
    00358
    00359         //Clamp the frames
    00360         int first = getFirstFrame();
    00361         int last  = getLastFrame();
    00362
    00363         int nfirst = Math.max(0, Math.min(nframes-1, first));
    00364         int nlast = Math.max(nfirst, Math.min(nframes-1, last));
    00365
    00366         if(first != nfirst)
    00367             getFirstFrameField().setText(Integer.toString(nfirst));
    00368         if(last != nlast)
    00369             getLastFrameField().setText(Integer.toString(nlast));
    00370         return ok;
    00371     }
    00372
    00373     int parseInt(String s)
    00374     {
    00375         try
    00376         {
    00377             return Integer.parseInt(s);
    00378         }
    00379         catch(Exception e)
    00380         {
    00381             return 0;
    00382         }
    00383     }
    00384
    00385     public double parseDouble(String s)
    00386     {
    00387         try
    00388         {
    00389             return Double.parseDouble(s);
    00390         }
    00391         catch(Exception e)
    00392         {
    00393             return 0;
    00394         }
    00395     }
    00396
    00397     TextField getPixelSizeField()
    00398     {
    00399         return (TextField)(getNumericFields().get(0));
    00400     }
    00401
    00402     TextField getFirstFrameField()
    00403     {
    00404         return (TextField)(getNumericFields().get(1));
    00405     }
    00406
    00407
    00408     TextField getLastFrameField()
    00409     {
    00410         return (TextField)(getNumericFields().get(2));
    00411     }
    00412
    00413     double getPixelSize()
    00414     {
    00415         return parseDouble(getPixelSizeField().getText());
    00416     }
    00417     int getFirstFrame()
    00418     {
    00419         return parseInt(getFirstFrameField().getText());
    00420     }
    00421
    00422     int getLastFrame()
    00423     {
    00424         return parseInt(getLastFrameField().getText());
    00425     }
    00426 }
    00427
    00428 ///Dialog box for starting 3B
    00429 ///The dialog highlights bad things in red.
    00430 ///@ingroup gPlugin
    00431 //Not pretty. Should I use dialogItemChanged rather than textValueChanged???
    00432 class ThreeBDialog extends GenericDialog
    00433 {
    00434     int count_, npix, nframes;
    00435     Color c, bg;
    00436
    00437     ThreeBDialog(int count, int npix_, int nframes_)
    00438     {
    00439         super("3B Analysis");
    00440         count_ = count;
    00441         npix   = npix_;
    00442         nframes= nframes_;
    00443
    00444         addNumericField("Microscope FWHM", 250.0, 1, 10, "nm");
    00445         addNumericField("Pixel size", 100., 1, 10, "nm");
    00446         addNumericField("Initial number of spots", Math.round(count / 10.), 0, 10, "spots");
    00447         addNumericField("First frame", 0., 0, 10, "");
    00448         addNumericField("Last frame", nframes-1., 0, 10, "");
    00449         addTextAreas("",null, 8,30);
    00450         getTextArea1().setEditable(false);
    00451         c = getFWHMField().getBackground(); //Get the default background colour
    00452         bg = getBackground(); //Dialog background color
    00453         getTextArea1().removeTextListener(this); //To prevent event thrashing when we write messages
    00454
    00455         //Process initial warnings
    00456         textValueChanged(null);
    00457     }
    00458
    00459     //Try to parse a double and return 0 for an empty string.
    00460     public double parseDouble(String s)
    00461     {
    00462         try
    00463         {
    00464             return Double.parseDouble(s);
    00465         }
    00466         catch(Exception e)
    00467         {
    00468             return 0;
    00469         }
    00470     }
    00471     int parseInt(String s)
    00472     {
    00473         try
    00474         {
    00475             return Integer.parseInt(s);
    00476         }
    00477         catch(Exception e)
    00478         {
    00479             return 0;
    00480         }
    00481     }
    00482
    00483     TextField getFWHMField()
    00484     {
    00485         return (TextField)(getNumericFields().get(0));
    00486     }
    00487     TextField getPixelSizeField()
    00488     {
    00489         return (TextField)(getNumericFields().get(1));
    00490     }
    00491     TextField getSpotsField()
    00492     {
    00493         return (TextField)(getNumericFields().get(2));
    00494     }
    00495     TextField getFirstFrameField()
    00496     {
    00497         return (TextField)(getNumericFields().get(3));
    00498     }
    00499     TextField getLastFrameField()
    00500     {
    00501         return (TextField)(getNumericFields().get(4));
    00502     }
    00503
    00504     double getFWHM()
    00505     {
    00506         return parseDouble(getFWHMField().getText());
    00507     }
    00508
    00509     double getPixelSize()
    00510     {
    00511         return parseDouble(getPixelSizeField().getText());
    00512     }
    00513
    00514     int getSpots()
    00515     {
    00516         return parseInt(getSpotsField().getText());
    00517     }
    00518
    00519     int getFirstFrame()
    00520     {
    00521         return parseInt(getFirstFrameField().getText());
    00522     }
    00523
    00524     int getLastFrame()
    00525     {
    00526         return parseInt(getLastFrameField().getText());
    00527     }
    00528
    00529     int getCount()
    00530     {
    00531         return count_;
    00532     }
    00533
    00534     public void textValueChanged(TextEvent e)
    00535     {
    00536         boolean long_run=false;
    00537         String err = "";
    00538         //               012345678901234567890123456789012345678901234567890
    00539         if(getCount() > 1000)
    00540         {
    00541             long_run=true;
    00542             err = err + "Warning: large area selected.\n3B will run very slowly.\n";
    00543         }
    00544
    00545         if(npix < 2500)
    00546             err = err + "Warning: image is very small. Fitting may be bad because\nimage noise cannot be accurately estimated.\n";
    00547
    00548         if(getSpots() > 500)
    00549         {
    00550             err = err + "Warning: large number of spots.\n3B will run very slowly.\n";
    00551             getSpotsField().setBackground(Color.RED);
    00552         }
    00553         else
    00554             getSpotsField().setBackground(c);
    00555
    00556
    00557         if(getFWHM() < 200)
    00558         {
    00559             err = err + "Warning: unrealistically small\nmicsoscope resolution.\n";
    00560             getFWHMField().setBackground(Color.RED);
    00561         }
    00562         else if(getFWHM() > 350)
    00563         {
    00564             err = err + "Warning: 3B will not work well with\na poorly focussed microscope.\n";
    00565             getFWHMField().setBackground(Color.RED);
    00566         }
    00567         else
    00568             getFWHMField().setBackground(c);
    00569
    00570
    00571         if(getPixelSize() < 70)
    00572         {
    00573             getPixelSizeField().setBackground(Color.RED);
    00574             err = err + "Warning: Very small pixels specified.\nAre you sure?\n";
    00575         }
    00576         else if(getPixelSize() > 180)
    00577         {
    00578             getPixelSizeField().setBackground(Color.RED);
    00579             err = err + "Warning: 3B will not work well if the camera\nresolution is too poor.\n";
    00580         }
    00581         else
    00582             getPixelSizeField().setBackground(c);
    00583
    00584         //Clamp the frames
    00585         int first = getFirstFrame();
    00586         int last  = getLastFrame();
    00587
    00588         int nfirst = Math.max(0, Math.min(nframes-1, first));
    00589         int nlast = Math.max(nfirst, Math.min(nframes-1, last));
    00590
    00591         if(first != nfirst)
    00592             getFirstFrameField().setText(Integer.toString(nfirst));
    00593         if(last != nlast)
    00594             getLastFrameField().setText(Integer.toString(nlast));
    00595
    00596         if(last -first + 1 > 500)
    00597         {
    00598             getFirstFrameField().setBackground(Color.RED);
    00599             getLastFrameField().setBackground(Color.RED);
    00600             err = err + "Warning: large number of frames specified.\n3B will run very slowly and may be inaccurate.\nFewer than 500 frames is strongly recommended.\n200--300 is generally most suitable.\n";
    00601             long_run = true;
    00602         }
    00603         if(last -first + 1 < 150)
    00604         {
    00605             getFirstFrameField().setBackground(Color.RED);
    00606             getLastFrameField().setBackground(Color.RED);
    00607             err = err + "Warning: small number of frames specified.\n3B may be inaccurate.\nAt least 150 frames is recommended.\n";
    00608         }
    00609         else
    00610         {
    00611             getFirstFrameField().setBackground(c);
    00612             getLastFrameField().setBackground(c);
    00613         }
    00614
    00615
    00616         if(!long_run && (last -first + 1)*getCount() > 200000)
    00617         {
    00618             err = err + "Warning: large amount of data specified.\n"+
    00619                         "3B will run very slowly.\n"+
    00620                         "Reduce the area and/or number of frames.\n"+
    00621                         "We recommend: \n" +
    00622                         "Number of frames*area in pixels < 200,000.";
    00623             getFirstFrameField().setBackground(Color.RED);
    00624             getLastFrameField().setBackground(Color.RED);
    00625         }
    00626
    00627
    00628         if(!err.equals(""))
    00629             getTextArea1().setBackground(Color.RED);
    00630         else
    00631             getTextArea1().setBackground(bg);
    00632
    00633
    00634
    00635
    00636         getTextArea1().setText(err);
    00637         repaint();
    00638
    00639     }
    00640 }
    00641
    00642
    00643
    00644
    00645
    00646 ///Basic spot class, simply contains coordinates.
    00647 ///@ingroup gPlugin
    00648 class Spot
    00649 {
    00650     double x, y;
    00651     Spot()
    00652     {
    00653     }
    00654
    00655     Spot(double xx, double yy)
    00656     {
    00657         x=xx;
    00658         y=yy;
    00659     }
    00660 }
    00661
    00662
    00663 ///Listener class which triggers a complete redraw. Since all redraws are
    00664 ///pretty much equal, there is no need to distinguish them.
    00665 ///@ingroup gPlugin
    00666 class SomethingChanges implements ChangeListener
    00667 {
    00668     private EControlPanel c;
    00669     public SomethingChanges(EControlPanel c_)
    00670     {
    00671         c = c_;
    00672     }
    00673
    00674
    00675     public void stateChanged(ChangeEvent e)
    00676     {
    00677         c.send_update_canvas_event();
    00678     }
    00679 }
    00680
    00681 ///This class makes a floating point slider bar with an edit box next to
    00682 ///it for more precision. Also has a reciprocal option for inverse, reciprocal scaling./
    00683 ///@ingroup gPlugin
    00684 //That's not at all bodged in in an unpleasant way.
    00685 class FloatSliderWithBox extends JPanel {
    00686
    00687     private JSlider slider;
    00688     private JTextField number;
    00689     private JLabel label;
    00690     private GridBagConstraints completePanelConstraints_;
    00691
    00692     private int steps=1000000;
    00693     private double min, max;
    00694     private String text;
    00695     private String units;
    00696     private String format = "%8.3f";
    00697
    00698     private double value;
    00699     private boolean reciprocal;
    00700
    00701     public FloatSliderWithBox(String text_, double min_, double max_, double value_, int cols, boolean rec_)
    00702     {
    00703
    00704         super( new GridBagLayout() );
    00705
    00706         reciprocal = rec_;
    00707         min=min_;
    00708         max=max_;
    00709         text=text_;
    00710         value = value_;
    00711
    00712         if(reciprocal)
    00713         {
    00714             min=1/max_;
    00715             max=1/min_;
    00716         }
    00717
    00718         slider = new JSlider(0, steps);
    00719
    00720         label = new JLabel();
    00721
    00722         number = new JTextField(cols);
    00723
    00724         //Assemble into a panel
    00725         completePanelConstraints_ = new GridBagConstraints();
    00726         completePanelConstraints_.fill = GridBagConstraints.HORIZONTAL;
    00727
    00728         completePanelConstraints_.gridx = 0;
    00729         completePanelConstraints_.gridy = 0;
    00730         completePanelConstraints_.weightx=1;
    00731         this.add( slider, completePanelConstraints_ );
    00732
    00733         completePanelConstraints_.gridx = 2;
    00734         completePanelConstraints_.gridy = 0;
    00735         completePanelConstraints_.weightx=0;
    00736         this.add(label, completePanelConstraints_ );
    00737
    00738         completePanelConstraints_.gridx = 1;
    00739         completePanelConstraints_.gridy = 0;
    00740         completePanelConstraints_.weightx=0;
    00741         //Add some space to the left to move away from slider slightly
    00742         //And some more to the bottom to help with the way they are displayed
    00743         completePanelConstraints_.insets = new Insets(0,5,10,0);
    00744         this.add( number, completePanelConstraints_ );
    00745
    00746         this.setBorder(BorderFactory.createTitledBorder(text));
    00747
    00748
    00749         slider.addChangeListener(new SliderChanged(this));
    00750         number.addActionListener(new TextChanged(this));
    00751         setValue(value);
    00752
    00753     }
    00754
    00755     public FloatSliderWithBox setUnits(String s)
    00756     {
    00757         units = s;
    00758         setValue(value);
    00759         return this;
    00760     }
    00761
    00762     public FloatSliderWithBox setFormat(String s)
    00763     {
    00764         format = s;
    00765         setValue(value);
    00766         return this;
    00767     }
    00768
    00769     public void addChangeListener(ChangeListener changeListener){
    00770         slider.addChangeListener( changeListener );
    00771         return;
    00772     }
    00773
    00774     void setValue(double v)
    00775     {
    00776         value = v;
    00777         if(reciprocal)
    00778             slider.setValue((int)Math.round(steps * (1/value-min)/(max-min)));
    00779         else
    00780             slider.setValue((int)Math.round(steps * (value-min)/(max-min)));
    00781
    00782         number.setText(String.format(format, value));
    00783         label.setText(units);
    00784     }
    00785
    00786     double getValue()
    00787     {
    00788         return value;
    00789     }
    00790
    00791     public double get_value_from_slider()
    00792     {
    00793         if(reciprocal)
    00794             return 1/((slider.getValue() * 1.0 / steps) * (max - min) + min);
    00795         else
    00796             return (slider.getValue() * 1.0 / steps) * (max - min) + min;
    00797     }
    00798     public double get_value_from_text()
    00799     {
    00800         return Double.parseDouble(number.getText());
    00801     }
    00802
    00803     class SliderChanged implements ChangeListener
    00804     {
    00805         FloatSliderWithBox f;
    00806         SliderChanged(FloatSliderWithBox f_)
    00807         {
    00808             f = f_;
    00809         }
    00810
    00811         public void stateChanged(ChangeEvent e)
    00812         {
    00813             f.setValue(f.get_value_from_slider());
    00814         }
    00815     }
    00816
    00817     class TextChanged implements ActionListener
    00818     {
    00819         FloatSliderWithBox f;
    00820         TextChanged(FloatSliderWithBox f_)
    00821         {
    00822             f = f_;
    00823         }
    00824
    00825         public void actionPerformed(ActionEvent e)
    00826         {
    00827             f.setValue(f.get_value_from_text());
    00828         }
    00829     }
    00830
    00831 }
    00832
    00833
    00834 ///Close button issues a window close event. Actual closing logic is then
    00835 ///done in the close event handler.
    00836 ///@ingroup gPlugin
    00837 class CloseButtonListener implements ActionListener
    00838 {
    00839     private JFrame f;
    00840     public CloseButtonListener(JFrame fr)
    00841     {
    00842         f = fr;
    00843     }
    00844
    00845
    00846     public void actionPerformed(ActionEvent e)
    00847     {
    00848         //Voodoo from Stack Overflow
    00849         WindowEvent wev = new WindowEvent(f, WindowEvent.WINDOW_CLOSING);
    00850         Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
    00851     }
    00852 }
    00853
    00854 ///Stop 3B thread
    00855 ///@ingroup gPlugin
    00856 class StopButtonListener implements ActionListener
    00857 {
    00858     private EControlPanel t;
    00859     public StopButtonListener(EControlPanel t_)
    00860     {
    00861         t = t_;
    00862     }
    00863
    00864     public void actionPerformed(ActionEvent e)
    00865     {
    00866         t.issue_stop();
    00867     }
    00868 }
    00869
    00870 ///Listener for the export button.
    00871 ///@ingroup gPlugin
    00872 class ExportButtonListener implements ActionListener
    00873 {
    00874     private EControlPanel c;
    00875     public ExportButtonListener(EControlPanel c_)
    00876     {
    00877         c = c_;
    00878     }
    00879
    00880     public void actionPerformed(ActionEvent e)
    00881     {
    00882         c.export_reconstruction_as_ij();
    00883     }
    00884 }
    00885
    00886 ///Control panel for running 3B plugin and providing interactive update.
    00887 ///@ingroup gPlugin
    00888 class EControlPanel extends JFrame implements WindowListener
    00889 {
    00890     private ImagePlus   linear_reconstruction;  //Reconstructed image
    00891     private ImageCanvas canvas;
    00892     private JButton stopButton, exportButton, closeButton;
    00893     private Color exportButtonColor;
    00894     private JLabel status, time_msg;
    00895     private FloatSliderWithBox blur_fwhm;
    00896     private FloatSliderWithBox reconstructed_pixel_size;
    00897
    00898     private Panel complete;
    00899     private JPanel buttons;
    00900
    00901     private ThreeBRunner tbr;
    00902     private ArrayList<Spot> pts;
    00903
    00904
    00905     private Rectangle roi;
    00906     private double zoom=0.01; //Sets initial zoom small,  so the ImageCanvas will always be bigger than its initial size
    00907                               //otherwise it always puts up the zooming rectangle :(
    00908     private double reconstruction_blur_fwhm=.5;
    00909
    00910     private double pixel_size_in_nm;
    00911     private String filename;
    00912
    00913     //Number of iterations. Used to colourize export button and provide warnings.
    00914     //Architecture is now getting quite messy.
    00915     private int iterations = 0;
    00916
    00917
    00918     EControlPanel(Rectangle roi_, double ps_, String filename_, ThreeBRunner tbr_)
    00919     {
    00920         //Constract superclass
    00921         super(filename_);
    00922
    00923         tbr = tbr_;
    00924         filename=filename_;
    00925
    00926         roi = roi_;
    00927         pixel_size_in_nm = ps_;
    00928         pts = new ArrayList<Spot>();
    00929
    00930
    00931         //Now generate the dialog box
    00932         complete = new Panel(new GridBagLayout());
    00933
    00934         //Create the image viewer
    00935         linear_reconstruction = new ImagePlus();
    00936         linear_reconstruction.setProcessor(reconstruct());
    00937         canvas = new ImageCanvas(linear_reconstruction);
    00938
    00939         //Make the image scrollable. This seems to work, if the correct cargo-culting
    00940         //is performed with the gridbaglayout fill constraints. I don't really understand why.
    00941         ScrollPane scroll = new ScrollPane();
    00942         scroll.add(canvas);
    00943         complete.add(scroll, canvas_pos());
    00944
    00945         //Create the status message
    00946         status = new JLabel();
    00947         if(tbr != null)
    00948             set_status("Running.");
    00949         else
    00950             set_status("Using loaded data.");
    00951         complete.add(status, status_pos());
    00952
    00953         //Create the ETA massage
    00954         time_msg = new JLabel();
    00955         if(tbr != null)
    00956             set_time("unknown");
    00957         else
    00958             set_time("not running");
    00959         complete.add(time_msg, time_pos());
    00960
    00961
    00962
    00963         //The two control sliders
    00964         blur_fwhm = new FloatSliderWithBox("Reconstruction blur FWHM", 0, 150, 100.0, 5, false);
    00965         blur_fwhm.setUnits("nm").setFormat("%5.1f");
    00966         blur_fwhm.addChangeListener(new SomethingChanges(this));
    00967         complete.add(blur_fwhm, blur_pos());
    00968
    00969         reconstructed_pixel_size = new FloatSliderWithBox("Reconstructed pixel size", 1, 300, 10.0, 5, true);
    00970         reconstructed_pixel_size.setUnits("nm").setFormat("%5.1f");
    00971         reconstructed_pixel_size.addChangeListener(new SomethingChanges(this));
    00972         complete.add(reconstructed_pixel_size, pixel_size_pos());
    00973
    00974         //The button bar
    00975         buttons = new JPanel();
    00976
    00977         exportButton = new JButton("Export...");
    00978         exportButton.addActionListener(new ExportButtonListener(this));
    00979         buttons.add(exportButton);
    00980         exportButtonColor = exportButton.getBackground();
    00981
    00982         //No point in having a stop button if this is just a viewer
    00983         if(tbr != null)
    00984         {
    00985             stopButton = new JButton("Stop");
    00986             stopButton.addActionListener(new StopButtonListener(this));
    00987             buttons.add(stopButton);
    00988         }
    00989
    00990         closeButton = new JButton("Close");
    00991         closeButton.addActionListener(new CloseButtonListener(this));
    00992         buttons.add(closeButton);
    00993
    00994
    00995         complete.add(buttons, buttons_pos());
    00996
    00997         getContentPane().add(complete);
    00998
    00999         //This class knows how to handle window close events.
    01000         //Don't close the window by default, so we can try to bring up a
    01001         //confirmation dialog.
    01002         addWindowListener(this);
    01003         setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
    01004
    01005
    01006         //Cargo culting...
    01007         pack();
    01008         setVisible(true);
    01009         validate();
    01010
    01011
    01012         //Start the 3B thread, if we have one to run.
    01013         if(tbr != null)
    01014         {
    01015             tbr.register(this);
    01016             Thread t = new Thread(tbr);
    01017             t.start();
    01018         }
    01019     }
    01020
    01021     //Functions for generating constraings for gridbaglayout
    01022     private GridBagConstraints canvas_pos()
    01023     {
    01024         GridBagConstraints g = new GridBagConstraints();
    01025         g.gridx = 0;
    01026         g.gridy = 0;
    01027         g.fill = GridBagConstraints.BOTH;
    01028         g.weightx=100;
    01029         g.weighty=100;
    01030         return g;
    01031     }
    01032
    01033     private GridBagConstraints status_pos()
    01034     {
    01035         GridBagConstraints g = new GridBagConstraints();
    01036         g.gridx = 0;
    01037         g.gridy = 1;
    01038         g.anchor = GridBagConstraints.FIRST_LINE_START;
    01039         g.fill = GridBagConstraints.BOTH;
    01040         return g;
    01041     }
    01042
    01043     private GridBagConstraints time_pos()
    01044     {
    01045         GridBagConstraints g = new GridBagConstraints();
    01046         g.gridx = 0;
    01047         g.gridy = 2;
    01048         g.anchor = GridBagConstraints.FIRST_LINE_START;
    01049         g.fill = GridBagConstraints.BOTH;
    01050         return g;
    01051     }
    01052     private GridBagConstraints buttons_pos()
    01053     {
    01054         GridBagConstraints g = new GridBagConstraints();
    01055         g.gridx = 0;
    01056         g.gridy = 5;
    01057         g.fill = GridBagConstraints.BOTH;
    01058         return g;
    01059     }
    01060
    01061     private GridBagConstraints pixel_size_pos()
    01062     {
    01063         GridBagConstraints g = new GridBagConstraints();
    01064         g.gridx = 0;
    01065         g.gridy = 4;
    01066         g.anchor = GridBagConstraints.FIRST_LINE_START;
    01067         g.fill = GridBagConstraints.BOTH;
    01068         return g;
    01069     }
    01070
    01071     private GridBagConstraints blur_pos()
    01072     {
    01073         GridBagConstraints g = new GridBagConstraints();
    01074         g.gridx = 0;
    01075         g.gridy = 3;
    01076         g.anchor = GridBagConstraints.FIRST_LINE_START;
    01077         g.fill = GridBagConstraints.BOTH;
    01078         return g;
    01079     }
    01080
    01081     private void set_status(String s)
    01082     {
    01083         status.setText("Status: " + s);
    01084     }
    01085
    01086     private void set_time(String s)
    01087     {
    01088         time_msg.setText("Estimated time: " + s);
    01089     }
    01090
    01091     ///Generic redraw function which recomputes the reconstruction.
    01092     ///this is a synchronized method since it makes use of the shared writable
    01093     ///array of points.
    01094     private synchronized void update_canvas()
    01095     {
    01096         reconstruction_blur_fwhm = blur_fwhm.getValue();
    01097         zoom = pixel_size_in_nm / reconstructed_pixel_size.getValue();
    01098
    01099         linear_reconstruction.setProcessor(reconstruct());
    01100         linear_reconstruction.updateImage();
    01101         linear_reconstruction.updateAndRepaintWindow();
    01102         canvas.repaint();
    01103         //Update the canvas size and viewport to prevent funny things with zooming.
    01104         canvas.setDrawingSize(linear_reconstruction.getWidth(), linear_reconstruction.getHeight());
    01105         canvas.setSourceRect(new Rectangle(linear_reconstruction.getWidth(), linear_reconstruction.getHeight()));
    01106         //validate();
    01107         //pack();
    01108
    01109         if(iterations >= ThreeBGlobalConstants.critical_iterations)
    01110         {
    01111             exportButton.setBackground(exportButtonColor);
    01112         }
    01113         else
    01114         {
    01115             exportButton.setBackground(Color.RED);
    01116         }
    01117
    01118     }
    01119
    01120
    01121     //Window event methods. We have to implement all of these to
    01122     //be a window listener. We don't care about any of these.
    01123     public void windowOpened(WindowEvent e) { }
    01124     public void windowClosed(WindowEvent e){}
    01125     public void windowDeactivated(WindowEvent e) { }
    01126     public void windowActivated(WindowEvent e) { }
    01127     public void windowDeiconified(WindowEvent e) { }
    01128     public void windowIconified(WindowEvent e) { }
    01129
    01130     ///Send a stop message to the thread if the window is closed
    01131     public void windowClosing(WindowEvent e)
    01132     {
    01133         if(tbr != null && !tbr.has_stopped())
    01134         {
    01135             int confirmed = JOptionPane.showConfirmDialog(null, "3B still running! Closing will terminate the run. Really close?", "User Confirmation", JOptionPane.YES_NO_OPTION);
    01136
    01137             if (confirmed == JOptionPane.YES_OPTION)
    01138             {
    01139                 dispose();
    01140                 tbr.stop_thread();
    01141             }
    01142
    01143         }
    01144         else
    01145         {
    01146             //OK to close
    01147             dispose();
    01148         }
    01149     }
    01150
    01151     ///Generate a fresh ImageJ image in a standard imageJ window, so that ti can be
    01152     ///manipulated using the usual ImageJ commands. The image is size calibrated, so
    01153     ///scalebars are easy to add.
    01154     void export_reconstruction_as_ij()
    01155     {
    01156         ImageProcessor export = linear_reconstruction.getProcessor().duplicate();
    01157         ImagePlus export_win = new ImagePlus(filename + " reconstruction", export);
    01158         export_win.getCalibration().pixelWidth = reconstructed_pixel_size.getValue();
    01159         export_win.getCalibration().pixelHeight = reconstructed_pixel_size.getValue();
    01160         export_win.getCalibration().setXUnit("nm");
    01161         export_win.getCalibration().setYUnit("nm");
    01162         export_win.show();
    01163         export_win.updateAndDraw();
    01164     }
    01165
    01166     ///Compute a brand new reconstruction.
    01167     private synchronized FloatProcessor reconstruct()
    01168     {
    01169         //Reconstruct an image which is based around the ROI in size
    01170         int xoff = (int)roi.x;
    01171         int yoff = (int)roi.y;
    01172
    01173         int xsize = (int)Math.ceil(roi.width * zoom);
    01174         int ysize = (int)Math.ceil(roi.height * zoom);
    01175
    01176         //New blank image set to zero
    01177         FloatProcessor reconstructed = new FloatProcessor(xsize, ysize);
    01178
    01179         for(int i=0; i < pts.size(); i++)
    01180         {
    01181
    01182             //Increment the count
    01183             int xc = (int)Math.floor((pts.get(i).x-xoff) * zoom + 0.5);
    01184             int yc = (int)Math.floor((pts.get(i).y-yoff) * zoom + 0.5);
    01185             float p = reconstructed.getPixelValue(xc, yc);
    01186             reconstructed.putPixelValue(xc, yc, p+1);
    01187         }
    01188
    01189         double blur_sigma = reconstruction_blur_fwhm / (2 * Math.sqrt(2 * Math.log(2))) * zoom / pixel_size_in_nm;
    01190
    01191         (new GaussianBlur()).blurGaussian(reconstructed, blur_sigma, blur_sigma, 0.005);
    01192         return reconstructed;
    01193     }
    01194
    01195
    01196     public void issue_stop()
    01197     {
    01198         if(tbr != null)
    01199             tbr.stop_thread();
    01200         stopButton.setEnabled(false);
    01201     }
    01202
    01203     //Below here are callbacks used by the ThreeBRunner thread.
    01204
    01205     ///Callback issued by the runnre thread which appends the latest set of
    01206     ///points to the array. This is synchronized since it involves the shared
    01207     ///writable array of points.
    01208     synchronized public void append(final ArrayList<Spot> p, int its)
    01209     {
    01210         for(int i=0; i < p.size(); i++)
    01211             pts.add(p.get(i));
    01212
    01213         iterations = its;
    01214     }
    01215
    01216
    01217     ///Callback for causing a recomputation of the reconstruction.
    01218     ///This is a safe method for telling the class to recompute and redraw the reconstruction.
    01219     ///It can be called from anywhere, but ensures that the redraw happens in the GUI thread.
    01220     public void send_update_canvas_event()
    01221     {
    01222         SwingUtilities.invokeLater(
    01223             new Runnable() {
    01224                 final public void run() {
    01225                         update_canvas();
    01226                     }
    01227                 }
    01228         );
    01229     }
    01230
    01231
    01232     ///Callback which causes a fatal termination of the 3B control panel. This is generally caused
    01233     ///by something changing such that the error condition was not seen in the Java code, but was seen
    01234     ///in C++. Ought never to be called.
    01235     public void die(final String s)
    01236     {
    01237         SwingUtilities.invokeLater(
    01238             new Runnable() {
    01239                 final public void run() {
    01240                         ij.IJ.showStatus("3B run terminated due to an error.\n");
    01241                         JOptionPane.showMessageDialog(null, "Error: "+s, "Fatal error", JOptionPane.ERROR_MESSAGE);
    01242                         dispose();
    01243                     }
    01244                 }
    01245         );
    01246     }
    01247
    01248     ///Callback to update the status message safely.
    01249     public void send_status_text_message(final String s)
    01250     {
    01251         SwingUtilities.invokeLater(
    01252             new Runnable(){
    01253                 final public void run() {
    01254                         set_status(s);
    01255                     }
    01256                 }
    01257         );
    01258     }
    01259
    01260
    01261     ///Callback to update the status message safely.
    01262     public void send_time_text_message(final String s)
    01263     {
    01264         SwingUtilities.invokeLater(
    01265             new Runnable(){
    01266                 final public void run() {
    01267                         set_time(s);
    01268                     }
    01269                 }
    01270         );
    01271     }
    01272
    01273
    01274
    01275 };
    01276
    01277
    01278 ///This class deals with running the actual 3B code
    01279 ///It should be run in a separate thread
    01280 ///It will make calls back to a viewer, and accepts
    01281 ///termination calls as well.
    01282 ///@ingroup gPlugin
    01283 class ThreeBRunner implements Runnable
    01284 {
    01285     ByteProcessor  mask;
    01286     float[][]      pixels;
    01287     private volatile boolean stop=false;
    01288     private volatile boolean stopped=false;
    01289     private boolean fatal=false;
    01290     EControlPanel  cp;
    01291     String config;
    01292     String filename;
    01293     long start_time;
    01294     int its;
    01295
    01296     ThreeBRunner(ByteProcessor mask_, ImageStack s, String cfg_, String filename_, int firstfr, int lastfr)
    01297     {
    01298         config = cfg_;
    01299         filename = filename_;
    01300
    01301         //Take a copy of the mask to stop it getting trashed by other threads
    01302         mask = (ByteProcessor)mask_.duplicate();
    01303
    01304         //Convert all the input images into float as a common
    01305         //format. They will be scaled differently from float images
    01306         //loaded by libcvd, but the later normalization will take care of that
    01307         //
    01308         //Oh, and ImageStack counts from 1, not 0
    01309         pixels = new float[lastfr - firstfr + 1][];
    01310         for(int i=firstfr; i <= lastfr; i++)
    01311             pixels[i-firstfr] = (float[])s.getProcessor(i+1).convertToFloat().getPixels();
    01312     }
    01313
    01314     public void register(EControlPanel c)
    01315     {
    01316         cp = c;
    01317     }
    01318
    01319     void stop_thread()
    01320     {
    01321         stop=true;
    01322         send_message_string("stopping...");
    01323     }
    01324
    01325     boolean has_stopped()
    01326     {
    01327         return stopped;
    01328     }
    01329
    01330
    01331     //Callbacks for the native code
    01332     void send_message_string(String s)
    01333     {
    01334         cp.send_status_text_message(s);
    01335     }
    01336
    01337     void send_new_points(float[] points)
    01338     {
    01339         //New points are sent in the form x, y, x, y
    01340         ArrayList<Spot> tmp = new ArrayList<Spot>();
    01341         for(int i=0; i < points.length; i+=2)
    01342         {
    01343             Spot s = new Spot();
    01344             s.x = points[i];
    01345             s.y = points[i+1];
    01346             tmp.add(s);
    01347         }
    01348
    01349         cp.append(tmp, its);
    01350         cp.send_update_canvas_event();
    01351
    01352         //This happens each "iteration", i.e. each pass.
    01353         long current = (new java.util.Date()).getTime();
    01354
    01355         if(its > 0)
    01356         {
    01357             if(its < 8)
    01358                 cp.send_time_text_message("computing...");
    01359             else
    01360             {
    01361                 long time_per_it = (current -start_time) / its;
    01362                 int target = (int)Math.ceil(its * 1.0 / ThreeBGlobalConstants.critical_iterations) * ThreeBGlobalConstants.critical_iterations;
    01363                 int its_remaining = target-its;
    01364
    01365                 System.out.println("Its = " + its);
    01366                 System.out.println("rem = " + its_remaining);
    01367                 System.out.println("time_per_it = " + time_per_it);
    01368
    01369
    01370                 long time_remaining_s = (its_remaining * time_per_it)/1000;
    01371
    01372                 long s = time_remaining_s % 60;
    01373                 long m = (time_remaining_s / 60)%60;
    01374                 long h = time_remaining_s / 3600;
    01375
    01376                 cp.send_time_text_message( h + "h" + m + "m (for " + target + " iterations)");
    01377             }
    01378
    01379         }
    01380
    01381         //Increment after, since it outputs the initial spots as well
    01382         its++;
    01383     }
    01384
    01385     void die(String err)
    01386     {
    01387         cp.die(err);
    01388         fatal=true;
    01389     }
    01390
    01391     boolean should_stop()
    01392     {
    01393         return stop;
    01394     }
    01395
    01396     native void call(String cfg, float[][] images, byte[] mask, int n_images,int rows, int cols, String file);
    01397
    01398     public void run()
    01399     {
    01400         System.out.println("About to call...");
    01401         start_time = (new java.util.Date()).getTime();
    01402         its=0;
    01403
    01404         call(config, pixels, (byte[])mask.getPixels(), pixels.length, mask.getHeight(), mask.getWidth(), filename);
    01405
    01406         System.out.println("Finished.");
    01407
    01408         if(!fatal)
    01409         {
    01410             cp.send_status_text_message("Finished\n");
    01411             ij.IJ.showStatus("3B run terminated");
    01412         }
    01413
    01414         stopped=true;
    01415     }
    01416
    01417     //
    01418     // Decodes % encoding.
    01419     // "a%20space" translates to "a space"
    01420     private static String decodePercent(String str)
    01421     {
    01422         StringBuffer sb = new StringBuffer();
    01423         for (int i = 0; i < str.length(); i++)
    01424         {
    01425             char c = str.charAt(i);
    01426             if(c == '+')
    01427                 sb.append(' ');
    01428             else if(c == '%')
    01429             {
    01430                 sb.append((char) Integer.parseInt(str.substring(i + 1, i + 3), 16));
    01431                 i += 2;
    01432             }
    01433             else
    01434                 sb.append(c);
    01435         }
    01436         return sb.toString();
    01437     }
    01438
    01439     /**
    01440     * Adds the specified path to the java library path
    01441     *
    01442     * @param pathToAdd the path to add
    01443     * @throws Exception
    01444     */
    01445     public static void addLibraryPath(String pathToAdd) throws Exception{
    01446         final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
    01447         usrPathsField.setAccessible(true);
    01448
    01449         //get array of paths
    01450         final String[] paths = (String[])usrPathsField.get(null);
    01451
    01452         //check if the path to add is already present
    01453         for(String path : paths) {
    01454             if(path.equals(pathToAdd)) {
    01455                 return;
    01456             }
    01457         }
    01458
    01459         //add the new path
    01460         final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
    01461         newPaths[newPaths.length-1] = pathToAdd;
    01462         usrPathsField.set(null, newPaths);
    01463     }
    01464
    01465     //OK, so Java is a tricky beast.
    01466     //
    01467     //On Windows, one needs DLLs to be in the PATH
    01468     //On Linux, one needs .so's to be in the LD_LIBRARY_PATH or ld config.
    01469     //
    01470     //System.loadLibrary examines the PATH, then does its own path parsing and
    01471     //examines that too (so it seems). As a result, if you modify Java's .so lookup
    01472     //after it starts, then you can load the required .so, but you haven't modified the
    01473     //LD_LIBRARY_PATH/PATH, so the loader doesn't know where to look for dependencies.
    01474     //
    01475     //So we have to do everything manually, and load them in the correct order.
    01476     //
    01477     //~Yay.~
    01478     //
    01479     //Addendum:
    01480     //
    01481     //Due to the vast amounts of pain involved in building a DLL linking to other
    01482     //DLLs (specifically the stock LAPACK, BLAS, gFortran) which rely on MingW
    01483     //we now cross compile a DLL with no dependencies at all, which means
    01484     //only a single DLL needs to be loaded now.
    01485     //
    01486     //(Woo hoo.)
    01487
    01488
    01489     static String get_plugin_dir()
    01490     {
    01491         try{
    01492             String img_url = (new SPair()).getClass().getResource("img").getFile();
    01493             URI jar_url = null;
    01494
    01495             jar_url= new URI(img_url.substring(0, img_url.length()-5));
    01496
    01497             File jar = new File(jar_url);
    01498
    01499             System.out.println("File: " + jar.getCanonicalPath());
    01500
    01501
    01502             String dir = jar.getParentFile().getCanonicalPath() + File.separator;
    01503             System.out.println("Dir: " + dir);
    01504
    01505             return dir;
    01506
    01507         }
    01508         catch(Exception e){
    01509             return "";
    01510         }
    01511     }
    01512
    01513
    01514     static UnsatisfiedLinkError try_to_load_dlls(String [] s)
    01515     {
    01516         String dir = get_plugin_dir();
    01517
    01518         try{
    01519             for(int i=0; i < s.length; i++)
    01520             {
    01521                 System.out.println("Loading " + dir+s[i]);
    01522                 System.load(dir + s[i]);
    01523             }
    01524
    01525             return null;
    01526         }
    01527         catch(UnsatisfiedLinkError e)
    01528         {
    01529             System.out.println("Link error: " + e.getMessage());
    01530             return e;
    01531         }
    01532
    01533     }
    01534
    01535
    01536
    01537     static
    01538     {
    01539         String dir = get_plugin_dir();
    01540
    01541         String[] windows_dlls = {
    01542             //"libgcc_s_dw2-1.dll",
    01543             //"libquadmath-0.dll",
    01544             //"libstdc++-6.dll",
    01545             //"libgfortran-3.dll",
    01546             //"libblas.dll",
    01547             //"liblapack.dll",
    01548             "threeB_jni.dll"
    01549         };
    01550
    01551         String[] linux_sos_32 = {
    01552             "libthreeB_jni_32.so",
    01553         };
    01554
    01555         String[] linux_sos_64 = {
    01556             "libthreeB_jni_64.so",
    01557         };
    01558
    01559         String errors;
    01560
    01561         // From http://blog.cedarsoft.com/tag/java-library-path/
    01562         //
    01563         //Explanation At first the system property is updated with the new
    01564         //value. This might be a relative path – or maybe you want to create
    01565         //that path dynamically. The Classloader has a static field (sys_paths)
    01566         //that contains the paths. If that field is set to null, it is
    01567         //initialized automatically.  Therefore forcing that field to null will
    01568         //result into the reevaluation of the library path as soon as
    01569         //loadLibrary() is called…
    01570
    01571         UnsatisfiedLinkError err;
    01572         try{
    01573             System.out.println("Trying to load the normal way...");
    01574             System.loadLibrary("threeB_jni");
    01575         }
    01576         catch(UnsatisfiedLinkError e)
    01577         {
    01578             System.out.println("First error: " + e.getMessage());
    01579
    01580             errors = e.getMessage();
    01581             err = e;
    01582
    01583             System.out.println("Trying manual loading...");
    01584
    01585             e = try_to_load_dlls(linux_sos_64);
    01586             if(e != null)
    01587             {
    01588                 errors = errors + "\n" + e.getMessage();
    01589
    01590                 e = try_to_load_dlls(linux_sos_32);
    01591                 if(e != null)
    01592                 {
    01593                     errors = errors + "\n" + e.getMessage();
    01594                     e = try_to_load_dlls(windows_dlls);
    01595                     if(e != null)
    01596                     {
    01597                         errors = errors + "\n" + e.getMessage();
    01598                         JOptionPane.showMessageDialog(null, "Error loading plugin:\n" + errors, "Error loading plugin", JOptionPane.ERROR_MESSAGE);
    01599                         throw err;
    01600                     }
    01601                 }
    01602             }
    01603         }
    01604     }
    01605 }