ThreeB 1.1
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 }