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