Hi Philippe,
> Is there a reason why the Process>Filters are written with a > GenericDialog and not a NonBlockingGenericDialog? One reason is that ImageJ2's headless support currently only supports GenericDialog, not NonBlockingGenericDialog. Consider the following macro: run("Blobs (25K)"); run("Variance...", "radius=2"); run("Save", "save=/Users/curtis/Desktop/blobs.tif"); With ImageJ2, you can execute this headless from the command line as follows: Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your platform, and "~/Desktop/variance.ijm" is the path to that macro file on disk. The result will be a file "/Users/curtis/Desktop/blobs.tif" with the variance filter applied to the Blobs sample image. If you make the change from GenericDialog to NonBlockingGenericDialog, there will instead be an error as follows: java.lang.VerifyError: Bad type on operand stack Exception Details: Location: ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic Reason: Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is not assignable to 'java/awt/Window' Followed by some details to assist with debugging. If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work will need to be done to improve the ImageJ Legacy component to support NonBlockingGenericDialog in headless mode. Regards, Curtis -- Curtis Rueden LOCI software architect - https://loci.wisc.edu/software ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden Have you tried the Image.sc Forum? https://forum.image.sc/ On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]> wrote: > Dear all (probably Wayne), > Is there a reason why the Process>Filters are written with a GenericDialog > and not a NonBlockingGenericDialog? > Using a NonBlockingGenericDialog has the huge advantage of being able to > make a preview on a picture with a given filter while still being able to > zoom and drag it in order to check the effect of the filter on small > details > within the picture. > And in order to do this, all that is needed to be changed is to: > - Add on line 5: import ij.gui.NonBlockingGenericDialog; > - Replace the line 112 from "GenericDialog gd = new > GenericDialog(command+"...");" > into " NonBlockingGenericDialog gd > = > new NonBlockingGenericDialog (command+"...");" > Within the "ij.plugin.filter.RankFilters.java" file. > I thank you very much in advance for your answers. > My best regards, > Philippe > > Philippe CARL > Laboratoire de Bioimagerie et Pathologies > UMR 7021 CNRS - Université de Strasbourg > Faculté de Pharmacie > 74 route du Rhin > 67401 ILLKIRCH > Tel : +33(0)3 68 85 41 84 > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
Dear Curtis,
Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue. Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools? This could give an agreement between both functionalities. My best regards, Philippe -----Message d'origine----- De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden Envoyé : vendredi 22 février 2019 19:01 À : [hidden email] Objet : Re: NonBlockingGenericDialog for Process>Filters Hi Philippe, > Is there a reason why the Process>Filters are written with a > GenericDialog and not a NonBlockingGenericDialog? One reason is that ImageJ2's headless support currently only supports GenericDialog, not NonBlockingGenericDialog. Consider the following macro: run("Blobs (25K)"); run("Variance...", "radius=2"); run("Save", "save=/Users/curtis/Desktop/blobs.tif"); With ImageJ2, you can execute this headless from the command line as follows: Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your platform, and "~/Desktop/variance.ijm" is the path to that macro file on disk. The result will be a file "/Users/curtis/Desktop/blobs.tif" with the variance filter applied to the Blobs sample image. If you make the change from GenericDialog to NonBlockingGenericDialog, there will instead be an error as follows: java.lang.VerifyError: Bad type on operand stack Exception Details: Location: ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic Reason: Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is not assignable to 'java/awt/Window' Followed by some details to assist with debugging. If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work will need to be done to improve the ImageJ Legacy component to support NonBlockingGenericDialog in headless mode. Regards, Curtis -- Curtis Rueden LOCI software architect - https://loci.wisc.edu/software ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden Have you tried the Image.sc Forum? https://forum.image.sc/ On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]> wrote: > Dear all (probably Wayne), > Is there a reason why the Process>Filters are written with a GenericDialog > and not a NonBlockingGenericDialog? > Using a NonBlockingGenericDialog has the huge advantage of being able to > make a preview on a picture with a given filter while still being able to > zoom and drag it in order to check the effect of the filter on small > details > within the picture. > And in order to do this, all that is needed to be changed is to: > - Add on line 5: import ij.gui.NonBlockingGenericDialog; > - Replace the line 112 from "GenericDialog gd = new > GenericDialog(command+"...");" > into " NonBlockingGenericDialog gd > = > new NonBlockingGenericDialog (command+"...");" > Within the "ij.plugin.filter.RankFilters.java" file. > I thank you very much in advance for your answers. > My best regards, > Philippe > > Philippe CARL > Laboratoire de Bioimagerie et Pathologies > UMR 7021 CNRS - Université de Strasbourg > Faculté de Pharmacie > 74 route du Rhin > 67401 ILLKIRCH > Tel : +33(0)3 68 85 41 84 > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
Hi Philippe,
well, I fear that making standard PlugInFilter dialogs non-modal could cause more problems than just headless execution. Unfortunately, most filters are not multithreading-safe. With a non-modal dialog there would be nothing that prevents the user to use the same filter on two different images at the same time, which would result in unpredictable behavior. I think that the RankFilters (Mean, Variance, Median, Min, Max) are fine. Gaussian Blur, Convolve, and the 3D filters use static variables for the filter parameters, so they must remain modal. What I had thought about a while ago was creating a GenericDialog field that would allow the user to pan and zoom in/out with a line of small buttons (arrows and +/-) or scrollbars during preview. Maybe also dragging in a small square representing the image. Same for brightness&contrast. I had an experimental version for B&C with scrollbars, but these were too clumsy and then I did not find time to continue on that project. In case you want to make something nice out of it (or someone else wants to do this), feel free to use my experimental code pasted at the very bottom! Michael ________________________________________________________________ On 25.02.19 11:53, Philippe CARL wrote: > Dear Curtis, > Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue. > Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools? > This could give an agreement between both functionalities. > My best regards, > Philippe > > -----Message d'origine----- > De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden > Envoyé : vendredi 22 février 2019 19:01 > À : [hidden email] > Objet : Re: NonBlockingGenericDialog for Process>Filters > > Hi Philippe, > >> Is there a reason why the Process>Filters are written with a >> GenericDialog and not a NonBlockingGenericDialog? > > One reason is that ImageJ2's headless support currently only supports > GenericDialog, not NonBlockingGenericDialog. > > Consider the following macro: > > run("Blobs (25K)"); > run("Variance...", "radius=2"); > run("Save", "save=/Users/curtis/Desktop/blobs.tif"); > > With ImageJ2, you can execute this headless from the command line as > follows: > > Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm > > Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your > platform, and "~/Desktop/variance.ijm" is the path to that macro file on > disk. > > The result will be a file "/Users/curtis/Desktop/blobs.tif" with the > variance filter applied to the Blobs sample image. > > If you make the change from GenericDialog to NonBlockingGenericDialog, > there will instead be an error as follows: > > java.lang.VerifyError: Bad type on operand stack > Exception Details: > Location: > ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic > Reason: > Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is > not assignable to 'java/awt/Window' > > Followed by some details to assist with debugging. > > If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work > will need to be done to improve the ImageJ Legacy component to support > NonBlockingGenericDialog in headless mode. > > Regards, > Curtis > > -- > Curtis Rueden > LOCI software architect - https://loci.wisc.edu/software > ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden > Have you tried the Image.sc Forum? https://forum.image.sc/ > > > > On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]> > wrote: > >> Dear all (probably Wayne), >> Is there a reason why the Process>Filters are written with a GenericDialog >> and not a NonBlockingGenericDialog? >> Using a NonBlockingGenericDialog has the huge advantage of being able to >> make a preview on a picture with a given filter while still being able to >> zoom and drag it in order to check the effect of the filter on small >> details >> within the picture. >> And in order to do this, all that is needed to be changed is to: >> - Add on line 5: import ij.gui.NonBlockingGenericDialog; >> - Replace the line 112 from "GenericDialog gd = new >> GenericDialog(command+"...");" >> into " NonBlockingGenericDialog gd >> = >> new NonBlockingGenericDialog (command+"...");" >> Within the "ij.plugin.filter.RankFilters.java" file. >> I thank you very much in advance for your answers. >> My best regards, >> Philippe >> >> Philippe CARL >> Laboratoire de Bioimagerie et Pathologies >> UMR 7021 CNRS - Université de Strasbourg import ij.*; import ij.measure.Measurements; import ij.process.*; import java.awt.*; import java.awt.event.*; public class PreviewHelper extends Panel implements MouseListener, AdjustmentListener { final static int WIDTH = 250; final static Dimension SB_SIZE = new Dimension(120,8); final static int BC_HEIGHT = 100; // brightness/contrast area final static int ZS_HEIGHT = 100; // zoom/scroll area final static int SB_FULL = 200; // scrollbar steps final static double MIN_CONTRAST = 0.25; final static double MAX_CONTRAST = 128; final ImagePlus imp; final boolean hasBC; //whether we have a Brightness&Contrast subpanel private final int height; private Scrollbar bScrollbar, cScrollbar, sScrollbar; private double brightness, contrast;// 0 - SB_FULL range each private static double saturated; // 0 - SB_FULL range, is remembered private boolean autoMode; // saturated overrides min&max if true Label rangeText; // text displaying min&max Label sLabel; // 'auto contrast' (saturated) label public PreviewHelper(ImagePlus imp) { this.imp = imp; hasBC = imp.getType() != ImagePlus.COLOR_RGB; height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT; prepareGui(); } private void prepareGui() { setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; c.insets = new Insets(1, 1, 1, 1); c.fill = GridBagConstraints.HORIZONTAL; if (hasBC) { // setup B&C area add(new Label("Bright"),c); c.gridx++; bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, 1, 0, SB_FULL+1); bScrollbar.setFocusable(false); // prevents blinking on Windows bScrollbar.setPreferredSize(SB_SIZE); bScrollbar.addAdjustmentListener(this); add(bScrollbar, c); c.gridx = 0; c.gridy++; add(new Label("Contr"),c); c.gridx++; cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, 1, 0, SB_FULL+1); cScrollbar.setFocusable(false); // prevents blinking on Windows cScrollbar.setPreferredSize(SB_SIZE); cScrollbar.addAdjustmentListener(this); add(cScrollbar, c); c.gridx = 0; c.gridy++; sLabel = new Label("Auto"); sLabel.addMouseListener(this); add(sLabel, c); c.gridx++; sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, 1, 0, SB_FULL+1); sScrollbar.setFocusable(false); // prevents blinking on Windows sScrollbar.setPreferredSize(SB_SIZE); sScrollbar.addAdjustmentListener(this); add(sScrollbar, c); calculateBC('m', imp.getProcessor()); //set initial scrollbar values } } /** Overrides Component getPreferredSize(). Added to work around a bug in Java 1.4.1 on Mac OS X.*/ public Dimension getPreferredSize() { return new Dimension(WIDTH+1, height+1); } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { super.paint(g); drawRect(g, sLabel.getBounds()); } public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mouseClicked(MouseEvent e) { Object source = e.getSource(); if (source==sLabel) { autoMode = !autoMode; sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? Font.BOLD : Font.PLAIN)); calculateBC('s', imp.getProcessor()); } } public void mouseEntered(MouseEvent e) {} public void adjustmentValueChanged(AdjustmentEvent e) { Object source = e.getSource(); if (source==bScrollbar) calculateBC('b', imp.getProcessor()); else if (source==cScrollbar) calculateBC('c', imp.getProcessor()); else if (source==sScrollbar) calculateBC('s', imp.getProcessor()); } /** If in 'Auto' mode, adjust brightness&contrast according to 'Auto' (saturated) scrollbar */ public void autoAdjust(ImageProcessor ip) { if (autoMode) calculateBC('s', ip); } /** Calculate brightness, contrast, min, and max; set imageProcessor and scrollbars * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast), 's'(aturation), 'm'(in&max) * If changed='m', the min and max of the imageProcessor sets B&C scrollbars * Otherwise, the scrollbar values set the imageProcessor's min&max */ private void calculateBC(char changed, ImageProcessor ip) { double fullMin = 0, fullMax = 255; double min = ip.getMin(), max = ip.getMax(); if (!(ip instanceof ByteProcessor)) { ip.resetMinAndMax(); // calculate range of pixels fullMin = ip.getMin(); fullMax = ip.getMax(); if (changed=='m') ip.setMinAndMax(min, max); //revert to previous min&max } double fullRange = fullMax - fullMin; if (fullRange <= 0) fullRange = 1e-100; //avoid division by 0 double fullMid = 0.5*(fullMax + fullMin); if (changed=='m') { sScrollbar.setValue((int)(saturated+0.5)); } else if (changed=='s') { saturated = sScrollbar.getValue(); min = fullMin; max=fullMax; if (saturated != 0) { ImageStatistics stats = ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null); int[] histogram = ip.getHistogram(); int total = 0; int first = -1, last = -1; for (int i=0; i<histogram.length; i++) { int n = histogram[i]; if (n > 0) { total += n; if (first < 0) first = i; last = i; } } int nSaturated = (int)(total*0.2*saturated/SB_FULL); //max 20% saturated int count = 0; int iMin = first; for (; iMin<last; iMin++) { count += histogram[iMin]; if (count>nSaturated) break; } count = 0; int iMax = last; for (; iMax>first; iMax--) { count += histogram[iMax]; if (count>nSaturated) break; } if (ip instanceof FloatProcessor) { } else { min = iMin; max = iMax; } } IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); ip.setMinAndMax(min,max); imp.updateAndDraw(); } if (changed=='m' || changed=='s') { //calculate and set B&C scrollbars double currentMid = 0.5*(max + min); double currentRange = max-min; if (currentRange <= 0) currentRange = 1e-100; IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); brightness = SB_FULL * (0.5 - (currentMid-fullMid)/(fullRange)); contrast = SB_FULL * Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST); bScrollbar.setValue((int)(brightness+0.5)); cScrollbar.setValue((int)(contrast+0.5)); IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); } else if (changed=='b' || changed=='c') { brightness = bScrollbar.getValue(); contrast = cScrollbar.getValue(); //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); double currentMid = fullMid + fullRange*(0.5 - brightness/SB_FULL); double currentRange = fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST))); min = currentMid - 0.5*currentRange; max = currentMid + 0.5*currentRange; //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max)); ip.setMinAndMax(min, max); imp.updateAndDraw(); } } //draw rectangle around a component private void drawRect(Graphics g, Rectangle r) { g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1); } } // ContrastPlot class ____________________________________________________________________________________________________________________ import ij.*; import ij.plugin.filter.ExtendedPlugInFilter; import ij.plugin.filter.PlugInFilterRunner; import ij.gui.GenericDialog; import ij.gui.DialogListener; import ij.process.*; import java.awt.*; import java.awt.event.*; //test plugin for Preview helper /* * An ImageJ ExtendedPlugInFilter that changes pixels that deviate from the * two neighbors above&below by more than a certain value (the threshold). * These pixels are set to the mean of these two neighbors. Pixels at the * top and bottom edges are not processed. * The filter is useful for eliminating hot pixels of CCDs and single * bad lines of scanning probe images. * * It demonstrates how to write an plugin-filter with preview. * Michael Schmid, 2007-05-21 */ public class Outliers_Simple implements ExtendedPlugInFilter, DialogListener { // ExtendedPlugInFilter: An ExtendedPlugInFilter will be invoked by ImageJ the // following way: // (1) Call to setup with the current foreground ImagePlus (image window) // and the argument string 'arg' specified in the plugins.config file // or a.jar file that contains it. // The PlugInFilter returns its flags. // (2) If the currently active image is compatible with the flags (and DONE // was not specified) showDialog is called. If the reference to the // PlugInFilterRunner passed with showDialog is specified as an argument // to the addPreviewCheckbox method of a GenericDialog, preview will be // possible and the run method of this ExtendedPlugInFilter will be called // in the background for preview. // (3) Unless otherwise specified in the flags (DONE or keeping the preview // as an output for a non-stack ImagePlus), the run method is called. // For stacks, it is called repeatedly for each slice. // (4) If the FINAL_PROCESSING flag has been set (but not DONE), the // setup method is invoked with "final" as parameter string. // ** If this PlugInFilter contains a showAbout() method, its setup method // may be also called with argument string "about". Then, the filter will // display a short help message. // // DialogListener: This PlugInFilter provides the dialogItemChanged method, whoch will // be called if required by the addDialogListener method of the // GenericDialog. // For preview-enabled ExtendedPlugInFilters, the filter parameters/options // should be set in the dialogItemChanged method. // F i l t e r p a r a m e t e r s - don't declare the paramters actually used as static, // otherwise parallel execution (in different threads with different parameters) won't work. /** The threshold. Only pixels deviating by more than this value will be replaced */ private double threshold; /** For saving the threshold; here we also put the initial value */ private static double sThreshold = 10; /** Whether to filter dark (instead of bright) pixels */ private static boolean filterDark = false; // F u r t h e r c l a s s v a r i a b l e s /** Flags specifying the capabilities and needs of this filter. See interfaces PlugInFilter and ExtendedPlugInFIlter for details */ int flags = DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW| PARALLELIZE_STACKS; PreviewHelper previewHelper; /** Setup of the PlugInFilter. Returns the flags specifying the capabilities and needs * of the filter. * * @param arg An argument string that may be specified, e.g., in the plugins.config file of a * jar archive containing th plugin. Unused here. * @param imp The ImagePlus to be processed (image with associated window, calibration, etc.) * @return Flags specifying further action of the PlugInFilterRunner */ public int setup(String arg, ImagePlus imp) { if (arg.equals("about")) { //this is a special case - we only display the "about..." info showAbout(); return DONE; } else { previewHelper = new PreviewHelper(imp); return flags; } } /** Show the dialog asking for the parameters and get them. * @return Flags specifying further action of the PlugInFilterRunner */ public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) { GenericDialog gd = new GenericDialog(command+"..."); gd.addNumericField("Threshold", sThreshold, 0); //use saved value as default gd.addCheckbox("Dark Outliers", filterDark); gd.addPreviewCheckbox(pfr); //passing pfr makes the filter ready for preview gd.addDialogListener(this); //the DialogItemChanged method will be called on user input gd.addPanel(previewHelper); gd.showDialog(); //display the dialog; preview runs in the background if (gd.wasCanceled()) return DONE; dialogItemChanged(gd, null); //read the parameters finally set IJ.register(this.getClass()); //protect static class variables (filter parameters) from garbage collection return IJ.setupDialog(imp, flags); //ask whether to process all slices of stack (if a stack) } /** If this PlugInFilter has been added as DialogListener, the method dialogItemChanged * is invoked by GenericDialog every time the user changes something in the dialog. * Time-consuming code should not be placed in this method and any methods called. * @param gd A reference to the GenericDialog, needed to read the input * @param e An Event characterizing what has been changed in the dialog. * @return Whether the dialog input is valid. If true, preview may be invoked. * (There is no need to check the state of the preview Checkbox here, * this is done by the PlugInFilterRunner.) * If false is returned (invalid input), the "OK" button of the dialog * and preview are disabled. */ public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { threshold = gd.getNextNumber(); filterDark = gd.getNextBoolean(); if (gd.invalidNumber() || threshold <= 0) return false; sThreshold = threshold; // save valid value for the next time return true; } /** This method is invoked by ImageJ for processing. For stacks, it can be called once for * each slice (depending on IJ.setupDialog, where the user is asked whether to do so, see * end of method setup, above). * If preview is enabled and the filter parameters have changed, processing should * be stopped when this thread is interrupted. Thherefore, during long calculations * (say, longer than a tenth of a second) the following code should be executed repeatedly: * <code> if (Thread.currentThread().isInterrupted()) return; </code> * Since this PlugInFilter has specified the CONVERT_TO_FLOAT and SNAPSHOT flags * it will be always called with a FloatProcessor that has a valid snapshot. * @param ip The ImageProcessor containing the image. */ public void run(ImageProcessor ip) { // For filters with preview, the filter parameter(s) should be copied from the // class variables set in DialogItemChanged to local variables to avoid changing // parameters during filtering. This can be done by calling a method with // the filter parameter(s) as arguments. // If the filter may crash with invalid or inconsistent parameters, parameter checking // can be done in dialogItemChanged. In that case, both the dialogItemChanged // method and copying should be done <code>synchronized</code> to avoid using // invalid parameters. The filtering operation itself should not be synchronized, // otherwise it would block the dialog during processing. doFiltering((FloatProcessor)ip, (float)threshold, filterDark); } /** Here the filtering is really done. * This is also the method that provides the API for use in other plugins, it should * not use any class variables. * A static method has the advantage that unintended reading of the filter parameters * from the class variables (that may change asynchronously during preview) will be * flagged by the compiler as error. * (it cannot be static if it needs to access any class variable such as the ImagePlus imp * or if we want to display a progress bar). * * This method only affects the pixels within the getRoi(ip) rectangle. Pixels within the * rectangle, but outside the actual roi will be reset by ImageJ if SUPPORTS_MASKING * has been added to the flags in the setup method. * * @param ip A FloatProcessor containing the image data. It must have a valid snapshot. * @param threshold Determines how much a pixel value may deviate from the mean of the * neighboring pixels to remain unaltered. Threshold is in uncalibrated units. * @param filterDark Whether dark outlies should be eliminated (otherwise bright outliers are * eliminated) */ public static void doFiltering(FloatProcessor ip, float threshold, boolean filterDark) { boolean filterMax = // whether to eliminate maxima (not minima) (ip.isInvertedLut()&&filterDark) || (!ip.isInvertedLut()&&!filterDark); Thread thread = Thread.currentThread(); // needed to check for interrupted state float[] pixels = (float[])ip.getPixels(); // array of the pixel values of the input image float[] snapshotPixels = (float[])ip.getSnapshotPixels(); // array with an (unaltered) copy of the pixel values int width = ip.getWidth(); // width & height of the image int height = ip.getHeight(); Rectangle roi = ip.getRoi(); // the rectangle containing the roi (full image size if no roi) int xEnd = roi.x + roi.width; // loops run only over pixels inside the roi int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost line (line y=0) int yEnd = roi.y + roi.height; if (yEnd == height) yEnd--; // do not process the bottommost line (line y=height-1) for (int y=yStart; y<yEnd; y++) { if (y%100==0 && thread.isInterrupted()) return; // from time to time, check whether interrupted for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to pixel (x,y) in the arrays float neighbor1 = snapshotPixels[p+width]; // the neighbor at (x, y+1) float neighbor2 = snapshotPixels[p-width]; // the neighbor at (x, y-1) if ((filterMax && snapshotPixels[p]>neighbor1+threshold && snapshotPixels[p]>neighbor2+threshold) || (!filterMax && snapshotPixels[p]<neighbor1-threshold && snapshotPixels[p]<neighbor2-threshold)) pixels[p] = (neighbor1+neighbor2)/2; } } } /** This method required by the ExtendedPlugInFilter interface is not used here. * It specifies the number of calls to the run(ip) method. * For filters displaying a progress bar, nPasses is needed to display a * smooth progress bar when processing stacks. */ public void setNPasses (int nPasses) {} /** Show the "About..." text. * This method must be named "showAbout", then the plugin is added to the * Help>About Plugins menu */ private void showAbout() { IJ.showMessage("About Outliers...", "This plug-in filter alters pixels that deviate from their \n"+ "neighbors above and below by more than a given threshold. \n" + "These outliers are set to the mean of these neighbors. \n" + "Edge pixels at the top and bottom of the image are not \n" + "processed." ); } } ____________________________________________________________________________________________________________________ -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
Gruss Gott Michael,
I made as well a GenericDialog to NonBlockingGenericDialog conversion onto the GaussianBlur.java file, replaced the compiled GaussianBlur.class within the IJ.jar and then made all crazy things I could think of which could be problematic having static variables. More precisely, I opened two pictures, launched a Gaussian blurr on one of it, activate the preview button and then selected the other one, applied a Contras&Brightness update and then modified the Gaussian Blurr filter values and the modifications were only applied on the picture on which the filter had been launched. Alternatively, I launched a Gaussian blur on one picture, activated the preview button, than opened another one, played with it, and finally modified the filter values and the updates were on the picture on which the filter had at first be applied. So which issues could up to you be found on some filters due to static variables? I compiled as well the codes you had as attachment, but wasn't able to see any special zoom and drag buttons (unless I missed something) when I launched them. Thanks a lot on advance for your very lightened answer. Kindest regards, Philippe -----Message d'origine----- De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid Envoyé : lundi 25 février 2019 13:17 À : [hidden email] Objet : Re: NonBlockingGenericDialog for Process>Filters Hi Philippe, well, I fear that making standard PlugInFilter dialogs non-modal could cause more problems than just headless execution. Unfortunately, most filters are not multithreading-safe. With a non-modal dialog there would be nothing that prevents the user to use the same filter on two different images at the same time, which would result in unpredictable behavior. I think that the RankFilters (Mean, Variance, Median, Min, Max) are fine. Gaussian Blur, Convolve, and the 3D filters use static variables for the filter parameters, so they must remain modal. What I had thought about a while ago was creating a GenericDialog field that would allow the user to pan and zoom in/out with a line of small buttons (arrows and +/-) or scrollbars during preview. Maybe also dragging in a small square representing the image. Same for brightness&contrast. I had an experimental version for B&C with scrollbars, but these were too clumsy and then I did not find time to continue on that project. In case you want to make something nice out of it (or someone else wants to do this), feel free to use my experimental code pasted at the very bottom! Michael ________________________________________________________________ On 25.02.19 11:53, Philippe CARL wrote: > Dear Curtis, > Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue. > Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools? > This could give an agreement between both functionalities. > My best regards, > Philippe > > -----Message d'origine----- > De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden > Envoyé : vendredi 22 février 2019 19:01 > À : [hidden email] > Objet : Re: NonBlockingGenericDialog for Process>Filters > > Hi Philippe, > >> Is there a reason why the Process>Filters are written with a >> GenericDialog and not a NonBlockingGenericDialog? > > One reason is that ImageJ2's headless support currently only supports > GenericDialog, not NonBlockingGenericDialog. > > Consider the following macro: > > run("Blobs (25K)"); > run("Variance...", "radius=2"); > run("Save", "save=/Users/curtis/Desktop/blobs.tif"); > > With ImageJ2, you can execute this headless from the command line as > follows: > > Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm > > Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your > platform, and "~/Desktop/variance.ijm" is the path to that macro file on > disk. > > The result will be a file "/Users/curtis/Desktop/blobs.tif" with the > variance filter applied to the Blobs sample image. > > If you make the change from GenericDialog to NonBlockingGenericDialog, > there will instead be an error as follows: > > java.lang.VerifyError: Bad type on operand stack > Exception Details: > Location: > ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic > Reason: > Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is > not assignable to 'java/awt/Window' > > Followed by some details to assist with debugging. > > If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work > will need to be done to improve the ImageJ Legacy component to support > NonBlockingGenericDialog in headless mode. > > Regards, > Curtis > > -- > Curtis Rueden > LOCI software architect - https://loci.wisc.edu/software > ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden > Have you tried the Image.sc Forum? https://forum.image.sc/ > > > > On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]> > wrote: > >> Dear all (probably Wayne), >> Is there a reason why the Process>Filters are written with a GenericDialog >> and not a NonBlockingGenericDialog? >> Using a NonBlockingGenericDialog has the huge advantage of being able to >> make a preview on a picture with a given filter while still being able to >> zoom and drag it in order to check the effect of the filter on small >> details >> within the picture. >> And in order to do this, all that is needed to be changed is to: >> - Add on line 5: import ij.gui.NonBlockingGenericDialog; >> - Replace the line 112 from "GenericDialog gd = new >> GenericDialog(command+"...");" >> into " NonBlockingGenericDialog gd >> = >> new NonBlockingGenericDialog (command+"...");" >> Within the "ij.plugin.filter.RankFilters.java" file. >> I thank you very much in advance for your answers. >> My best regards, >> Philippe >> >> Philippe CARL >> Laboratoire de Bioimagerie et Pathologies >> UMR 7021 CNRS - Université de Strasbourg import ij.*; import ij.measure.Measurements; import ij.process.*; import java.awt.*; import java.awt.event.*; public class PreviewHelper extends Panel implements MouseListener, AdjustmentListener { final static int WIDTH = 250; final static Dimension SB_SIZE = new Dimension(120,8); final static int BC_HEIGHT = 100; // brightness/contrast area final static int ZS_HEIGHT = 100; // zoom/scroll area final static int SB_FULL = 200; // scrollbar steps final static double MIN_CONTRAST = 0.25; final static double MAX_CONTRAST = 128; final ImagePlus imp; final boolean hasBC; //whether we have a Brightness&Contrast subpanel private final int height; private Scrollbar bScrollbar, cScrollbar, sScrollbar; private double brightness, contrast;// 0 - SB_FULL range each private static double saturated; // 0 - SB_FULL range, is remembered private boolean autoMode; // saturated overrides min&max if true Label rangeText; // text displaying min&max Label sLabel; // 'auto contrast' (saturated) label public PreviewHelper(ImagePlus imp) { this.imp = imp; hasBC = imp.getType() != ImagePlus.COLOR_RGB; height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT; prepareGui(); } private void prepareGui() { setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; c.insets = new Insets(1, 1, 1, 1); c.fill = GridBagConstraints.HORIZONTAL; if (hasBC) { // setup B&C area add(new Label("Bright"),c); c.gridx++; bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, 1, 0, SB_FULL+1); bScrollbar.setFocusable(false); // prevents blinking on Windows bScrollbar.setPreferredSize(SB_SIZE); bScrollbar.addAdjustmentListener(this); add(bScrollbar, c); c.gridx = 0; c.gridy++; add(new Label("Contr"),c); c.gridx++; cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, 1, 0, SB_FULL+1); cScrollbar.setFocusable(false); // prevents blinking on Windows cScrollbar.setPreferredSize(SB_SIZE); cScrollbar.addAdjustmentListener(this); add(cScrollbar, c); c.gridx = 0; c.gridy++; sLabel = new Label("Auto"); sLabel.addMouseListener(this); add(sLabel, c); c.gridx++; sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, 1, 0, SB_FULL+1); sScrollbar.setFocusable(false); // prevents blinking on Windows sScrollbar.setPreferredSize(SB_SIZE); sScrollbar.addAdjustmentListener(this); add(sScrollbar, c); calculateBC('m', imp.getProcessor()); //set initial scrollbar values } } /** Overrides Component getPreferredSize(). Added to work around a bug in Java 1.4.1 on Mac OS X.*/ public Dimension getPreferredSize() { return new Dimension(WIDTH+1, height+1); } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { super.paint(g); drawRect(g, sLabel.getBounds()); } public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mouseClicked(MouseEvent e) { Object source = e.getSource(); if (source==sLabel) { autoMode = !autoMode; sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? Font.BOLD : Font.PLAIN)); calculateBC('s', imp.getProcessor()); } } public void mouseEntered(MouseEvent e) {} public void adjustmentValueChanged(AdjustmentEvent e) { Object source = e.getSource(); if (source==bScrollbar) calculateBC('b', imp.getProcessor()); else if (source==cScrollbar) calculateBC('c', imp.getProcessor()); else if (source==sScrollbar) calculateBC('s', imp.getProcessor()); } /** If in 'Auto' mode, adjust brightness&contrast according to 'Auto' (saturated) scrollbar */ public void autoAdjust(ImageProcessor ip) { if (autoMode) calculateBC('s', ip); } /** Calculate brightness, contrast, min, and max; set imageProcessor and scrollbars * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast), 's'(aturation), 'm'(in&max) * If changed='m', the min and max of the imageProcessor sets B&C scrollbars * Otherwise, the scrollbar values set the imageProcessor's min&max */ private void calculateBC(char changed, ImageProcessor ip) { double fullMin = 0, fullMax = 255; double min = ip.getMin(), max = ip.getMax(); if (!(ip instanceof ByteProcessor)) { ip.resetMinAndMax(); // calculate range of pixels fullMin = ip.getMin(); fullMax = ip.getMax(); if (changed=='m') ip.setMinAndMax(min, max); //revert to previous min&max } double fullRange = fullMax - fullMin; if (fullRange <= 0) fullRange = 1e-100; //avoid division by 0 double fullMid = 0.5*(fullMax + fullMin); if (changed=='m') { sScrollbar.setValue((int)(saturated+0.5)); } else if (changed=='s') { saturated = sScrollbar.getValue(); min = fullMin; max=fullMax; if (saturated != 0) { ImageStatistics stats = ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null); int[] histogram = ip.getHistogram(); int total = 0; int first = -1, last = -1; for (int i=0; i<histogram.length; i++) { int n = histogram[i]; if (n > 0) { total += n; if (first < 0) first = i; last = i; } } int nSaturated = (int)(total*0.2*saturated/SB_FULL); //max 20% saturated int count = 0; int iMin = first; for (; iMin<last; iMin++) { count += histogram[iMin]; if (count>nSaturated) break; } count = 0; int iMax = last; for (; iMax>first; iMax--) { count += histogram[iMax]; if (count>nSaturated) break; } if (ip instanceof FloatProcessor) { } else { min = iMin; max = iMax; } } IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); ip.setMinAndMax(min,max); imp.updateAndDraw(); } if (changed=='m' || changed=='s') { //calculate and set B&C scrollbars double currentMid = 0.5*(max + min); double currentRange = max-min; if (currentRange <= 0) currentRange = 1e-100; IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); brightness = SB_FULL * (0.5 - (currentMid-fullMid)/(fullRange)); contrast = SB_FULL * Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST); bScrollbar.setValue((int)(brightness+0.5)); cScrollbar.setValue((int)(contrast+0.5)); IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); } else if (changed=='b' || changed=='c') { brightness = bScrollbar.getValue(); contrast = cScrollbar.getValue(); //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); double currentMid = fullMid + fullRange*(0.5 - brightness/SB_FULL); double currentRange = fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST))); min = currentMid - 0.5*currentRange; max = currentMid + 0.5*currentRange; //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max)); ip.setMinAndMax(min, max); imp.updateAndDraw(); } } //draw rectangle around a component private void drawRect(Graphics g, Rectangle r) { g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1); } } // ContrastPlot class ____________________________________________________________________________________________________________________ import ij.*; import ij.plugin.filter.ExtendedPlugInFilter; import ij.plugin.filter.PlugInFilterRunner; import ij.gui.GenericDialog; import ij.gui.DialogListener; import ij.process.*; import java.awt.*; import java.awt.event.*; //test plugin for Preview helper /* * An ImageJ ExtendedPlugInFilter that changes pixels that deviate from the * two neighbors above&below by more than a certain value (the threshold). * These pixels are set to the mean of these two neighbors. Pixels at the * top and bottom edges are not processed. * The filter is useful for eliminating hot pixels of CCDs and single * bad lines of scanning probe images. * * It demonstrates how to write an plugin-filter with preview. * Michael Schmid, 2007-05-21 */ public class Outliers_Simple implements ExtendedPlugInFilter, DialogListener { // ExtendedPlugInFilter: An ExtendedPlugInFilter will be invoked by ImageJ the // following way: // (1) Call to setup with the current foreground ImagePlus (image window) // and the argument string 'arg' specified in the plugins.config file // or a.jar file that contains it. // The PlugInFilter returns its flags. // (2) If the currently active image is compatible with the flags (and DONE // was not specified) showDialog is called. If the reference to the // PlugInFilterRunner passed with showDialog is specified as an argument // to the addPreviewCheckbox method of a GenericDialog, preview will be // possible and the run method of this ExtendedPlugInFilter will be called // in the background for preview. // (3) Unless otherwise specified in the flags (DONE or keeping the preview // as an output for a non-stack ImagePlus), the run method is called. // For stacks, it is called repeatedly for each slice. // (4) If the FINAL_PROCESSING flag has been set (but not DONE), the // setup method is invoked with "final" as parameter string. // ** If this PlugInFilter contains a showAbout() method, its setup method // may be also called with argument string "about". Then, the filter will // display a short help message. // // DialogListener: This PlugInFilter provides the dialogItemChanged method, whoch will // be called if required by the addDialogListener method of the // GenericDialog. // For preview-enabled ExtendedPlugInFilters, the filter parameters/options // should be set in the dialogItemChanged method. // F i l t e r p a r a m e t e r s - don't declare the paramters actually used as static, // otherwise parallel execution (in different threads with different parameters) won't work. /** The threshold. Only pixels deviating by more than this value will be replaced */ private double threshold; /** For saving the threshold; here we also put the initial value */ private static double sThreshold = 10; /** Whether to filter dark (instead of bright) pixels */ private static boolean filterDark = false; // F u r t h e r c l a s s v a r i a b l e s /** Flags specifying the capabilities and needs of this filter. See interfaces PlugInFilter and ExtendedPlugInFIlter for details */ int flags = DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW| PARALLELIZE_STACKS; PreviewHelper previewHelper; /** Setup of the PlugInFilter. Returns the flags specifying the capabilities and needs * of the filter. * * @param arg An argument string that may be specified, e.g., in the plugins.config file of a * jar archive containing th plugin. Unused here. * @param imp The ImagePlus to be processed (image with associated window, calibration, etc.) * @return Flags specifying further action of the PlugInFilterRunner */ public int setup(String arg, ImagePlus imp) { if (arg.equals("about")) { //this is a special case - we only display the "about..." info showAbout(); return DONE; } else { previewHelper = new PreviewHelper(imp); return flags; } } /** Show the dialog asking for the parameters and get them. * @return Flags specifying further action of the PlugInFilterRunner */ public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) { GenericDialog gd = new GenericDialog(command+"..."); gd.addNumericField("Threshold", sThreshold, 0); //use saved value as default gd.addCheckbox("Dark Outliers", filterDark); gd.addPreviewCheckbox(pfr); //passing pfr makes the filter ready for preview gd.addDialogListener(this); //the DialogItemChanged method will be called on user input gd.addPanel(previewHelper); gd.showDialog(); //display the dialog; preview runs in the background if (gd.wasCanceled()) return DONE; dialogItemChanged(gd, null); //read the parameters finally set IJ.register(this.getClass()); //protect static class variables (filter parameters) from garbage collection return IJ.setupDialog(imp, flags); //ask whether to process all slices of stack (if a stack) } /** If this PlugInFilter has been added as DialogListener, the method dialogItemChanged * is invoked by GenericDialog every time the user changes something in the dialog. * Time-consuming code should not be placed in this method and any methods called. * @param gd A reference to the GenericDialog, needed to read the input * @param e An Event characterizing what has been changed in the dialog. * @return Whether the dialog input is valid. If true, preview may be invoked. * (There is no need to check the state of the preview Checkbox here, * this is done by the PlugInFilterRunner.) * If false is returned (invalid input), the "OK" button of the dialog * and preview are disabled. */ public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { threshold = gd.getNextNumber(); filterDark = gd.getNextBoolean(); if (gd.invalidNumber() || threshold <= 0) return false; sThreshold = threshold; // save valid value for the next time return true; } /** This method is invoked by ImageJ for processing. For stacks, it can be called once for * each slice (depending on IJ.setupDialog, where the user is asked whether to do so, see * end of method setup, above). * If preview is enabled and the filter parameters have changed, processing should * be stopped when this thread is interrupted. Thherefore, during long calculations * (say, longer than a tenth of a second) the following code should be executed repeatedly: * <code> if (Thread.currentThread().isInterrupted()) return; </code> * Since this PlugInFilter has specified the CONVERT_TO_FLOAT and SNAPSHOT flags * it will be always called with a FloatProcessor that has a valid snapshot. * @param ip The ImageProcessor containing the image. */ public void run(ImageProcessor ip) { // For filters with preview, the filter parameter(s) should be copied from the // class variables set in DialogItemChanged to local variables to avoid changing // parameters during filtering. This can be done by calling a method with // the filter parameter(s) as arguments. // If the filter may crash with invalid or inconsistent parameters, parameter checking // can be done in dialogItemChanged. In that case, both the dialogItemChanged // method and copying should be done <code>synchronized</code> to avoid using // invalid parameters. The filtering operation itself should not be synchronized, // otherwise it would block the dialog during processing. doFiltering((FloatProcessor)ip, (float)threshold, filterDark); } /** Here the filtering is really done. * This is also the method that provides the API for use in other plugins, it should * not use any class variables. * A static method has the advantage that unintended reading of the filter parameters * from the class variables (that may change asynchronously during preview) will be * flagged by the compiler as error. * (it cannot be static if it needs to access any class variable such as the ImagePlus imp * or if we want to display a progress bar). * * This method only affects the pixels within the getRoi(ip) rectangle. Pixels within the * rectangle, but outside the actual roi will be reset by ImageJ if SUPPORTS_MASKING * has been added to the flags in the setup method. * * @param ip A FloatProcessor containing the image data. It must have a valid snapshot. * @param threshold Determines how much a pixel value may deviate from the mean of the * neighboring pixels to remain unaltered. Threshold is in uncalibrated units. * @param filterDark Whether dark outlies should be eliminated (otherwise bright outliers are * eliminated) */ public static void doFiltering(FloatProcessor ip, float threshold, boolean filterDark) { boolean filterMax = // whether to eliminate maxima (not minima) (ip.isInvertedLut()&&filterDark) || (!ip.isInvertedLut()&&!filterDark); Thread thread = Thread.currentThread(); // needed to check for interrupted state float[] pixels = (float[])ip.getPixels(); // array of the pixel values of the input image float[] snapshotPixels = (float[])ip.getSnapshotPixels(); // array with an (unaltered) copy of the pixel values int width = ip.getWidth(); // width & height of the image int height = ip.getHeight(); Rectangle roi = ip.getRoi(); // the rectangle containing the roi (full image size if no roi) int xEnd = roi.x + roi.width; // loops run only over pixels inside the roi int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost line (line y=0) int yEnd = roi.y + roi.height; if (yEnd == height) yEnd--; // do not process the bottommost line (line y=height-1) for (int y=yStart; y<yEnd; y++) { if (y%100==0 && thread.isInterrupted()) return; // from time to time, check whether interrupted for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to pixel (x,y) in the arrays float neighbor1 = snapshotPixels[p+width]; // the neighbor at (x, y+1) float neighbor2 = snapshotPixels[p-width]; // the neighbor at (x, y-1) if ((filterMax && snapshotPixels[p]>neighbor1+threshold && snapshotPixels[p]>neighbor2+threshold) || (!filterMax && snapshotPixels[p]<neighbor1-threshold && snapshotPixels[p]<neighbor2-threshold)) pixels[p] = (neighbor1+neighbor2)/2; } } } /** This method required by the ExtendedPlugInFilter interface is not used here. * It specifies the number of calls to the run(ip) method. * For filters displaying a progress bar, nPasses is needed to display a * smooth progress bar when processing stacks. */ public void setNPasses (int nPasses) {} /** Show the "About..." text. * This method must be named "showAbout", then the plugin is added to the * Help>About Plugins menu */ private void showAbout() { IJ.showMessage("About Outliers...", "This plug-in filter alters pixels that deviate from their \n"+ "neighbors above and below by more than a given threshold. \n" + "These outliers are set to the mean of these neighbors. \n" + "Edge pixels at the top and bottom of the image are not \n" + "processed." ); } } ____________________________________________________________________________________________________________________ -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
Hi Philippe,
a typical case of two Gaussian Blurs interfering would be the following: Have one Gaussian Blur of a stack running (a large stack so it take some time) and at the same time do a preview of another GaussianBlur with a different radius (sigma). Then, as soon as the preview starts, the remaining slices of the stack will be processed with the sigma of the preview, not the one that should be used for the stack. In principle, you can do the same thing already now (I just tried with a 1024x1024x1024 stack that needs about 15-20 seconds for sigma=7, and quickly started another GaussianBlur on another image with different sigma before the stack was fully processed - the last slices stack got blurred with sigma intended for the other image). Fortunately, it is rather unlikely that this happens in practice. It is much more likely that this kind of problem occurs if one can have several GaussianBlur dialogs open at one time, e.g., to compare how it affects different images. Then it would be much more likely that GaussianBlur runs on two images/stacks at the same moment. Of course, the clean solution would be making the class variables for the parameters non-static, and adding the static ones for saving the parameters, in GaussianBlur, UnsharpMask, Convolver, and ImageMath (the BackgroundSubtracter & RankFilters seem to be fine). In principle, it would be easy, but it has to be done. Then, we still have to care about the headless mode... -- Experimental test plugin: There should be three additional sliders (rather narrow ones) for adjusting B&C, but only for grayscale images (not for RGB). Michael ________________________________________________________________ On 25.02.19 15:41, Philippe CARL wrote: > Gruss Gott Michael, > I made as well a GenericDialog to NonBlockingGenericDialog conversion onto the GaussianBlur.java file, replaced the compiled GaussianBlur.class within the IJ.jar and then made all crazy things I could think of which could be problematic having static variables. > More precisely, I opened two pictures, launched a Gaussian blurr on one of it, activate the preview button and then selected the other one, applied a Contras&Brightness update and then modified the Gaussian Blurr filter values and the modifications were only applied on the picture on which the filter had been launched. > Alternatively, I launched a Gaussian blur on one picture, activated the preview button, than opened another one, played with it, and finally modified the filter values and the updates were on the picture on which the filter had at first be applied. > So which issues could up to you be found on some filters due to static variables? > I compiled as well the codes you had as attachment, but wasn't able to see any special zoom and drag buttons (unless I missed something) when I launched them. > Thanks a lot on advance for your very lightened answer. > Kindest regards, > Philippe > > -----Message d'origine----- > De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid > Envoyé : lundi 25 février 2019 13:17 > À : [hidden email] > Objet : Re: NonBlockingGenericDialog for Process>Filters > > Hi Philippe, > > well, I fear that making standard PlugInFilter dialogs non-modal could > cause more problems than just headless execution. > > Unfortunately, most filters are not multithreading-safe. With a > non-modal dialog there would be nothing that prevents the user to use > the same filter on two different images at the same time, which would > result in unpredictable behavior. > I think that the RankFilters (Mean, Variance, Median, Min, Max) are fine. > Gaussian Blur, Convolve, and the 3D filters use static variables for the > filter parameters, so they must remain modal. > > What I had thought about a while ago was creating a GenericDialog field > that would allow the user to pan and zoom in/out with a line of small > buttons (arrows and +/-) or scrollbars during preview. Maybe also > dragging in a small square representing the image. > Same for brightness&contrast. > I had an experimental version for B&C with scrollbars, but these were > too clumsy and then I did not find time to continue on that project. In > case you want to make something nice out of it (or someone else wants to > do this), feel free to use my experimental code pasted at the very bottom! > > > Michael > ________________________________________________________________ > On 25.02.19 11:53, Philippe CARL wrote: >> Dear Curtis, >> Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue. >> Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools? >> This could give an agreement between both functionalities. >> My best regards, >> Philippe >> >> -----Message d'origine----- >> De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden >> Envoyé : vendredi 22 février 2019 19:01 >> À : [hidden email] >> Objet : Re: NonBlockingGenericDialog for Process>Filters >> >> Hi Philippe, >> >>> Is there a reason why the Process>Filters are written with a >>> GenericDialog and not a NonBlockingGenericDialog? >> >> One reason is that ImageJ2's headless support currently only supports >> GenericDialog, not NonBlockingGenericDialog. >> >> Consider the following macro: >> >> run("Blobs (25K)"); >> run("Variance...", "radius=2"); >> run("Save", "save=/Users/curtis/Desktop/blobs.tif"); >> >> With ImageJ2, you can execute this headless from the command line as >> follows: >> >> Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm >> >> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your >> platform, and "~/Desktop/variance.ijm" is the path to that macro file on >> disk. >> >> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the >> variance filter applied to the Blobs sample image. >> >> If you make the change from GenericDialog to NonBlockingGenericDialog, >> there will instead be an error as follows: >> >> java.lang.VerifyError: Bad type on operand stack >> Exception Details: >> Location: >> ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic >> Reason: >> Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is >> not assignable to 'java/awt/Window' >> >> Followed by some details to assist with debugging. >> >> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work >> will need to be done to improve the ImageJ Legacy component to support >> NonBlockingGenericDialog in headless mode. >> >> Regards, >> Curtis >> >> -- >> Curtis Rueden >> LOCI software architect - https://loci.wisc.edu/software >> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden >> Have you tried the Image.sc Forum? https://forum.image.sc/ >> >> >> >> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]> >> wrote: >> >>> Dear all (probably Wayne), >>> Is there a reason why the Process>Filters are written with a GenericDialog >>> and not a NonBlockingGenericDialog? >>> Using a NonBlockingGenericDialog has the huge advantage of being able to >>> make a preview on a picture with a given filter while still being able to >>> zoom and drag it in order to check the effect of the filter on small >>> details >>> within the picture. >>> And in order to do this, all that is needed to be changed is to: >>> - Add on line 5: import ij.gui.NonBlockingGenericDialog; >>> - Replace the line 112 from "GenericDialog gd = new >>> GenericDialog(command+"...");" >>> into " NonBlockingGenericDialog gd >>> = >>> new NonBlockingGenericDialog (command+"...");" >>> Within the "ij.plugin.filter.RankFilters.java" file. >>> I thank you very much in advance for your answers. >>> My best regards, >>> Philippe >>> >>> Philippe CARL >>> Laboratoire de Bioimagerie et Pathologies >>> UMR 7021 CNRS - Université de Strasbourg > ____________________________________________________________________________________________________________________ > import ij.*; > import ij.measure.Measurements; > import ij.process.*; > import java.awt.*; > import java.awt.event.*; > > > public class PreviewHelper extends Panel implements MouseListener, > AdjustmentListener { > final static int WIDTH = 250; > final static Dimension SB_SIZE = new Dimension(120,8); > final static int BC_HEIGHT = 100; // brightness/contrast area > final static int ZS_HEIGHT = 100; // zoom/scroll area > final static int SB_FULL = 200; // scrollbar steps > final static double MIN_CONTRAST = 0.25; > final static double MAX_CONTRAST = 128; > final ImagePlus imp; > final boolean hasBC; //whether we have a Brightness&Contrast > subpanel > private final int height; > private Scrollbar bScrollbar, cScrollbar, sScrollbar; > private double brightness, contrast;// 0 - SB_FULL range each > private static double saturated; // 0 - SB_FULL range, is remembered > private boolean autoMode; // saturated overrides min&max > if true > Label rangeText; // text displaying min&max > Label sLabel; // 'auto contrast' (saturated) > label > > public PreviewHelper(ImagePlus imp) { > this.imp = imp; > hasBC = imp.getType() != ImagePlus.COLOR_RGB; > height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT; > prepareGui(); > } > > private void prepareGui() { > setLayout(new GridBagLayout()); > GridBagConstraints c = new GridBagConstraints(); > c.gridx = 0; c.gridy = 0; > c.insets = new Insets(1, 1, 1, 1); > c.fill = GridBagConstraints.HORIZONTAL; > if (hasBC) { // setup B&C area > add(new Label("Bright"),c); > c.gridx++; > bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, > 1, 0, SB_FULL+1); > bScrollbar.setFocusable(false); // prevents blinking on Windows > bScrollbar.setPreferredSize(SB_SIZE); > bScrollbar.addAdjustmentListener(this); > add(bScrollbar, c); > c.gridx = 0; c.gridy++; > add(new Label("Contr"),c); > c.gridx++; > cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, > 1, 0, SB_FULL+1); > cScrollbar.setFocusable(false); // prevents blinking on Windows > cScrollbar.setPreferredSize(SB_SIZE); > cScrollbar.addAdjustmentListener(this); > add(cScrollbar, c); > c.gridx = 0; c.gridy++; > sLabel = new Label("Auto"); > sLabel.addMouseListener(this); > add(sLabel, c); > c.gridx++; > sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, > 1, 0, SB_FULL+1); > sScrollbar.setFocusable(false); // prevents blinking on Windows > sScrollbar.setPreferredSize(SB_SIZE); > sScrollbar.addAdjustmentListener(this); > add(sScrollbar, c); > > calculateBC('m', imp.getProcessor()); //set initial > scrollbar values > } > > } > > /** Overrides Component getPreferredSize(). Added to work > around a bug in Java 1.4.1 on Mac OS X.*/ > public Dimension getPreferredSize() { > return new Dimension(WIDTH+1, height+1); > } > > > public void update(Graphics g) { > paint(g); > } > > public void paint(Graphics g) { > super.paint(g); > drawRect(g, sLabel.getBounds()); > } > > > public void mousePressed(MouseEvent e) {} > public void mouseReleased(MouseEvent e) {} > public void mouseExited(MouseEvent e) {} > public void mouseClicked(MouseEvent e) { > Object source = e.getSource(); > if (source==sLabel) { > autoMode = !autoMode; > sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? Font.BOLD > : Font.PLAIN)); > calculateBC('s', imp.getProcessor()); > } > } > public void mouseEntered(MouseEvent e) {} > > public void adjustmentValueChanged(AdjustmentEvent e) { > Object source = e.getSource(); > if (source==bScrollbar) > calculateBC('b', imp.getProcessor()); > else if (source==cScrollbar) > calculateBC('c', imp.getProcessor()); > else if (source==sScrollbar) > calculateBC('s', imp.getProcessor()); > } > > /** If in 'Auto' mode, adjust brightness&contrast according to > 'Auto' (saturated) scrollbar */ > public void autoAdjust(ImageProcessor ip) { > if (autoMode) calculateBC('s', ip); > } > > /** Calculate brightness, contrast, min, and max; set > imageProcessor and scrollbars > * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast), > 's'(aturation), 'm'(in&max) > * If changed='m', the min and max of the imageProcessor sets B&C > scrollbars > * Otherwise, the scrollbar values set the imageProcessor's min&max > */ > private void calculateBC(char changed, ImageProcessor ip) { > double fullMin = 0, fullMax = 255; > double min = ip.getMin(), max = ip.getMax(); > if (!(ip instanceof ByteProcessor)) { > ip.resetMinAndMax(); // calculate range of pixels > fullMin = ip.getMin(); > fullMax = ip.getMax(); > if (changed=='m') ip.setMinAndMax(min, max); //revert to > previous min&max > } > double fullRange = fullMax - fullMin; > if (fullRange <= 0) fullRange = 1e-100; //avoid division by 0 > double fullMid = 0.5*(fullMax + fullMin); > if (changed=='m') { > sScrollbar.setValue((int)(saturated+0.5)); > } else if (changed=='s') { > saturated = sScrollbar.getValue(); > min = fullMin; max=fullMax; > if (saturated != 0) { > ImageStatistics stats = > ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null); > int[] histogram = ip.getHistogram(); > int total = 0; > int first = -1, last = -1; > for (int i=0; i<histogram.length; i++) { > int n = histogram[i]; > if (n > 0) { > total += n; > if (first < 0) first = i; > last = i; > } > } > int nSaturated = (int)(total*0.2*saturated/SB_FULL); > //max 20% saturated > int count = 0; > int iMin = first; > for (; iMin<last; iMin++) { > count += histogram[iMin]; > if (count>nSaturated) break; > } > count = 0; > int iMax = last; > for (; iMax>first; iMax--) { > count += histogram[iMax]; > if (count>nSaturated) break; > } > if (ip instanceof FloatProcessor) { > } else { > min = iMin; max = iMax; > } > } > IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); > ip.setMinAndMax(min,max); > imp.updateAndDraw(); > } > if (changed=='m' || changed=='s') { //calculate and set B&C > scrollbars > double currentMid = 0.5*(max + min); > double currentRange = max-min; > if (currentRange <= 0) currentRange = 1e-100; > IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); > IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); > brightness = SB_FULL * (0.5 - > (currentMid-fullMid)/(fullRange)); > contrast = SB_FULL * > Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST); > bScrollbar.setValue((int)(brightness+0.5)); > cScrollbar.setValue((int)(contrast+0.5)); > IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); > } else if (changed=='b' || changed=='c') { > brightness = bScrollbar.getValue(); > contrast = cScrollbar.getValue(); > //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); > double currentMid = fullMid + fullRange*(0.5 - > brightness/SB_FULL); > double currentRange = > fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST))); > min = currentMid - 0.5*currentRange; > max = currentMid + 0.5*currentRange; > //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); > //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max)); > ip.setMinAndMax(min, max); > imp.updateAndDraw(); > } > } > > //draw rectangle around a component > private void drawRect(Graphics g, Rectangle r) { > g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1); > } > } // ContrastPlot class > > ____________________________________________________________________________________________________________________ > > import ij.*; > import ij.plugin.filter.ExtendedPlugInFilter; > import ij.plugin.filter.PlugInFilterRunner; > import ij.gui.GenericDialog; > import ij.gui.DialogListener; > import ij.process.*; > import java.awt.*; > import java.awt.event.*; > > //test plugin for Preview helper > > /* > * An ImageJ ExtendedPlugInFilter that changes pixels that deviate from the > * two neighbors above&below by more than a certain value (the threshold). > * These pixels are set to the mean of these two neighbors. Pixels at the > * top and bottom edges are not processed. > * The filter is useful for eliminating hot pixels of CCDs and single > * bad lines of scanning probe images. > * > * It demonstrates how to write an plugin-filter with preview. > * Michael Schmid, 2007-05-21 > */ > > public class Outliers_Simple implements ExtendedPlugInFilter, > DialogListener { > // ExtendedPlugInFilter: An ExtendedPlugInFilter will be invoked > by ImageJ the > // following way: > // (1) Call to setup with the current foreground > ImagePlus (image window) > // and the argument string 'arg' specified in the > plugins.config file > // or a.jar file that contains it. > // The PlugInFilter returns its flags. > // (2) If the currently active image is compatible > with the flags (and DONE > // was not specified) showDialog is called. If the > reference to the > // PlugInFilterRunner passed with showDialog is > specified as an argument > // to the addPreviewCheckbox method of a > GenericDialog, preview will be > // possible and the run method of this > ExtendedPlugInFilter will be called > // in the background for preview. > // (3) Unless otherwise specified in the flags (DONE > or keeping the preview > // as an output for a non-stack ImagePlus), the > run method is called. > // For stacks, it is called repeatedly for each slice. > // (4) If the FINAL_PROCESSING flag has been set (but > not DONE), the > // setup method is invoked with "final" as parameter > string. > // ** If this PlugInFilter contains a showAbout() > method, its setup method > // may be also called with argument string > "about". Then, the filter will > // display a short help message. > // > // DialogListener: This PlugInFilter provides the > dialogItemChanged method, whoch will > // be called if required by the addDialogListener > method of the > // GenericDialog. > // For preview-enabled ExtendedPlugInFilters, the > filter parameters/options > // should be set in the dialogItemChanged method. > > > // F i l t e r p a r a m e t e r s - don't declare the > paramters actually used as static, > // otherwise parallel execution (in different threads with > different parameters) won't work. > /** The threshold. Only pixels deviating by more than this value > will be replaced */ > private double threshold; > /** For saving the threshold; here we also put the initial value */ > private static double sThreshold = 10; > /** Whether to filter dark (instead of bright) pixels */ > private static boolean filterDark = false; > // F u r t h e r c l a s s v a r i a b l e s > /** Flags specifying the capabilities and needs of this filter. See > interfaces PlugInFilter and > ExtendedPlugInFIlter for details */ > int flags = > DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW| > PARALLELIZE_STACKS; > PreviewHelper previewHelper; > > /** Setup of the PlugInFilter. Returns the flags specifying the > capabilities and needs > * of the filter. > * > * @param arg An argument string that may be specified, e.g., in > the plugins.config file of a > * jar archive containing th plugin. Unused here. > * @param imp The ImagePlus to be processed (image with > associated window, calibration, etc.) > * @return Flags specifying further action of the > PlugInFilterRunner > */ > public int setup(String arg, ImagePlus imp) { > if (arg.equals("about")) { //this is a special > case - we only display the "about..." info > showAbout(); > return DONE; > } else { > previewHelper = new PreviewHelper(imp); > return flags; > } > } > > /** Show the dialog asking for the parameters and get them. > * @return Flags specifying further action of the PlugInFilterRunner > */ > public int showDialog(ImagePlus imp, String command, > PlugInFilterRunner pfr) { > GenericDialog gd = new GenericDialog(command+"..."); > gd.addNumericField("Threshold", sThreshold, 0); //use saved > value as default > gd.addCheckbox("Dark Outliers", filterDark); > gd.addPreviewCheckbox(pfr); //passing pfr makes the filter > ready for preview > gd.addDialogListener(this); //the DialogItemChanged method > will be called on user input > gd.addPanel(previewHelper); > gd.showDialog(); //display the dialog; preview runs > in the background > if (gd.wasCanceled()) > return DONE; > dialogItemChanged(gd, null); //read the parameters finally set > IJ.register(this.getClass()); //protect static class > variables (filter parameters) from garbage collection > return IJ.setupDialog(imp, flags); //ask whether to process > all slices of stack (if a stack) > } > > /** If this PlugInFilter has been added as DialogListener, the > method dialogItemChanged > * is invoked by GenericDialog every time the user changes > something in the dialog. > * Time-consuming code should not be placed in this method and any > methods called. > * @param gd A reference to the GenericDialog, needed to read the input > * @param e An Event characterizing what has been changed in the > dialog. > * @return Whether the dialog input is valid. If true, preview > may be invoked. > * (There is no need to check the state of the > preview Checkbox here, > * this is done by the PlugInFilterRunner.) > * If false is returned (invalid input), the "OK" button of the dialog > * and preview are disabled. > */ > public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { > threshold = gd.getNextNumber(); > filterDark = gd.getNextBoolean(); > if (gd.invalidNumber() || threshold <= 0) > return false; > sThreshold = threshold; // save valid value for the next time > return true; > } > > /** This method is invoked by ImageJ for processing. For stacks, it > can be called once for > * each slice (depending on IJ.setupDialog, where the user is asked > whether to do so, see > * end of method setup, above). > * If preview is enabled and the filter parameters have changed, > processing should > * be stopped when this thread is interrupted. Thherefore, during > long calculations > * (say, longer than a tenth of a second) the following code should be > executed repeatedly: > * <code> if (Thread.currentThread().isInterrupted()) return; </code> > * Since this PlugInFilter has specified the CONVERT_TO_FLOAT and > SNAPSHOT flags > * it will be always called with a FloatProcessor that has a valid > snapshot. > * @param ip The ImageProcessor containing the image. > */ > public void run(ImageProcessor ip) { > // For filters with preview, the filter parameter(s) should be > copied from the > // class variables set in DialogItemChanged to local variables to > avoid changing > // parameters during filtering. This can be done by calling a method with > // the filter parameter(s) as arguments. > // If the filter may crash with invalid or inconsistent parameters, > parameter checking > // can be done in dialogItemChanged. In that case, both the > dialogItemChanged > // method and copying should be done <code>synchronized</code> to > avoid using > // invalid parameters. The filtering operation itself should not be > synchronized, > // otherwise it would block the dialog during processing. > doFiltering((FloatProcessor)ip, (float)threshold, filterDark); > } > > /** Here the filtering is really done. > * This is also the method that provides the API for use in other > plugins, it should > * not use any class variables. > * A static method has the advantage that unintended reading of the > filter parameters > * from the class variables (that may change asynchronously during > preview) will be > * flagged by the compiler as error. > * (it cannot be static if it needs to access any class variable > such as the ImagePlus imp > * or if we want to display a progress bar). > * > * This method only affects the pixels within the getRoi(ip) > rectangle. Pixels within the > * rectangle, but outside the actual roi will be reset by ImageJ if > SUPPORTS_MASKING > * has been added to the flags in the setup method. > * > * @param ip A FloatProcessor containing the image data. It must > have a valid snapshot. > * @param threshold Determines how much a pixel value may deviate > from the mean of the > * neighboring pixels to remain unaltered. Threshold is in > uncalibrated units. > * @param filterDark Whether dark outlies should be eliminated > (otherwise bright outliers are > * eliminated) > */ > public static void doFiltering(FloatProcessor ip, float threshold, > boolean filterDark) { > boolean filterMax = // whether to eliminate maxima (not minima) > (ip.isInvertedLut()&&filterDark) || (!ip.isInvertedLut()&&!filterDark); > Thread thread = Thread.currentThread(); // needed to check > for interrupted state > float[] pixels = (float[])ip.getPixels(); // array of the > pixel values of the input image > float[] snapshotPixels = > (float[])ip.getSnapshotPixels(); // array with an > (unaltered) copy of the pixel values > int width = ip.getWidth(); // width & height > of the image > int height = ip.getHeight(); > Rectangle roi = ip.getRoi(); // the rectangle > containing the roi (full image size if no roi) > int xEnd = roi.x + roi.width; // loops run only over pixels inside > the roi > int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost > line (line y=0) > int yEnd = roi.y + roi.height; > if (yEnd == height) yEnd--; // do not process the bottommost line > (line y=height-1) > for (int y=yStart; y<yEnd; y++) { > if (y%100==0 && thread.isInterrupted()) return; // from time to > time, check whether interrupted > for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to > pixel (x,y) in the arrays > float neighbor1 = snapshotPixels[p+width]; // the neighbor at > (x, y+1) > float neighbor2 = snapshotPixels[p-width]; // the neighbor at (x, > y-1) > if ((filterMax && snapshotPixels[p]>neighbor1+threshold && > snapshotPixels[p]>neighbor2+threshold) || > (!filterMax && snapshotPixels[p]<neighbor1-threshold && > snapshotPixels[p]<neighbor2-threshold)) > pixels[p] = (neighbor1+neighbor2)/2; > } > } > } > > /** This method required by the ExtendedPlugInFilter interface is not > used here. > * It specifies the number of calls to the run(ip) method. > * For filters displaying a progress bar, nPasses is needed to display a > * smooth progress bar when processing stacks. */ > public void setNPasses (int nPasses) {} > > /** Show the "About..." text. > * This method must be named "showAbout", then the plugin is added > to the > * Help>About Plugins menu > */ > private void showAbout() { > IJ.showMessage("About Outliers...", > "This plug-in filter alters pixels that deviate from their \n"+ > "neighbors above and below by more than a given threshold. \n" + > "These outliers are set to the mean of these neighbors. \n" + > "Edge pixels at the top and bottom of the image are not \n" + > "processed." > ); > } > } > > ____________________________________________________________________________________________________________________ > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
In reply to this post by Curtis Rueden
Hi Curtis, (Wayne, Philippe, and everyone):
can we get some boolean to determine whether ImageJ/Fiji is headless? Then, maybe we could have a static method, e.g., in NonBlockingGenericDialog: /** Returns a new NonBlockingGenericDialog with given title, unless * java is running in headless mode; then a GenericDialog will be * returned*/ public static GenericDialog newDialog(String title) { if (IJ.isHeadless()) return new GenericDialog(title); else return new NonBlockingGenericDialog(title); } Since NonBlockingGenericDialog is a subclass of GenericDialog, this should give a modeless user interface and nevertheless work as GenericDialog in headless mode. Michael ________________________________________________________________ On 22.02.19 19:01, Curtis Rueden wrote: Hi Philippe, Is there a reason why the Process>Filters are written with a GenericDialog and not a NonBlockingGenericDialog? One reason is that ImageJ2's headless support currently only supports GenericDialog, not NonBlockingGenericDialog. Consider the following macro: run("Blobs (25K)"); run("Variance...", "radius=2"); run("Save", "save=/Users/curtis/Desktop/blobs.tif"); With ImageJ2, you can execute this headless from the command line as follows: Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your platform, and "~/Desktop/variance.ijm" is the path to that macro file on disk. The result will be a file "/Users/curtis/Desktop/blobs.tif" with the variance filter applied to the Blobs sample image. If you make the change from GenericDialog to NonBlockingGenericDialog, there will instead be an error as follows: java.lang.VerifyError: Bad type on operand stack Exception Details: Location: ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic Reason: Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is not assignable to 'java/awt/Window' Followed by some details to assist with debugging. If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work will need to be done to improve the ImageJ Legacy component to support NonBlockingGenericDialog in headless mode. Regards, Curtis -- Curtis Rueden LOCI software architect - https://loci.wisc.edu/software ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden Have you tried the Image.sc Forum? https://forum.image.sc/ On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]> wrote: Dear all (probably Wayne), Is there a reason why the Process>Filters are written with a GenericDialog and not a NonBlockingGenericDialog? Using a NonBlockingGenericDialog has the huge advantage of being able to make a preview on a picture with a given filter while still being able to zoom and drag it in order to check the effect of the filter on small details within the picture. And in order to do this, all that is needed to be changed is to: - Add on line 5: import ij.gui.NonBlockingGenericDialog; - Replace the line 112 from "GenericDialog gd = new GenericDialog(command+"...");" into " NonBlockingGenericDialog gd = new NonBlockingGenericDialog (command+"...");" Within the "ij.plugin.filter.RankFilters.java" file. I thank you very much in advance for your answers. My best regards, Philippe Philippe CARL -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
In reply to this post by Michael Schmid
Servus Michael,
To play with several Gaussian Blur windows in parallel, I indeed didn't think about it. And considering static parameters definitions, I now agree completely with you that this will indeed be an issue. My idea (and actually need) behind NonBlockingGenericDialog filters was to be nice and gentle with them, by only using "regular" tools in parallel with these filters like zoom, drag, threshold, ..., i.e. tools where there would be no static parameter definition issues. So in order to fix this potential static parameter issue, what would you think about restricting the creation of only one instance of these filters (potentially throwing an error message in the case a user wants to create a second one)? This solution would enlarge the analysis versatility, providing in the same the "needed security" for the static parameters. As the issue with the headless mode, it could be solved with the addition of a new option within "Edit>Options>Misc..." window. Experimental test plugin: Ok I saw these 3 sliders. But thinking more about it and considering what I described higher, i.e. the combined use of the tools zoom, drag and threshold with a filter, I think that your idea becomes already too complicated. I also saw that the Analyze>Analyze-particles... tool now excludes the holes. Thanks a lot!!! Kindest regards, Philippe -----Message d'origine----- De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid Envoyé : lundi 25 février 2019 17:02 À : [hidden email] Objet : Re: NonBlockingGenericDialog for Process>Filters Hi Philippe, a typical case of two Gaussian Blurs interfering would be the following: Have one Gaussian Blur of a stack running (a large stack so it take some time) and at the same time do a preview of another GaussianBlur with a different radius (sigma). Then, as soon as the preview starts, the remaining slices of the stack will be processed with the sigma of the preview, not the one that should be used for the stack. In principle, you can do the same thing already now (I just tried with a 1024x1024x1024 stack that needs about 15-20 seconds for sigma=7, and quickly started another GaussianBlur on another image with different sigma before the stack was fully processed - the last slices stack got blurred with sigma intended for the other image). Fortunately, it is rather unlikely that this happens in practice. It is much more likely that this kind of problem occurs if one can have several GaussianBlur dialogs open at one time, e.g., to compare how it affects different images. Then it would be much more likely that GaussianBlur runs on two images/stacks at the same moment. Of course, the clean solution would be making the class variables for the parameters non-static, and adding the static ones for saving the parameters, in GaussianBlur, UnsharpMask, Convolver, and ImageMath (the BackgroundSubtracter & RankFilters seem to be fine). In principle, it would be easy, but it has to be done. Then, we still have to care about the headless mode... -- Experimental test plugin: There should be three additional sliders (rather narrow ones) for adjusting B&C, but only for grayscale images (not for RGB). Michael ________________________________________________________________ On 25.02.19 15:41, Philippe CARL wrote: > Gruss Gott Michael, > I made as well a GenericDialog to NonBlockingGenericDialog conversion onto the GaussianBlur.java file, replaced the compiled GaussianBlur.class within the IJ.jar and then made all crazy things I could think of which could be problematic having static variables. > More precisely, I opened two pictures, launched a Gaussian blurr on one of it, activate the preview button and then selected the other one, applied a Contras&Brightness update and then modified the Gaussian Blurr filter values and the modifications were only applied on the picture on which the filter had been launched. > Alternatively, I launched a Gaussian blur on one picture, activated the preview button, than opened another one, played with it, and finally modified the filter values and the updates were on the picture on which the filter had at first be applied. > So which issues could up to you be found on some filters due to static variables? > I compiled as well the codes you had as attachment, but wasn't able to see any special zoom and drag buttons (unless I missed something) when I launched them. > Thanks a lot on advance for your very lightened answer. > Kindest regards, > Philippe > > -----Message d'origine----- > De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid > Envoyé : lundi 25 février 2019 13:17 > À : [hidden email] > Objet : Re: NonBlockingGenericDialog for Process>Filters > > Hi Philippe, > > well, I fear that making standard PlugInFilter dialogs non-modal could > cause more problems than just headless execution. > > Unfortunately, most filters are not multithreading-safe. With a > non-modal dialog there would be nothing that prevents the user to use > the same filter on two different images at the same time, which would > result in unpredictable behavior. > I think that the RankFilters (Mean, Variance, Median, Min, Max) are fine. > Gaussian Blur, Convolve, and the 3D filters use static variables for the > filter parameters, so they must remain modal. > > What I had thought about a while ago was creating a GenericDialog field > that would allow the user to pan and zoom in/out with a line of small > buttons (arrows and +/-) or scrollbars during preview. Maybe also > dragging in a small square representing the image. > Same for brightness&contrast. > I had an experimental version for B&C with scrollbars, but these were > too clumsy and then I did not find time to continue on that project. In > case you want to make something nice out of it (or someone else wants to > do this), feel free to use my experimental code pasted at the very bottom! > > > Michael > ________________________________________________________________ > On 25.02.19 11:53, Philippe CARL wrote: >> Dear Curtis, >> Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue. >> Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools? >> This could give an agreement between both functionalities. >> My best regards, >> Philippe >> >> -----Message d'origine----- >> De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden >> Envoyé : vendredi 22 février 2019 19:01 >> À : [hidden email] >> Objet : Re: NonBlockingGenericDialog for Process>Filters >> >> Hi Philippe, >> >>> Is there a reason why the Process>Filters are written with a >>> GenericDialog and not a NonBlockingGenericDialog? >> >> One reason is that ImageJ2's headless support currently only supports >> GenericDialog, not NonBlockingGenericDialog. >> >> Consider the following macro: >> >> run("Blobs (25K)"); >> run("Variance...", "radius=2"); >> run("Save", "save=/Users/curtis/Desktop/blobs.tif"); >> >> With ImageJ2, you can execute this headless from the command line as >> follows: >> >> Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm >> >> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your >> platform, and "~/Desktop/variance.ijm" is the path to that macro file on >> disk. >> >> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the >> variance filter applied to the Blobs sample image. >> >> If you make the change from GenericDialog to NonBlockingGenericDialog, >> there will instead be an error as follows: >> >> java.lang.VerifyError: Bad type on operand stack >> Exception Details: >> Location: >> ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic >> Reason: >> Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is >> not assignable to 'java/awt/Window' >> >> Followed by some details to assist with debugging. >> >> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work >> will need to be done to improve the ImageJ Legacy component to support >> NonBlockingGenericDialog in headless mode. >> >> Regards, >> Curtis >> >> -- >> Curtis Rueden >> LOCI software architect - https://loci.wisc.edu/software >> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden >> Have you tried the Image.sc Forum? https://forum.image.sc/ >> >> >> >> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]> >> wrote: >> >>> Dear all (probably Wayne), >>> Is there a reason why the Process>Filters are written with a GenericDialog >>> and not a NonBlockingGenericDialog? >>> Using a NonBlockingGenericDialog has the huge advantage of being able to >>> make a preview on a picture with a given filter while still being able to >>> zoom and drag it in order to check the effect of the filter on small >>> details >>> within the picture. >>> And in order to do this, all that is needed to be changed is to: >>> - Add on line 5: import ij.gui.NonBlockingGenericDialog; >>> - Replace the line 112 from "GenericDialog gd = new >>> GenericDialog(command+"...");" >>> into " NonBlockingGenericDialog gd >>> = >>> new NonBlockingGenericDialog (command+"...");" >>> Within the "ij.plugin.filter.RankFilters.java" file. >>> I thank you very much in advance for your answers. >>> My best regards, >>> Philippe >>> >>> Philippe CARL >>> Laboratoire de Bioimagerie et Pathologies >>> UMR 7021 CNRS - Université de Strasbourg > ____________________________________________________________________________________________________________________ > import ij.*; > import ij.measure.Measurements; > import ij.process.*; > import java.awt.*; > import java.awt.event.*; > > > public class PreviewHelper extends Panel implements MouseListener, > AdjustmentListener { > final static int WIDTH = 250; > final static Dimension SB_SIZE = new Dimension(120,8); > final static int BC_HEIGHT = 100; // brightness/contrast area > final static int ZS_HEIGHT = 100; // zoom/scroll area > final static int SB_FULL = 200; // scrollbar steps > final static double MIN_CONTRAST = 0.25; > final static double MAX_CONTRAST = 128; > final ImagePlus imp; > final boolean hasBC; //whether we have a Brightness&Contrast > subpanel > private final int height; > private Scrollbar bScrollbar, cScrollbar, sScrollbar; > private double brightness, contrast;// 0 - SB_FULL range each > private static double saturated; // 0 - SB_FULL range, is remembered > private boolean autoMode; // saturated overrides min&max > if true > Label rangeText; // text displaying min&max > Label sLabel; // 'auto contrast' (saturated) > label > > public PreviewHelper(ImagePlus imp) { > this.imp = imp; > hasBC = imp.getType() != ImagePlus.COLOR_RGB; > height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT; > prepareGui(); > } > > private void prepareGui() { > setLayout(new GridBagLayout()); > GridBagConstraints c = new GridBagConstraints(); > c.gridx = 0; c.gridy = 0; > c.insets = new Insets(1, 1, 1, 1); > c.fill = GridBagConstraints.HORIZONTAL; > if (hasBC) { // setup B&C area > add(new Label("Bright"),c); > c.gridx++; > bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, > 1, 0, SB_FULL+1); > bScrollbar.setFocusable(false); // prevents blinking on Windows > bScrollbar.setPreferredSize(SB_SIZE); > bScrollbar.addAdjustmentListener(this); > add(bScrollbar, c); > c.gridx = 0; c.gridy++; > add(new Label("Contr"),c); > c.gridx++; > cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, > 1, 0, SB_FULL+1); > cScrollbar.setFocusable(false); // prevents blinking on Windows > cScrollbar.setPreferredSize(SB_SIZE); > cScrollbar.addAdjustmentListener(this); > add(cScrollbar, c); > c.gridx = 0; c.gridy++; > sLabel = new Label("Auto"); > sLabel.addMouseListener(this); > add(sLabel, c); > c.gridx++; > sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL, > 1, 0, SB_FULL+1); > sScrollbar.setFocusable(false); // prevents blinking on Windows > sScrollbar.setPreferredSize(SB_SIZE); > sScrollbar.addAdjustmentListener(this); > add(sScrollbar, c); > > calculateBC('m', imp.getProcessor()); //set initial > scrollbar values > } > > } > > /** Overrides Component getPreferredSize(). Added to work > around a bug in Java 1.4.1 on Mac OS X.*/ > public Dimension getPreferredSize() { > return new Dimension(WIDTH+1, height+1); > } > > > public void update(Graphics g) { > paint(g); > } > > public void paint(Graphics g) { > super.paint(g); > drawRect(g, sLabel.getBounds()); > } > > > public void mousePressed(MouseEvent e) {} > public void mouseReleased(MouseEvent e) {} > public void mouseExited(MouseEvent e) {} > public void mouseClicked(MouseEvent e) { > Object source = e.getSource(); > if (source==sLabel) { > autoMode = !autoMode; > sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? Font.BOLD > : Font.PLAIN)); > calculateBC('s', imp.getProcessor()); > } > } > public void mouseEntered(MouseEvent e) {} > > public void adjustmentValueChanged(AdjustmentEvent e) { > Object source = e.getSource(); > if (source==bScrollbar) > calculateBC('b', imp.getProcessor()); > else if (source==cScrollbar) > calculateBC('c', imp.getProcessor()); > else if (source==sScrollbar) > calculateBC('s', imp.getProcessor()); > } > > /** If in 'Auto' mode, adjust brightness&contrast according to > 'Auto' (saturated) scrollbar */ > public void autoAdjust(ImageProcessor ip) { > if (autoMode) calculateBC('s', ip); > } > > /** Calculate brightness, contrast, min, and max; set > imageProcessor and scrollbars > * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast), > 's'(aturation), 'm'(in&max) > * If changed='m', the min and max of the imageProcessor sets B&C > scrollbars > * Otherwise, the scrollbar values set the imageProcessor's min&max > */ > private void calculateBC(char changed, ImageProcessor ip) { > double fullMin = 0, fullMax = 255; > double min = ip.getMin(), max = ip.getMax(); > if (!(ip instanceof ByteProcessor)) { > ip.resetMinAndMax(); // calculate range of pixels > fullMin = ip.getMin(); > fullMax = ip.getMax(); > if (changed=='m') ip.setMinAndMax(min, max); //revert to > previous min&max > } > double fullRange = fullMax - fullMin; > if (fullRange <= 0) fullRange = 1e-100; //avoid division by 0 > double fullMid = 0.5*(fullMax + fullMin); > if (changed=='m') { > sScrollbar.setValue((int)(saturated+0.5)); > } else if (changed=='s') { > saturated = sScrollbar.getValue(); > min = fullMin; max=fullMax; > if (saturated != 0) { > ImageStatistics stats = > ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null); > int[] histogram = ip.getHistogram(); > int total = 0; > int first = -1, last = -1; > for (int i=0; i<histogram.length; i++) { > int n = histogram[i]; > if (n > 0) { > total += n; > if (first < 0) first = i; > last = i; > } > } > int nSaturated = (int)(total*0.2*saturated/SB_FULL); > //max 20% saturated > int count = 0; > int iMin = first; > for (; iMin<last; iMin++) { > count += histogram[iMin]; > if (count>nSaturated) break; > } > count = 0; > int iMax = last; > for (; iMax>first; iMax--) { > count += histogram[iMax]; > if (count>nSaturated) break; > } > if (ip instanceof FloatProcessor) { > } else { > min = iMin; max = iMax; > } > } > IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); > ip.setMinAndMax(min,max); > imp.updateAndDraw(); > } > if (changed=='m' || changed=='s') { //calculate and set B&C > scrollbars > double currentMid = 0.5*(max + min); > double currentRange = max-min; > if (currentRange <= 0) currentRange = 1e-100; > IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); > IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); > brightness = SB_FULL * (0.5 - > (currentMid-fullMid)/(fullRange)); > contrast = SB_FULL * > Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST); > bScrollbar.setValue((int)(brightness+0.5)); > cScrollbar.setValue((int)(contrast+0.5)); > IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); > } else if (changed=='b' || changed=='c') { > brightness = bScrollbar.getValue(); > contrast = cScrollbar.getValue(); > //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); > double currentMid = fullMid + fullRange*(0.5 - > brightness/SB_FULL); > double currentRange = > fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST))); > min = currentMid - 0.5*currentRange; > max = currentMid + 0.5*currentRange; > //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); > //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max)); > ip.setMinAndMax(min, max); > imp.updateAndDraw(); > } > } > > //draw rectangle around a component > private void drawRect(Graphics g, Rectangle r) { > g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1); > } > } // ContrastPlot class > > ____________________________________________________________________________________________________________________ > > import ij.*; > import ij.plugin.filter.ExtendedPlugInFilter; > import ij.plugin.filter.PlugInFilterRunner; > import ij.gui.GenericDialog; > import ij.gui.DialogListener; > import ij.process.*; > import java.awt.*; > import java.awt.event.*; > > //test plugin for Preview helper > > /* > * An ImageJ ExtendedPlugInFilter that changes pixels that deviate from the > * two neighbors above&below by more than a certain value (the threshold). > * These pixels are set to the mean of these two neighbors. Pixels at the > * top and bottom edges are not processed. > * The filter is useful for eliminating hot pixels of CCDs and single > * bad lines of scanning probe images. > * > * It demonstrates how to write an plugin-filter with preview. > * Michael Schmid, 2007-05-21 > */ > > public class Outliers_Simple implements ExtendedPlugInFilter, > DialogListener { > // ExtendedPlugInFilter: An ExtendedPlugInFilter will be invoked > by ImageJ the > // following way: > // (1) Call to setup with the current foreground > ImagePlus (image window) > // and the argument string 'arg' specified in the > plugins.config file > // or a.jar file that contains it. > // The PlugInFilter returns its flags. > // (2) If the currently active image is compatible > with the flags (and DONE > // was not specified) showDialog is called. If the > reference to the > // PlugInFilterRunner passed with showDialog is > specified as an argument > // to the addPreviewCheckbox method of a > GenericDialog, preview will be > // possible and the run method of this > ExtendedPlugInFilter will be called > // in the background for preview. > // (3) Unless otherwise specified in the flags (DONE > or keeping the preview > // as an output for a non-stack ImagePlus), the > run method is called. > // For stacks, it is called repeatedly for each slice. > // (4) If the FINAL_PROCESSING flag has been set (but > not DONE), the > // setup method is invoked with "final" as parameter > string. > // ** If this PlugInFilter contains a showAbout() > method, its setup method > // may be also called with argument string > "about". Then, the filter will > // display a short help message. > // > // DialogListener: This PlugInFilter provides the > dialogItemChanged method, whoch will > // be called if required by the addDialogListener > method of the > // GenericDialog. > // For preview-enabled ExtendedPlugInFilters, the > filter parameters/options > // should be set in the dialogItemChanged method. > > > // F i l t e r p a r a m e t e r s - don't declare the > paramters actually used as static, > // otherwise parallel execution (in different threads with > different parameters) won't work. > /** The threshold. Only pixels deviating by more than this value > will be replaced */ > private double threshold; > /** For saving the threshold; here we also put the initial value */ > private static double sThreshold = 10; > /** Whether to filter dark (instead of bright) pixels */ > private static boolean filterDark = false; > // F u r t h e r c l a s s v a r i a b l e s > /** Flags specifying the capabilities and needs of this filter. See > interfaces PlugInFilter and > ExtendedPlugInFIlter for details */ > int flags = > DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW| > PARALLELIZE_STACKS; > PreviewHelper previewHelper; > > /** Setup of the PlugInFilter. Returns the flags specifying the > capabilities and needs > * of the filter. > * > * @param arg An argument string that may be specified, e.g., in > the plugins.config file of a > * jar archive containing th plugin. Unused here. > * @param imp The ImagePlus to be processed (image with > associated window, calibration, etc.) > * @return Flags specifying further action of the > PlugInFilterRunner > */ > public int setup(String arg, ImagePlus imp) { > if (arg.equals("about")) { //this is a special > case - we only display the "about..." info > showAbout(); > return DONE; > } else { > previewHelper = new PreviewHelper(imp); > return flags; > } > } > > /** Show the dialog asking for the parameters and get them. > * @return Flags specifying further action of the PlugInFilterRunner > */ > public int showDialog(ImagePlus imp, String command, > PlugInFilterRunner pfr) { > GenericDialog gd = new GenericDialog(command+"..."); > gd.addNumericField("Threshold", sThreshold, 0); //use saved > value as default > gd.addCheckbox("Dark Outliers", filterDark); > gd.addPreviewCheckbox(pfr); //passing pfr makes the filter > ready for preview > gd.addDialogListener(this); //the DialogItemChanged method > will be called on user input > gd.addPanel(previewHelper); > gd.showDialog(); //display the dialog; preview runs > in the background > if (gd.wasCanceled()) > return DONE; > dialogItemChanged(gd, null); //read the parameters finally set > IJ.register(this.getClass()); //protect static class > variables (filter parameters) from garbage collection > return IJ.setupDialog(imp, flags); //ask whether to process > all slices of stack (if a stack) > } > > /** If this PlugInFilter has been added as DialogListener, the > method dialogItemChanged > * is invoked by GenericDialog every time the user changes > something in the dialog. > * Time-consuming code should not be placed in this method and any > methods called. > * @param gd A reference to the GenericDialog, needed to read the input > * @param e An Event characterizing what has been changed in the > dialog. > * @return Whether the dialog input is valid. If true, preview > may be invoked. > * (There is no need to check the state of the > preview Checkbox here, > * this is done by the PlugInFilterRunner.) > * If false is returned (invalid input), the "OK" button of the dialog > * and preview are disabled. > */ > public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { > threshold = gd.getNextNumber(); > filterDark = gd.getNextBoolean(); > if (gd.invalidNumber() || threshold <= 0) > return false; > sThreshold = threshold; // save valid value for the next time > return true; > } > > /** This method is invoked by ImageJ for processing. For stacks, it > can be called once for > * each slice (depending on IJ.setupDialog, where the user is asked > whether to do so, see > * end of method setup, above). > * If preview is enabled and the filter parameters have changed, > processing should > * be stopped when this thread is interrupted. Thherefore, during > long calculations > * (say, longer than a tenth of a second) the following code should be > executed repeatedly: > * <code> if (Thread.currentThread().isInterrupted()) return; </code> > * Since this PlugInFilter has specified the CONVERT_TO_FLOAT and > SNAPSHOT flags > * it will be always called with a FloatProcessor that has a valid > snapshot. > * @param ip The ImageProcessor containing the image. > */ > public void run(ImageProcessor ip) { > // For filters with preview, the filter parameter(s) should be > copied from the > // class variables set in DialogItemChanged to local variables to > avoid changing > // parameters during filtering. This can be done by calling a method with > // the filter parameter(s) as arguments. > // If the filter may crash with invalid or inconsistent parameters, > parameter checking > // can be done in dialogItemChanged. In that case, both the > dialogItemChanged > // method and copying should be done <code>synchronized</code> to > avoid using > // invalid parameters. The filtering operation itself should not be > synchronized, > // otherwise it would block the dialog during processing. > doFiltering((FloatProcessor)ip, (float)threshold, filterDark); > } > > /** Here the filtering is really done. > * This is also the method that provides the API for use in other > plugins, it should > * not use any class variables. > * A static method has the advantage that unintended reading of the > filter parameters > * from the class variables (that may change asynchronously during > preview) will be > * flagged by the compiler as error. > * (it cannot be static if it needs to access any class variable > such as the ImagePlus imp > * or if we want to display a progress bar). > * > * This method only affects the pixels within the getRoi(ip) > rectangle. Pixels within the > * rectangle, but outside the actual roi will be reset by ImageJ if > SUPPORTS_MASKING > * has been added to the flags in the setup method. > * > * @param ip A FloatProcessor containing the image data. It must > have a valid snapshot. > * @param threshold Determines how much a pixel value may deviate > from the mean of the > * neighboring pixels to remain unaltered. Threshold is in > uncalibrated units. > * @param filterDark Whether dark outlies should be eliminated > (otherwise bright outliers are > * eliminated) > */ > public static void doFiltering(FloatProcessor ip, float threshold, > boolean filterDark) { > boolean filterMax = // whether to eliminate maxima (not minima) > (ip.isInvertedLut()&&filterDark) || (!ip.isInvertedLut()&&!filterDark); > Thread thread = Thread.currentThread(); // needed to check > for interrupted state > float[] pixels = (float[])ip.getPixels(); // array of the > pixel values of the input image > float[] snapshotPixels = > (float[])ip.getSnapshotPixels(); // array with an > (unaltered) copy of the pixel values > int width = ip.getWidth(); // width & height > of the image > int height = ip.getHeight(); > Rectangle roi = ip.getRoi(); // the rectangle > containing the roi (full image size if no roi) > int xEnd = roi.x + roi.width; // loops run only over pixels inside > the roi > int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost > line (line y=0) > int yEnd = roi.y + roi.height; > if (yEnd == height) yEnd--; // do not process the bottommost line > (line y=height-1) > for (int y=yStart; y<yEnd; y++) { > if (y%100==0 && thread.isInterrupted()) return; // from time to > time, check whether interrupted > for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to > pixel (x,y) in the arrays > float neighbor1 = snapshotPixels[p+width]; // the neighbor at > (x, y+1) > float neighbor2 = snapshotPixels[p-width]; // the neighbor at (x, > y-1) > if ((filterMax && snapshotPixels[p]>neighbor1+threshold && > snapshotPixels[p]>neighbor2+threshold) || > (!filterMax && snapshotPixels[p]<neighbor1-threshold && > snapshotPixels[p]<neighbor2-threshold)) > pixels[p] = (neighbor1+neighbor2)/2; > } > } > } > > /** This method required by the ExtendedPlugInFilter interface is not > used here. > * It specifies the number of calls to the run(ip) method. > * For filters displaying a progress bar, nPasses is needed to display a > * smooth progress bar when processing stacks. */ > public void setNPasses (int nPasses) {} > > /** Show the "About..." text. > * This method must be named "showAbout", then the plugin is added > to the > * Help>About Plugins menu > */ > private void showAbout() { > IJ.showMessage("About Outliers...", > "This plug-in filter alters pixels that deviate from their \n"+ > "neighbors above and below by more than a given threshold. \n" + > "These outliers are set to the mean of these neighbors. \n" + > "Edge pixels at the top and bottom of the image are not \n" + > "processed." > ); > } > } > > ____________________________________________________________________________________________________________________ > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
In reply to this post by Curtis Rueden
Hi Michael,
> can we get some boolean to determine whether ImageJ/Fiji is headless? It will probably be OK to use java.awt.GraphicsEnvironment.isHeadless(). > Since NonBlockingGenericDialog is a subclass of GenericDialog, this > should give a modeless user interface and nevertheless work as > GenericDialog in headless mode. Sounds like a sensible solution. Regards, Curtis -- Curtis Rueden LOCI software architect - https://loci.wisc.edu/software ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden Have you tried the Image.sc Forum? https://forum.image.sc/ On Tue, Feb 26, 2019 at 4:44 AM Michael Schmid <[hidden email]> wrote: > Hi Curtis, (Wayne, Philippe, and everyone): > > can we get some boolean to determine whether ImageJ/Fiji is headless? > Then, maybe we could have a static method, e.g., in > NonBlockingGenericDialog: > > /** Returns a new NonBlockingGenericDialog with given title, unless > * java is running in headless mode; then a GenericDialog will be > * returned*/ > public static GenericDialog newDialog(String title) { > if (IJ.isHeadless()) > return new GenericDialog(title); > else > return new NonBlockingGenericDialog(title); > } > > Since NonBlockingGenericDialog is a subclass of GenericDialog, this > should give a modeless user interface and nevertheless work as > GenericDialog in headless mode. > > > Michael > ________________________________________________________________ > On 22.02.19 19:01, Curtis Rueden wrote: > > Hi Philippe, > > Is there a reason why the Process>Filters are written with a > GenericDialog and not a NonBlockingGenericDialog? > > > One reason is that ImageJ2's headless support currently only > supports > GenericDialog, not NonBlockingGenericDialog. > > Consider the following macro: > > run("Blobs (25K)"); > run("Variance...", "radius=2"); > run("Save", "save=/Users/curtis/Desktop/blobs.tif"); > > With ImageJ2, you can execute this headless from the command line as > follows: > > Contents/MacOS/ImageJ-macosx --headless -macro > ~/Desktop/variance.ijm > > Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your > platform, and "~/Desktop/variance.ijm" is the path to that macro > file on > disk. > > The result will be a file "/Users/curtis/Desktop/blobs.tif" with the > variance filter applied to the Blobs sample image. > > If you make the change from GenericDialog to > NonBlockingGenericDialog, > there will instead be an error as follows: > > java.lang.VerifyError: Bad type on operand stack > Exception Details: > Location: > ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic > Reason: > Type 'ij/gui/NonBlockingGenericDialog' (current frame, > stack[0]) is > not assignable to 'java/awt/Window' > > Followed by some details to assist with debugging. > > If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, > work > will need to be done to improve the ImageJ Legacy component to > support > NonBlockingGenericDialog in headless mode. > > Regards, > Curtis > > -- > Curtis Rueden > LOCI software architect - https://loci.wisc.edu/software > ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden > Have you tried the Image.sc Forum? https://forum.image.sc/ > > > > On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL > <[hidden email]> > wrote: > > Dear all (probably Wayne), > Is there a reason why the Process>Filters are written with a > GenericDialog > and not a NonBlockingGenericDialog? > Using a NonBlockingGenericDialog has the huge advantage of being > able to > make a preview on a picture with a given filter while still > being able to > zoom and drag it in order to check the effect of the filter on > small > details > within the picture. > And in order to do this, all that is needed to be changed is to: > - Add on line 5: import > ij.gui.NonBlockingGenericDialog; > - Replace the line 112 from "GenericDialog gd = new > GenericDialog(command+"...");" > into " > NonBlockingGenericDialog gd > = > new NonBlockingGenericDialog (command+"...");" > Within the "ij.plugin.filter.RankFilters.java" file. > I thank you very much in advance for your answers. > My best regards, > Philippe > > Philippe CARL > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
In reply to this post by CARL Philippe (LBP)
Bonjour Philippe, and Hi everyone in the non-francophone regions,
there is more to care about when using NonBlockingGenericDialog for the filters: Any action that overwrites the undo buffer ("snapshot") will interfere with preview (which uses the snapshot). Plugins that lock the image (all PlugInFilters) should be no problem; running any of these on a locked image should cause an error message. I did a quick search for "snapshot" and found the following potentially problematic ones (currently none of them locks the image they work on): ContrastEnhancer GelAnalyzer ImageCalculator * ScaleBar Scaler * ContrastAdjuster RoiManager * I think these should lock the image anyhow Out of these, the ContrastAdjuster (Brightness&Contrast) and ContrastEnhancer are most relevant; if the user tries to adjust the contrast during preview to better see the result, and if there is either a ROI or the image is RGB, it will make it impossible to revert preview. A possible way out might be keeping the snapshot during preview, and using setSnapshotPixels before reverting from preview. But this will need more thought. What one should also think of is whether creating or modifying ROIs should be blocked while an image is locked. To me, the snapshot issue is currently the most severe problem (Wayne, are you aware of any others? Or someone else?). Concerning the static variables, I think that the cleaner and easier solution would be fixing it in the relevant plugins; then we need no mechanism to block concurrent running of GaussianBlur etc. Best, Michael ________________________________________________________________ On 2019-02-26 12:05, Philippe CARL wrote: > Servus Michael, > To play with several Gaussian Blur windows in parallel, I indeed > didn't think about it. > And considering static parameters definitions, I now agree completely > with you that this will indeed be an issue. > My idea (and actually need) behind NonBlockingGenericDialog filters > was to be nice and gentle with them, by only using "regular" tools in > parallel with these filters like zoom, drag, threshold, ..., i.e. > tools where there would be no static parameter definition issues. > So in order to fix this potential static parameter issue, what would > you think about restricting the creation of only one instance of these > filters (potentially throwing an error message in the case a user > wants to create a second one)? > This solution would enlarge the analysis versatility, providing in the > same the "needed security" for the static parameters. > As the issue with the headless mode, it could be solved with the > addition of a new option within "Edit>Options>Misc..." window. > Experimental test plugin: Ok I saw these 3 sliders. But thinking more > about it and considering what I described higher, i.e. the combined > use of the tools zoom, drag and threshold with a filter, I think that > your idea becomes already too complicated. > I also saw that the Analyze>Analyze-particles... tool now excludes the > holes. Thanks a lot!!! > Kindest regards, > Philippe > > -----Message d'origine----- > De : ImageJ Interest Group [mailto:[hidden email]] De la part de > Michael Schmid > Envoyé : lundi 25 février 2019 17:02 > À : [hidden email] > Objet : Re: NonBlockingGenericDialog for Process>Filters > > Hi Philippe, > > a typical case of two Gaussian Blurs interfering would be the > following: > > Have one Gaussian Blur of a stack running (a large stack so it take > some > time) and at the same time do a preview of another GaussianBlur with a > different radius (sigma). Then, as soon as the preview starts, the > remaining slices of the stack will be processed with the sigma of the > preview, not the one that should be used for the stack. > > In principle, you can do the same thing already now (I just tried with > a > 1024x1024x1024 stack that needs about 15-20 seconds for sigma=7, and > quickly started another GaussianBlur on another image with different > sigma before the stack was fully processed - the last slices stack got > blurred with sigma intended for the other image). Fortunately, it is > rather unlikely that this happens in practice. > > It is much more likely that this kind of problem occurs if one can have > several GaussianBlur dialogs open at one time, e.g., to compare how it > affects different images. Then it would be much more likely that > GaussianBlur runs on two images/stacks at the same moment. > > Of course, the clean solution would be making the class variables for > the parameters non-static, and adding the static ones for saving the > parameters, in GaussianBlur, UnsharpMask, Convolver, and ImageMath (the > BackgroundSubtracter & RankFilters seem to be fine). In principle, it > would be easy, but it has to be done. > > Then, we still have to care about the headless mode... > > -- > Experimental test plugin: There should be three additional sliders > (rather narrow ones) for adjusting B&C, but only for grayscale images > (not for RGB). > > > Michael > ________________________________________________________________ > On 25.02.19 15:41, Philippe CARL wrote: >> Gruss Gott Michael, >> I made as well a GenericDialog to NonBlockingGenericDialog conversion >> onto the GaussianBlur.java file, replaced the compiled >> GaussianBlur.class within the IJ.jar and then made all crazy things I >> could think of which could be problematic having static variables. >> More precisely, I opened two pictures, launched a Gaussian blurr on >> one of it, activate the preview button and then selected the other >> one, applied a Contras&Brightness update and then modified the >> Gaussian Blurr filter values and the modifications were only applied >> on the picture on which the filter had been launched. >> Alternatively, I launched a Gaussian blur on one picture, activated >> the preview button, than opened another one, played with it, and >> finally modified the filter values and the updates were on the picture >> on which the filter had at first be applied. >> So which issues could up to you be found on some filters due to static >> variables? >> I compiled as well the codes you had as attachment, but wasn't able to >> see any special zoom and drag buttons (unless I missed something) when >> I launched them. >> Thanks a lot on advance for your very lightened answer. >> Kindest regards, >> Philippe >> >> -----Message d'origine----- >> De : ImageJ Interest Group [mailto:[hidden email]] De la part de >> Michael Schmid >> Envoyé : lundi 25 février 2019 13:17 >> À : [hidden email] >> Objet : Re: NonBlockingGenericDialog for Process>Filters >> >> Hi Philippe, >> >> well, I fear that making standard PlugInFilter dialogs non-modal could >> cause more problems than just headless execution. >> >> Unfortunately, most filters are not multithreading-safe. With a >> non-modal dialog there would be nothing that prevents the user to use >> the same filter on two different images at the same time, which would >> result in unpredictable behavior. >> I think that the RankFilters (Mean, Variance, Median, Min, Max) are >> fine. >> Gaussian Blur, Convolve, and the 3D filters use static variables for >> the >> filter parameters, so they must remain modal. >> >> What I had thought about a while ago was creating a GenericDialog >> field >> that would allow the user to pan and zoom in/out with a line of small >> buttons (arrows and +/-) or scrollbars during preview. Maybe also >> dragging in a small square representing the image. >> Same for brightness&contrast. >> I had an experimental version for B&C with scrollbars, but these were >> too clumsy and then I did not find time to continue on that project. >> In >> case you want to make something nice out of it (or someone else wants >> to >> do this), feel free to use my experimental code pasted at the very >> bottom! >> >> >> Michael >> ________________________________________________________________ >> On 25.02.19 11:53, Philippe CARL wrote: >>> Dear Curtis, >>> Given that I never use ImageJ in headless mode, I wasn't aware of >>> this potential issue. >>> Thus what would you think about adding an additional option within >>> "Edit>Options>Misc..." which would give the possibility to choose >>> between GenericDialog or NonBlockingGenericDialog dialogs within all >>> the filter tools? >>> This could give an agreement between both functionalities. >>> My best regards, >>> Philippe >>> >>> -----Message d'origine----- >>> De : ImageJ Interest Group [mailto:[hidden email]] De la part de >>> Curtis Rueden >>> Envoyé : vendredi 22 février 2019 19:01 >>> À : [hidden email] >>> Objet : Re: NonBlockingGenericDialog for Process>Filters >>> >>> Hi Philippe, >>> >>>> Is there a reason why the Process>Filters are written with a >>>> GenericDialog and not a NonBlockingGenericDialog? >>> >>> One reason is that ImageJ2's headless support currently only supports >>> GenericDialog, not NonBlockingGenericDialog. >>> >>> Consider the following macro: >>> >>> run("Blobs (25K)"); >>> run("Variance...", "radius=2"); >>> run("Save", "save=/Users/curtis/Desktop/blobs.tif"); >>> >>> With ImageJ2, you can execute this headless from the command line as >>> follows: >>> >>> Contents/MacOS/ImageJ-macosx --headless -macro >>> ~/Desktop/variance.ijm >>> >>> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your >>> platform, and "~/Desktop/variance.ijm" is the path to that macro file >>> on >>> disk. >>> >>> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the >>> variance filter applied to the Blobs sample image. >>> >>> If you make the change from GenericDialog to >>> NonBlockingGenericDialog, >>> there will instead be an error as follows: >>> >>> java.lang.VerifyError: Bad type on operand stack >>> Exception Details: >>> Location: >>> ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic >>> Reason: >>> Type 'ij/gui/NonBlockingGenericDialog' (current frame, >>> stack[0]) is >>> not assignable to 'java/awt/Window' >>> >>> Followed by some details to assist with debugging. >>> >>> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, >>> work >>> will need to be done to improve the ImageJ Legacy component to >>> support >>> NonBlockingGenericDialog in headless mode. >>> >>> Regards, >>> Curtis >>> >>> -- >>> Curtis Rueden >>> LOCI software architect - https://loci.wisc.edu/software >>> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden >>> Have you tried the Image.sc Forum? https://forum.image.sc/ >>> >>> >>> >>> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL >>> <[hidden email]> >>> wrote: >>> >>>> Dear all (probably Wayne), >>>> Is there a reason why the Process>Filters are written with a >>>> GenericDialog >>>> and not a NonBlockingGenericDialog? >>>> Using a NonBlockingGenericDialog has the huge advantage of being >>>> able to >>>> make a preview on a picture with a given filter while still being >>>> able to >>>> zoom and drag it in order to check the effect of the filter on small >>>> details >>>> within the picture. >>>> And in order to do this, all that is needed to be changed is to: >>>> - Add on line 5: import ij.gui.NonBlockingGenericDialog; >>>> - Replace the line 112 from "GenericDialog gd = new >>>> GenericDialog(command+"...");" >>>> into " >>>> NonBlockingGenericDialog gd >>>> = >>>> new NonBlockingGenericDialog (command+"...");" >>>> Within the "ij.plugin.filter.RankFilters.java" file. >>>> I thank you very much in advance for your answers. >>>> My best regards, >>>> Philippe >>>> >>>> Philippe CARL >>>> Laboratoire de Bioimagerie et Pathologies >>>> UMR 7021 CNRS - Université de Strasbourg >> ____________________________________________________________________________________________________________________ >> import ij.*; >> import ij.measure.Measurements; >> import ij.process.*; >> import java.awt.*; >> import java.awt.event.*; >> >> >> public class PreviewHelper extends Panel implements MouseListener, >> AdjustmentListener { >> final static int WIDTH = 250; >> final static Dimension SB_SIZE = new Dimension(120,8); >> final static int BC_HEIGHT = 100; // brightness/contrast area >> final static int ZS_HEIGHT = 100; // zoom/scroll area >> final static int SB_FULL = 200; // scrollbar steps >> final static double MIN_CONTRAST = 0.25; >> final static double MAX_CONTRAST = 128; >> final ImagePlus imp; >> final boolean hasBC; //whether we have a >> Brightness&Contrast >> subpanel >> private final int height; >> private Scrollbar bScrollbar, cScrollbar, sScrollbar; >> private double brightness, contrast;// 0 - SB_FULL range each >> private static double saturated; // 0 - SB_FULL range, is >> remembered >> private boolean autoMode; // saturated overrides >> min&max >> if true >> Label rangeText; // text displaying min&max >> Label sLabel; // 'auto contrast' >> (saturated) >> label >> >> public PreviewHelper(ImagePlus imp) { >> this.imp = imp; >> hasBC = imp.getType() != ImagePlus.COLOR_RGB; >> height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT; >> prepareGui(); >> } >> >> private void prepareGui() { >> setLayout(new GridBagLayout()); >> GridBagConstraints c = new GridBagConstraints(); >> c.gridx = 0; c.gridy = 0; >> c.insets = new Insets(1, 1, 1, 1); >> c.fill = GridBagConstraints.HORIZONTAL; >> if (hasBC) { // setup B&C area >> add(new Label("Bright"),c); >> c.gridx++; >> bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, >> SB_FULL, >> 1, 0, SB_FULL+1); >> bScrollbar.setFocusable(false); // prevents blinking on >> Windows >> bScrollbar.setPreferredSize(SB_SIZE); >> bScrollbar.addAdjustmentListener(this); >> add(bScrollbar, c); >> c.gridx = 0; c.gridy++; >> add(new Label("Contr"),c); >> c.gridx++; >> cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, >> SB_FULL, >> 1, 0, SB_FULL+1); >> cScrollbar.setFocusable(false); // prevents blinking on >> Windows >> cScrollbar.setPreferredSize(SB_SIZE); >> cScrollbar.addAdjustmentListener(this); >> add(cScrollbar, c); >> c.gridx = 0; c.gridy++; >> sLabel = new Label("Auto"); >> sLabel.addMouseListener(this); >> add(sLabel, c); >> c.gridx++; >> sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, >> SB_FULL, >> 1, 0, SB_FULL+1); >> sScrollbar.setFocusable(false); // prevents blinking on >> Windows >> sScrollbar.setPreferredSize(SB_SIZE); >> sScrollbar.addAdjustmentListener(this); >> add(sScrollbar, c); >> >> calculateBC('m', imp.getProcessor()); //set initial >> scrollbar values >> } >> >> } >> >> /** Overrides Component getPreferredSize(). Added to work >> around a bug in Java 1.4.1 on Mac OS X.*/ >> public Dimension getPreferredSize() { >> return new Dimension(WIDTH+1, height+1); >> } >> >> >> public void update(Graphics g) { >> paint(g); >> } >> >> public void paint(Graphics g) { >> super.paint(g); >> drawRect(g, sLabel.getBounds()); >> } >> >> >> public void mousePressed(MouseEvent e) {} >> public void mouseReleased(MouseEvent e) {} >> public void mouseExited(MouseEvent e) {} >> public void mouseClicked(MouseEvent e) { >> Object source = e.getSource(); >> if (source==sLabel) { >> autoMode = !autoMode; >> sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? >> Font.BOLD >> : Font.PLAIN)); >> calculateBC('s', imp.getProcessor()); >> } >> } >> public void mouseEntered(MouseEvent e) {} >> >> public void adjustmentValueChanged(AdjustmentEvent e) { >> Object source = e.getSource(); >> if (source==bScrollbar) >> calculateBC('b', imp.getProcessor()); >> else if (source==cScrollbar) >> calculateBC('c', imp.getProcessor()); >> else if (source==sScrollbar) >> calculateBC('s', imp.getProcessor()); >> } >> >> /** If in 'Auto' mode, adjust brightness&contrast according to >> 'Auto' (saturated) scrollbar */ >> public void autoAdjust(ImageProcessor ip) { >> if (autoMode) calculateBC('s', ip); >> } >> >> /** Calculate brightness, contrast, min, and max; set >> imageProcessor and scrollbars >> * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast), >> 's'(aturation), 'm'(in&max) >> * If changed='m', the min and max of the imageProcessor sets >> B&C >> scrollbars >> * Otherwise, the scrollbar values set the imageProcessor's >> min&max >> */ >> private void calculateBC(char changed, ImageProcessor ip) { >> double fullMin = 0, fullMax = 255; >> double min = ip.getMin(), max = ip.getMax(); >> if (!(ip instanceof ByteProcessor)) { >> ip.resetMinAndMax(); // calculate range of pixels >> fullMin = ip.getMin(); >> fullMax = ip.getMax(); >> if (changed=='m') ip.setMinAndMax(min, max); //revert to >> previous min&max >> } >> double fullRange = fullMax - fullMin; >> if (fullRange <= 0) fullRange = 1e-100; //avoid division by >> 0 >> double fullMid = 0.5*(fullMax + fullMin); >> if (changed=='m') { >> sScrollbar.setValue((int)(saturated+0.5)); >> } else if (changed=='s') { >> saturated = sScrollbar.getValue(); >> min = fullMin; max=fullMax; >> if (saturated != 0) { >> ImageStatistics stats = >> ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null); >> int[] histogram = ip.getHistogram(); >> int total = 0; >> int first = -1, last = -1; >> for (int i=0; i<histogram.length; i++) { >> int n = histogram[i]; >> if (n > 0) { >> total += n; >> if (first < 0) first = i; >> last = i; >> } >> } >> int nSaturated = (int)(total*0.2*saturated/SB_FULL); >> //max 20% saturated >> int count = 0; >> int iMin = first; >> for (; iMin<last; iMin++) { >> count += histogram[iMin]; >> if (count>nSaturated) break; >> } >> count = 0; >> int iMax = last; >> for (; iMax>first; iMax--) { >> count += histogram[iMax]; >> if (count>nSaturated) break; >> } >> if (ip instanceof FloatProcessor) { >> } else { >> min = iMin; max = iMax; >> } >> } >> IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); >> ip.setMinAndMax(min,max); >> imp.updateAndDraw(); >> } >> if (changed=='m' || changed=='s') { //calculate and set B&C >> scrollbars >> double currentMid = 0.5*(max + min); >> double currentRange = max-min; >> if (currentRange <= 0) currentRange = 1e-100; >> IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); >> IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); >> brightness = SB_FULL * (0.5 - >> (currentMid-fullMid)/(fullRange)); >> contrast = SB_FULL * >> Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST); >> bScrollbar.setValue((int)(brightness+0.5)); >> cScrollbar.setValue((int)(contrast+0.5)); >> IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); >> } else if (changed=='b' || changed=='c') { >> brightness = bScrollbar.getValue(); >> contrast = cScrollbar.getValue(); >> //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); >> double currentMid = fullMid + fullRange*(0.5 - >> brightness/SB_FULL); >> double currentRange = >> fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST))); >> min = currentMid - 0.5*currentRange; >> max = currentMid + 0.5*currentRange; >> //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); >> //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max)); >> ip.setMinAndMax(min, max); >> imp.updateAndDraw(); >> } >> } >> >> //draw rectangle around a component >> private void drawRect(Graphics g, Rectangle r) { >> g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1); >> } >> } // ContrastPlot class >> >> ____________________________________________________________________________________________________________________ >> >> import ij.*; >> import ij.plugin.filter.ExtendedPlugInFilter; >> import ij.plugin.filter.PlugInFilterRunner; >> import ij.gui.GenericDialog; >> import ij.gui.DialogListener; >> import ij.process.*; >> import java.awt.*; >> import java.awt.event.*; >> >> //test plugin for Preview helper >> >> /* >> * An ImageJ ExtendedPlugInFilter that changes pixels that deviate >> from the >> * two neighbors above&below by more than a certain value (the >> threshold). >> * These pixels are set to the mean of these two neighbors. Pixels >> at the >> * top and bottom edges are not processed. >> * The filter is useful for eliminating hot pixels of CCDs and >> single >> * bad lines of scanning probe images. >> * >> * It demonstrates how to write an plugin-filter with preview. >> * Michael Schmid, 2007-05-21 >> */ >> >> public class Outliers_Simple implements ExtendedPlugInFilter, >> DialogListener { >> // ExtendedPlugInFilter: An ExtendedPlugInFilter will be >> invoked >> by ImageJ the >> // following way: >> // (1) Call to setup with the current foreground >> ImagePlus (image window) >> // and the argument string 'arg' specified in >> the >> plugins.config file >> // or a.jar file that contains it. >> // The PlugInFilter returns its flags. >> // (2) If the currently active image is compatible >> with the flags (and DONE >> // was not specified) showDialog is called. If the >> reference to the >> // PlugInFilterRunner passed with showDialog is >> specified as an argument >> // to the addPreviewCheckbox method of a >> GenericDialog, preview will be >> // possible and the run method of this >> ExtendedPlugInFilter will be called >> // in the background for preview. >> // (3) Unless otherwise specified in the flags >> (DONE >> or keeping the preview >> // as an output for a non-stack ImagePlus), the >> run method is called. >> // For stacks, it is called repeatedly for each >> slice. >> // (4) If the FINAL_PROCESSING flag has been set >> (but >> not DONE), the >> // setup method is invoked with "final" as parameter >> string. >> // ** If this PlugInFilter contains a showAbout() >> method, its setup method >> // may be also called with argument string >> "about". Then, the filter will >> // display a short help message. >> // >> // DialogListener: This PlugInFilter provides the >> dialogItemChanged method, whoch will >> // be called if required by the addDialogListener >> method of the >> // GenericDialog. >> // For preview-enabled ExtendedPlugInFilters, the >> filter parameters/options >> // should be set in the dialogItemChanged method. >> >> >> // F i l t e r p a r a m e t e r s - don't declare the >> paramters actually used as static, >> // otherwise parallel execution (in different threads with >> different parameters) won't work. >> /** The threshold. Only pixels deviating by more than this value >> will be replaced */ >> private double threshold; >> /** For saving the threshold; here we also put the initial value >> */ >> private static double sThreshold = 10; >> /** Whether to filter dark (instead of bright) pixels */ >> private static boolean filterDark = false; >> // F u r t h e r c l a s s v a r i a b l e s >> /** Flags specifying the capabilities and needs of this filter. See >> interfaces PlugInFilter and >> ExtendedPlugInFIlter for details */ >> int flags = >> DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW| >> PARALLELIZE_STACKS; >> PreviewHelper previewHelper; >> >> /** Setup of the PlugInFilter. Returns the flags specifying the >> capabilities and needs >> * of the filter. >> * >> * @param arg An argument string that may be specified, e.g., >> in >> the plugins.config file of a >> * jar archive containing th plugin. Unused here. >> * @param imp The ImagePlus to be processed (image with >> associated window, calibration, etc.) >> * @return Flags specifying further action of the >> PlugInFilterRunner >> */ >> public int setup(String arg, ImagePlus imp) { >> if (arg.equals("about")) { //this is a special >> case - we only display the "about..." info >> showAbout(); >> return DONE; >> } else { >> previewHelper = new PreviewHelper(imp); >> return flags; >> } >> } >> >> /** Show the dialog asking for the parameters and get them. >> * @return Flags specifying further action of the >> PlugInFilterRunner >> */ >> public int showDialog(ImagePlus imp, String command, >> PlugInFilterRunner pfr) { >> GenericDialog gd = new GenericDialog(command+"..."); >> gd.addNumericField("Threshold", sThreshold, 0); //use saved >> value as default >> gd.addCheckbox("Dark Outliers", filterDark); >> gd.addPreviewCheckbox(pfr); //passing pfr makes the >> filter >> ready for preview >> gd.addDialogListener(this); //the DialogItemChanged >> method >> will be called on user input >> gd.addPanel(previewHelper); >> gd.showDialog(); //display the dialog; preview >> runs >> in the background >> if (gd.wasCanceled()) >> return DONE; >> dialogItemChanged(gd, null); //read the parameters >> finally set >> IJ.register(this.getClass()); //protect static class >> variables (filter parameters) from garbage collection >> return IJ.setupDialog(imp, flags); //ask whether to process >> all slices of stack (if a stack) >> } >> >> /** If this PlugInFilter has been added as DialogListener, the >> method dialogItemChanged >> * is invoked by GenericDialog every time the user changes >> something in the dialog. >> * Time-consuming code should not be placed in this method and >> any >> methods called. >> * @param gd A reference to the GenericDialog, needed to read the >> input >> * @param e An Event characterizing what has been changed in >> the >> dialog. >> * @return Whether the dialog input is valid. If true, >> preview >> may be invoked. >> * (There is no need to check the state of the >> preview Checkbox here, >> * this is done by the PlugInFilterRunner.) >> * If false is returned (invalid input), the "OK" button of the >> dialog >> * and preview are disabled. >> */ >> public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { >> threshold = gd.getNextNumber(); >> filterDark = gd.getNextBoolean(); >> if (gd.invalidNumber() || threshold <= 0) >> return false; >> sThreshold = threshold; // save valid value for the next >> time >> return true; >> } >> >> /** This method is invoked by ImageJ for processing. For stacks, >> it >> can be called once for >> * each slice (depending on IJ.setupDialog, where the user is >> asked >> whether to do so, see >> * end of method setup, above). >> * If preview is enabled and the filter parameters have changed, >> processing should >> * be stopped when this thread is interrupted. Thherefore, >> during >> long calculations >> * (say, longer than a tenth of a second) the following code should >> be >> executed repeatedly: >> * <code> if (Thread.currentThread().isInterrupted()) return; >> </code> >> * Since this PlugInFilter has specified the CONVERT_TO_FLOAT >> and >> SNAPSHOT flags >> * it will be always called with a FloatProcessor that has a >> valid >> snapshot. >> * @param ip The ImageProcessor containing the image. >> */ >> public void run(ImageProcessor ip) { >> // For filters with preview, the filter parameter(s) should >> be >> copied from the >> // class variables set in DialogItemChanged to local variables to >> avoid changing >> // parameters during filtering. This can be done by calling a method >> with >> // the filter parameter(s) as arguments. >> // If the filter may crash with invalid or inconsistent parameters, >> parameter checking >> // can be done in dialogItemChanged. In that case, both the >> dialogItemChanged >> // method and copying should be done <code>synchronized</code> to >> avoid using >> // invalid parameters. The filtering operation itself should not be >> synchronized, >> // otherwise it would block the dialog during processing. >> doFiltering((FloatProcessor)ip, (float)threshold, >> filterDark); >> } >> >> /** Here the filtering is really done. >> * This is also the method that provides the API for use in >> other >> plugins, it should >> * not use any class variables. >> * A static method has the advantage that unintended reading of >> the >> filter parameters >> * from the class variables (that may change asynchronously >> during >> preview) will be >> * flagged by the compiler as error. >> * (it cannot be static if it needs to access any class variable >> such as the ImagePlus imp >> * or if we want to display a progress bar). >> * >> * This method only affects the pixels within the getRoi(ip) >> rectangle. Pixels within the >> * rectangle, but outside the actual roi will be reset by ImageJ >> if >> SUPPORTS_MASKING >> * has been added to the flags in the setup method. >> * >> * @param ip A FloatProcessor containing the image data. It must >> have a valid snapshot. >> * @param threshold Determines how much a pixel value may >> deviate >> from the mean of the >> * neighboring pixels to remain unaltered. Threshold is in >> uncalibrated units. >> * @param filterDark Whether dark outlies should be eliminated >> (otherwise bright outliers are >> * eliminated) >> */ >> public static void doFiltering(FloatProcessor ip, float >> threshold, >> boolean filterDark) { >> boolean filterMax = // whether to eliminate maxima (not >> minima) >> (ip.isInvertedLut()&&filterDark) || >> (!ip.isInvertedLut()&&!filterDark); >> Thread thread = Thread.currentThread(); // needed to >> check >> for interrupted state >> float[] pixels = (float[])ip.getPixels(); // array of the >> pixel values of the input image >> float[] snapshotPixels = >> (float[])ip.getSnapshotPixels(); // array with an >> (unaltered) copy of the pixel values >> int width = ip.getWidth(); // width & >> height >> of the image >> int height = ip.getHeight(); >> Rectangle roi = ip.getRoi(); // the rectangle >> containing the roi (full image size if no roi) >> int xEnd = roi.x + roi.width; // loops run only over pixels >> inside >> the roi >> int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost >> line (line y=0) >> int yEnd = roi.y + roi.height; >> if (yEnd == height) yEnd--; // do not process the bottommost >> line >> (line y=height-1) >> for (int y=yStart; y<yEnd; y++) { >> if (y%100==0 && thread.isInterrupted()) return; // from time to >> time, check whether interrupted >> for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to >> pixel (x,y) in the arrays >> float neighbor1 = snapshotPixels[p+width]; // the neighbor >> at >> (x, y+1) >> float neighbor2 = snapshotPixels[p-width]; // the neighbor at >> (x, >> y-1) >> if ((filterMax && snapshotPixels[p]>neighbor1+threshold && >> snapshotPixels[p]>neighbor2+threshold) || >> (!filterMax && snapshotPixels[p]<neighbor1-threshold && >> snapshotPixels[p]<neighbor2-threshold)) >> pixels[p] = (neighbor1+neighbor2)/2; >> } >> } >> } >> >> /** This method required by the ExtendedPlugInFilter interface is not >> used here. >> * It specifies the number of calls to the run(ip) method. >> * For filters displaying a progress bar, nPasses is needed to >> display a >> * smooth progress bar when processing stacks. */ >> public void setNPasses (int nPasses) {} >> >> /** Show the "About..." text. >> * This method must be named "showAbout", then the plugin is >> added >> to the >> * Help>About Plugins menu >> */ >> private void showAbout() { >> IJ.showMessage("About Outliers...", >> "This plug-in filter alters pixels that deviate from their \n"+ >> "neighbors above and below by more than a given threshold. \n" + >> "These outliers are set to the mean of these neighbors. \n" + >> "Edge pixels at the top and bottom of the image are not \n" + >> "processed." >> ); >> } >> } >> >> ____________________________________________________________________________________________________________________ >> >> -- >> ImageJ mailing list: http://imagej.nih.gov/ij/list.html >> >> -- >> ImageJ mailing list: http://imagej.nih.gov/ij/list.html >> > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
Dear Michael,
You will find modified versions (i.e. using NonBlockingGenericDialog and getting rid of the static parameters) of the filters plugins under the following links: http://punias.free.fr/ImageJ/NonBlocking/RankFilters.java http://punias.free.fr/ImageJ/NonBlocking/GaussianBlur.java http://punias.free.fr/ImageJ/NonBlocking/UnsharpMask.java http://punias.free.fr/ImageJ/NonBlocking/Convolver.java I didn't try to generate NonBlockingGenericDialog versions for the 3D filters plugins given that they didn't have a preview button. Also I have to admit that I don't understand your recommendations about what should be done with the use of the ij.process.ImageProcessor.snapshot() method. My best regards, Philippe Philippe CARL Laboratoire de Bioimagerie et Pathologies UMR 7021 CNRS - Université de Strasbourg Faculté de Pharmacie 74 route du Rhin 67401 ILLKIRCH Tel : +33(0)3 68 85 41 84 -----Message d'origine----- De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid Envoyé : mercredi 27 février 2019 18:50 À : [hidden email] Objet : Re: NonBlockingGenericDialog for Process>Filters Bonjour Philippe, and Hi everyone in the non-francophone regions, there is more to care about when using NonBlockingGenericDialog for the filters: Any action that overwrites the undo buffer ("snapshot") will interfere with preview (which uses the snapshot). Plugins that lock the image (all PlugInFilters) should be no problem; running any of these on a locked image should cause an error message. I did a quick search for "snapshot" and found the following potentially problematic ones (currently none of them locks the image they work on): ContrastEnhancer GelAnalyzer ImageCalculator * ScaleBar Scaler * ContrastAdjuster RoiManager * I think these should lock the image anyhow Out of these, the ContrastAdjuster (Brightness&Contrast) and ContrastEnhancer are most relevant; if the user tries to adjust the contrast during preview to better see the result, and if there is either a ROI or the image is RGB, it will make it impossible to revert preview. A possible way out might be keeping the snapshot during preview, and using setSnapshotPixels before reverting from preview. But this will need more thought. What one should also think of is whether creating or modifying ROIs should be blocked while an image is locked. To me, the snapshot issue is currently the most severe problem (Wayne, are you aware of any others? Or someone else?). Concerning the static variables, I think that the cleaner and easier solution would be fixing it in the relevant plugins; then we need no mechanism to block concurrent running of GaussianBlur etc. Best, Michael ________________________________________________________________ On 2019-02-26 12:05, Philippe CARL wrote: > Servus Michael, > To play with several Gaussian Blur windows in parallel, I indeed > didn't think about it. > And considering static parameters definitions, I now agree completely > with you that this will indeed be an issue. > My idea (and actually need) behind NonBlockingGenericDialog filters > was to be nice and gentle with them, by only using "regular" tools in > parallel with these filters like zoom, drag, threshold, ..., i.e. > tools where there would be no static parameter definition issues. > So in order to fix this potential static parameter issue, what would > you think about restricting the creation of only one instance of these > filters (potentially throwing an error message in the case a user > wants to create a second one)? > This solution would enlarge the analysis versatility, providing in the > same the "needed security" for the static parameters. > As the issue with the headless mode, it could be solved with the > addition of a new option within "Edit>Options>Misc..." window. > Experimental test plugin: Ok I saw these 3 sliders. But thinking more > about it and considering what I described higher, i.e. the combined > use of the tools zoom, drag and threshold with a filter, I think that > your idea becomes already too complicated. > I also saw that the Analyze>Analyze-particles... tool now excludes the > holes. Thanks a lot!!! > Kindest regards, > Philippe > > -----Message d'origine----- > De : ImageJ Interest Group [mailto:[hidden email]] De la part de > Michael Schmid > Envoyé : lundi 25 février 2019 17:02 > À : [hidden email] > Objet : Re: NonBlockingGenericDialog for Process>Filters > > Hi Philippe, > > a typical case of two Gaussian Blurs interfering would be the > following: > > Have one Gaussian Blur of a stack running (a large stack so it take > some > time) and at the same time do a preview of another GaussianBlur with a > different radius (sigma). Then, as soon as the preview starts, the > remaining slices of the stack will be processed with the sigma of the > preview, not the one that should be used for the stack. > > In principle, you can do the same thing already now (I just tried with > a > 1024x1024x1024 stack that needs about 15-20 seconds for sigma=7, and > quickly started another GaussianBlur on another image with different > sigma before the stack was fully processed - the last slices stack got > blurred with sigma intended for the other image). Fortunately, it is > rather unlikely that this happens in practice. > > It is much more likely that this kind of problem occurs if one can have > several GaussianBlur dialogs open at one time, e.g., to compare how it > affects different images. Then it would be much more likely that > GaussianBlur runs on two images/stacks at the same moment. > > Of course, the clean solution would be making the class variables for > the parameters non-static, and adding the static ones for saving the > parameters, in GaussianBlur, UnsharpMask, Convolver, and ImageMath (the > BackgroundSubtracter & RankFilters seem to be fine). In principle, it > would be easy, but it has to be done. > > Then, we still have to care about the headless mode... > > -- > Experimental test plugin: There should be three additional sliders > (rather narrow ones) for adjusting B&C, but only for grayscale images > (not for RGB). > > > Michael > ________________________________________________________________ > On 25.02.19 15:41, Philippe CARL wrote: >> Gruss Gott Michael, >> I made as well a GenericDialog to NonBlockingGenericDialog conversion >> onto the GaussianBlur.java file, replaced the compiled >> GaussianBlur.class within the IJ.jar and then made all crazy things I >> could think of which could be problematic having static variables. >> More precisely, I opened two pictures, launched a Gaussian blurr on >> one of it, activate the preview button and then selected the other >> one, applied a Contras&Brightness update and then modified the >> Gaussian Blurr filter values and the modifications were only applied >> on the picture on which the filter had been launched. >> Alternatively, I launched a Gaussian blur on one picture, activated >> the preview button, than opened another one, played with it, and >> finally modified the filter values and the updates were on the picture >> on which the filter had at first be applied. >> So which issues could up to you be found on some filters due to static >> variables? >> I compiled as well the codes you had as attachment, but wasn't able to >> see any special zoom and drag buttons (unless I missed something) when >> I launched them. >> Thanks a lot on advance for your very lightened answer. >> Kindest regards, >> Philippe >> >> -----Message d'origine----- >> De : ImageJ Interest Group [mailto:[hidden email]] De la part de >> Michael Schmid >> Envoyé : lundi 25 février 2019 13:17 >> À : [hidden email] >> Objet : Re: NonBlockingGenericDialog for Process>Filters >> >> Hi Philippe, >> >> well, I fear that making standard PlugInFilter dialogs non-modal could >> cause more problems than just headless execution. >> >> Unfortunately, most filters are not multithreading-safe. With a >> non-modal dialog there would be nothing that prevents the user to use >> the same filter on two different images at the same time, which would >> result in unpredictable behavior. >> I think that the RankFilters (Mean, Variance, Median, Min, Max) are >> fine. >> Gaussian Blur, Convolve, and the 3D filters use static variables for >> the >> filter parameters, so they must remain modal. >> >> What I had thought about a while ago was creating a GenericDialog >> field >> that would allow the user to pan and zoom in/out with a line of small >> buttons (arrows and +/-) or scrollbars during preview. Maybe also >> dragging in a small square representing the image. >> Same for brightness&contrast. >> I had an experimental version for B&C with scrollbars, but these were >> too clumsy and then I did not find time to continue on that project. >> In >> case you want to make something nice out of it (or someone else wants >> to >> do this), feel free to use my experimental code pasted at the very >> bottom! >> >> >> Michael >> ________________________________________________________________ >> On 25.02.19 11:53, Philippe CARL wrote: >>> Dear Curtis, >>> Given that I never use ImageJ in headless mode, I wasn't aware of >>> this potential issue. >>> Thus what would you think about adding an additional option within >>> "Edit>Options>Misc..." which would give the possibility to choose >>> between GenericDialog or NonBlockingGenericDialog dialogs within all >>> the filter tools? >>> This could give an agreement between both functionalities. >>> My best regards, >>> Philippe >>> >>> -----Message d'origine----- >>> De : ImageJ Interest Group [mailto:[hidden email]] De la part de >>> Curtis Rueden >>> Envoyé : vendredi 22 février 2019 19:01 >>> À : [hidden email] >>> Objet : Re: NonBlockingGenericDialog for Process>Filters >>> >>> Hi Philippe, >>> >>>> Is there a reason why the Process>Filters are written with a >>>> GenericDialog and not a NonBlockingGenericDialog? >>> >>> One reason is that ImageJ2's headless support currently only supports >>> GenericDialog, not NonBlockingGenericDialog. >>> >>> Consider the following macro: >>> >>> run("Blobs (25K)"); >>> run("Variance...", "radius=2"); >>> run("Save", "save=/Users/curtis/Desktop/blobs.tif"); >>> >>> With ImageJ2, you can execute this headless from the command line as >>> follows: >>> >>> Contents/MacOS/ImageJ-macosx --headless -macro >>> ~/Desktop/variance.ijm >>> >>> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your >>> platform, and "~/Desktop/variance.ijm" is the path to that macro file >>> on >>> disk. >>> >>> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the >>> variance filter applied to the Blobs sample image. >>> >>> If you make the change from GenericDialog to >>> NonBlockingGenericDialog, >>> there will instead be an error as follows: >>> >>> java.lang.VerifyError: Bad type on operand stack >>> Exception Details: >>> Location: >>> ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic >>> Reason: >>> Type 'ij/gui/NonBlockingGenericDialog' (current frame, >>> stack[0]) is >>> not assignable to 'java/awt/Window' >>> >>> Followed by some details to assist with debugging. >>> >>> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, >>> work >>> will need to be done to improve the ImageJ Legacy component to >>> support >>> NonBlockingGenericDialog in headless mode. >>> >>> Regards, >>> Curtis >>> >>> -- >>> Curtis Rueden >>> LOCI software architect - https://loci.wisc.edu/software >>> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden >>> Have you tried the Image.sc Forum? https://forum.image.sc/ >>> >>> >>> >>> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL >>> <[hidden email]> >>> wrote: >>> >>>> Dear all (probably Wayne), >>>> Is there a reason why the Process>Filters are written with a >>>> GenericDialog >>>> and not a NonBlockingGenericDialog? >>>> Using a NonBlockingGenericDialog has the huge advantage of being >>>> able to >>>> make a preview on a picture with a given filter while still being >>>> able to >>>> zoom and drag it in order to check the effect of the filter on small >>>> details >>>> within the picture. >>>> And in order to do this, all that is needed to be changed is to: >>>> - Add on line 5: import ij.gui.NonBlockingGenericDialog; >>>> - Replace the line 112 from "GenericDialog gd = new >>>> GenericDialog(command+"...");" >>>> into " >>>> NonBlockingGenericDialog gd >>>> = >>>> new NonBlockingGenericDialog (command+"...");" >>>> Within the "ij.plugin.filter.RankFilters.java" file. >>>> I thank you very much in advance for your answers. >>>> My best regards, >>>> Philippe >>>> >>>> Philippe CARL >>>> Laboratoire de Bioimagerie et Pathologies >>>> UMR 7021 CNRS - Université de Strasbourg >> ____________________________________________________________________________________________________________________ >> import ij.*; >> import ij.measure.Measurements; >> import ij.process.*; >> import java.awt.*; >> import java.awt.event.*; >> >> >> public class PreviewHelper extends Panel implements MouseListener, >> AdjustmentListener { >> final static int WIDTH = 250; >> final static Dimension SB_SIZE = new Dimension(120,8); >> final static int BC_HEIGHT = 100; // brightness/contrast area >> final static int ZS_HEIGHT = 100; // zoom/scroll area >> final static int SB_FULL = 200; // scrollbar steps >> final static double MIN_CONTRAST = 0.25; >> final static double MAX_CONTRAST = 128; >> final ImagePlus imp; >> final boolean hasBC; //whether we have a >> Brightness&Contrast >> subpanel >> private final int height; >> private Scrollbar bScrollbar, cScrollbar, sScrollbar; >> private double brightness, contrast;// 0 - SB_FULL range each >> private static double saturated; // 0 - SB_FULL range, is >> remembered >> private boolean autoMode; // saturated overrides >> min&max >> if true >> Label rangeText; // text displaying min&max >> Label sLabel; // 'auto contrast' >> (saturated) >> label >> >> public PreviewHelper(ImagePlus imp) { >> this.imp = imp; >> hasBC = imp.getType() != ImagePlus.COLOR_RGB; >> height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT; >> prepareGui(); >> } >> >> private void prepareGui() { >> setLayout(new GridBagLayout()); >> GridBagConstraints c = new GridBagConstraints(); >> c.gridx = 0; c.gridy = 0; >> c.insets = new Insets(1, 1, 1, 1); >> c.fill = GridBagConstraints.HORIZONTAL; >> if (hasBC) { // setup B&C area >> add(new Label("Bright"),c); >> c.gridx++; >> bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, >> SB_FULL, >> 1, 0, SB_FULL+1); >> bScrollbar.setFocusable(false); // prevents blinking on >> Windows >> bScrollbar.setPreferredSize(SB_SIZE); >> bScrollbar.addAdjustmentListener(this); >> add(bScrollbar, c); >> c.gridx = 0; c.gridy++; >> add(new Label("Contr"),c); >> c.gridx++; >> cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, >> SB_FULL, >> 1, 0, SB_FULL+1); >> cScrollbar.setFocusable(false); // prevents blinking on >> Windows >> cScrollbar.setPreferredSize(SB_SIZE); >> cScrollbar.addAdjustmentListener(this); >> add(cScrollbar, c); >> c.gridx = 0; c.gridy++; >> sLabel = new Label("Auto"); >> sLabel.addMouseListener(this); >> add(sLabel, c); >> c.gridx++; >> sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, >> SB_FULL, >> 1, 0, SB_FULL+1); >> sScrollbar.setFocusable(false); // prevents blinking on >> Windows >> sScrollbar.setPreferredSize(SB_SIZE); >> sScrollbar.addAdjustmentListener(this); >> add(sScrollbar, c); >> >> calculateBC('m', imp.getProcessor()); //set initial >> scrollbar values >> } >> >> } >> >> /** Overrides Component getPreferredSize(). Added to work >> around a bug in Java 1.4.1 on Mac OS X.*/ >> public Dimension getPreferredSize() { >> return new Dimension(WIDTH+1, height+1); >> } >> >> >> public void update(Graphics g) { >> paint(g); >> } >> >> public void paint(Graphics g) { >> super.paint(g); >> drawRect(g, sLabel.getBounds()); >> } >> >> >> public void mousePressed(MouseEvent e) {} >> public void mouseReleased(MouseEvent e) {} >> public void mouseExited(MouseEvent e) {} >> public void mouseClicked(MouseEvent e) { >> Object source = e.getSource(); >> if (source==sLabel) { >> autoMode = !autoMode; >> sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? >> Font.BOLD >> : Font.PLAIN)); >> calculateBC('s', imp.getProcessor()); >> } >> } >> public void mouseEntered(MouseEvent e) {} >> >> public void adjustmentValueChanged(AdjustmentEvent e) { >> Object source = e.getSource(); >> if (source==bScrollbar) >> calculateBC('b', imp.getProcessor()); >> else if (source==cScrollbar) >> calculateBC('c', imp.getProcessor()); >> else if (source==sScrollbar) >> calculateBC('s', imp.getProcessor()); >> } >> >> /** If in 'Auto' mode, adjust brightness&contrast according to >> 'Auto' (saturated) scrollbar */ >> public void autoAdjust(ImageProcessor ip) { >> if (autoMode) calculateBC('s', ip); >> } >> >> /** Calculate brightness, contrast, min, and max; set >> imageProcessor and scrollbars >> * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast), >> 's'(aturation), 'm'(in&max) >> * If changed='m', the min and max of the imageProcessor sets >> B&C >> scrollbars >> * Otherwise, the scrollbar values set the imageProcessor's >> min&max >> */ >> private void calculateBC(char changed, ImageProcessor ip) { >> double fullMin = 0, fullMax = 255; >> double min = ip.getMin(), max = ip.getMax(); >> if (!(ip instanceof ByteProcessor)) { >> ip.resetMinAndMax(); // calculate range of pixels >> fullMin = ip.getMin(); >> fullMax = ip.getMax(); >> if (changed=='m') ip.setMinAndMax(min, max); //revert to >> previous min&max >> } >> double fullRange = fullMax - fullMin; >> if (fullRange <= 0) fullRange = 1e-100; //avoid division by >> 0 >> double fullMid = 0.5*(fullMax + fullMin); >> if (changed=='m') { >> sScrollbar.setValue((int)(saturated+0.5)); >> } else if (changed=='s') { >> saturated = sScrollbar.getValue(); >> min = fullMin; max=fullMax; >> if (saturated != 0) { >> ImageStatistics stats = >> ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null); >> int[] histogram = ip.getHistogram(); >> int total = 0; >> int first = -1, last = -1; >> for (int i=0; i<histogram.length; i++) { >> int n = histogram[i]; >> if (n > 0) { >> total += n; >> if (first < 0) first = i; >> last = i; >> } >> } >> int nSaturated = (int)(total*0.2*saturated/SB_FULL); >> //max 20% saturated >> int count = 0; >> int iMin = first; >> for (; iMin<last; iMin++) { >> count += histogram[iMin]; >> if (count>nSaturated) break; >> } >> count = 0; >> int iMax = last; >> for (; iMax>first; iMax--) { >> count += histogram[iMax]; >> if (count>nSaturated) break; >> } >> if (ip instanceof FloatProcessor) { >> } else { >> min = iMin; max = iMax; >> } >> } >> IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); >> ip.setMinAndMax(min,max); >> imp.updateAndDraw(); >> } >> if (changed=='m' || changed=='s') { //calculate and set B&C >> scrollbars >> double currentMid = 0.5*(max + min); >> double currentRange = max-min; >> if (currentRange <= 0) currentRange = 1e-100; >> IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); >> IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange)); >> brightness = SB_FULL * (0.5 - >> (currentMid-fullMid)/(fullRange)); >> contrast = SB_FULL * >> Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST); >> bScrollbar.setValue((int)(brightness+0.5)); >> cScrollbar.setValue((int)(contrast+0.5)); >> IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); >> } else if (changed=='b' || changed=='c') { >> brightness = bScrollbar.getValue(); >> contrast = cScrollbar.getValue(); >> //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast)); >> double currentMid = fullMid + fullRange*(0.5 - >> brightness/SB_FULL); >> double currentRange = >> fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST))); >> min = currentMid - 0.5*currentRange; >> max = currentMid + 0.5*currentRange; >> //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange)); >> //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max)); >> ip.setMinAndMax(min, max); >> imp.updateAndDraw(); >> } >> } >> >> //draw rectangle around a component >> private void drawRect(Graphics g, Rectangle r) { >> g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1); >> } >> } // ContrastPlot class >> >> ____________________________________________________________________________________________________________________ >> >> import ij.*; >> import ij.plugin.filter.ExtendedPlugInFilter; >> import ij.plugin.filter.PlugInFilterRunner; >> import ij.gui.GenericDialog; >> import ij.gui.DialogListener; >> import ij.process.*; >> import java.awt.*; >> import java.awt.event.*; >> >> //test plugin for Preview helper >> >> /* >> * An ImageJ ExtendedPlugInFilter that changes pixels that deviate >> from the >> * two neighbors above&below by more than a certain value (the >> threshold). >> * These pixels are set to the mean of these two neighbors. Pixels >> at the >> * top and bottom edges are not processed. >> * The filter is useful for eliminating hot pixels of CCDs and >> single >> * bad lines of scanning probe images. >> * >> * It demonstrates how to write an plugin-filter with preview. >> * Michael Schmid, 2007-05-21 >> */ >> >> public class Outliers_Simple implements ExtendedPlugInFilter, >> DialogListener { >> // ExtendedPlugInFilter: An ExtendedPlugInFilter will be >> invoked >> by ImageJ the >> // following way: >> // (1) Call to setup with the current foreground >> ImagePlus (image window) >> // and the argument string 'arg' specified in >> the >> plugins.config file >> // or a.jar file that contains it. >> // The PlugInFilter returns its flags. >> // (2) If the currently active image is compatible >> with the flags (and DONE >> // was not specified) showDialog is called. If the >> reference to the >> // PlugInFilterRunner passed with showDialog is >> specified as an argument >> // to the addPreviewCheckbox method of a >> GenericDialog, preview will be >> // possible and the run method of this >> ExtendedPlugInFilter will be called >> // in the background for preview. >> // (3) Unless otherwise specified in the flags >> (DONE >> or keeping the preview >> // as an output for a non-stack ImagePlus), the >> run method is called. >> // For stacks, it is called repeatedly for each >> slice. >> // (4) If the FINAL_PROCESSING flag has been set >> (but >> not DONE), the >> // setup method is invoked with "final" as parameter >> string. >> // ** If this PlugInFilter contains a showAbout() >> method, its setup method >> // may be also called with argument string >> "about". Then, the filter will >> // display a short help message. >> // >> // DialogListener: This PlugInFilter provides the >> dialogItemChanged method, whoch will >> // be called if required by the addDialogListener >> method of the >> // GenericDialog. >> // For preview-enabled ExtendedPlugInFilters, the >> filter parameters/options >> // should be set in the dialogItemChanged method. >> >> >> // F i l t e r p a r a m e t e r s - don't declare the >> paramters actually used as static, >> // otherwise parallel execution (in different threads with >> different parameters) won't work. >> /** The threshold. Only pixels deviating by more than this value >> will be replaced */ >> private double threshold; >> /** For saving the threshold; here we also put the initial value >> */ >> private static double sThreshold = 10; >> /** Whether to filter dark (instead of bright) pixels */ >> private static boolean filterDark = false; >> // F u r t h e r c l a s s v a r i a b l e s >> /** Flags specifying the capabilities and needs of this filter. See >> interfaces PlugInFilter and >> ExtendedPlugInFIlter for details */ >> int flags = >> DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW| >> PARALLELIZE_STACKS; >> PreviewHelper previewHelper; >> >> /** Setup of the PlugInFilter. Returns the flags specifying the >> capabilities and needs >> * of the filter. >> * >> * @param arg An argument string that may be specified, e.g., >> in >> the plugins.config file of a >> * jar archive containing th plugin. Unused here. >> * @param imp The ImagePlus to be processed (image with >> associated window, calibration, etc.) >> * @return Flags specifying further action of the >> PlugInFilterRunner >> */ >> public int setup(String arg, ImagePlus imp) { >> if (arg.equals("about")) { //this is a special >> case - we only display the "about..." info >> showAbout(); >> return DONE; >> } else { >> previewHelper = new PreviewHelper(imp); >> return flags; >> } >> } >> >> /** Show the dialog asking for the parameters and get them. >> * @return Flags specifying further action of the >> PlugInFilterRunner >> */ >> public int showDialog(ImagePlus imp, String command, >> PlugInFilterRunner pfr) { >> GenericDialog gd = new GenericDialog(command+"..."); >> gd.addNumericField("Threshold", sThreshold, 0); //use saved >> value as default >> gd.addCheckbox("Dark Outliers", filterDark); >> gd.addPreviewCheckbox(pfr); //passing pfr makes the >> filter >> ready for preview >> gd.addDialogListener(this); //the DialogItemChanged >> method >> will be called on user input >> gd.addPanel(previewHelper); >> gd.showDialog(); //display the dialog; preview >> runs >> in the background >> if (gd.wasCanceled()) >> return DONE; >> dialogItemChanged(gd, null); //read the parameters >> finally set >> IJ.register(this.getClass()); //protect static class >> variables (filter parameters) from garbage collection >> return IJ.setupDialog(imp, flags); //ask whether to process >> all slices of stack (if a stack) >> } >> >> /** If this PlugInFilter has been added as DialogListener, the >> method dialogItemChanged >> * is invoked by GenericDialog every time the user changes >> something in the dialog. >> * Time-consuming code should not be placed in this method and >> any >> methods called. >> * @param gd A reference to the GenericDialog, needed to read the >> input >> * @param e An Event characterizing what has been changed in >> the >> dialog. >> * @return Whether the dialog input is valid. If true, >> preview >> may be invoked. >> * (There is no need to check the state of the >> preview Checkbox here, >> * this is done by the PlugInFilterRunner.) >> * If false is returned (invalid input), the "OK" button of the >> dialog >> * and preview are disabled. >> */ >> public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { >> threshold = gd.getNextNumber(); >> filterDark = gd.getNextBoolean(); >> if (gd.invalidNumber() || threshold <= 0) >> return false; >> sThreshold = threshold; // save valid value for the next >> time >> return true; >> } >> >> /** This method is invoked by ImageJ for processing. For stacks, >> it >> can be called once for >> * each slice (depending on IJ.setupDialog, where the user is >> asked >> whether to do so, see >> * end of method setup, above). >> * If preview is enabled and the filter parameters have changed, >> processing should >> * be stopped when this thread is interrupted. Thherefore, >> during >> long calculations >> * (say, longer than a tenth of a second) the following code should >> be >> executed repeatedly: >> * <code> if (Thread.currentThread().isInterrupted()) return; >> </code> >> * Since this PlugInFilter has specified the CONVERT_TO_FLOAT >> and >> SNAPSHOT flags >> * it will be always called with a FloatProcessor that has a >> valid >> snapshot. >> * @param ip The ImageProcessor containing the image. >> */ >> public void run(ImageProcessor ip) { >> // For filters with preview, the filter parameter(s) should >> be >> copied from the >> // class variables set in DialogItemChanged to local variables to >> avoid changing >> // parameters during filtering. This can be done by calling a method >> with >> // the filter parameter(s) as arguments. >> // If the filter may crash with invalid or inconsistent parameters, >> parameter checking >> // can be done in dialogItemChanged. In that case, both the >> dialogItemChanged >> // method and copying should be done <code>synchronized</code> to >> avoid using >> // invalid parameters. The filtering operation itself should not be >> synchronized, >> // otherwise it would block the dialog during processing. >> doFiltering((FloatProcessor)ip, (float)threshold, >> filterDark); >> } >> >> /** Here the filtering is really done. >> * This is also the method that provides the API for use in >> other >> plugins, it should >> * not use any class variables. >> * A static method has the advantage that unintended reading of >> the >> filter parameters >> * from the class variables (that may change asynchronously >> during >> preview) will be >> * flagged by the compiler as error. >> * (it cannot be static if it needs to access any class variable >> such as the ImagePlus imp >> * or if we want to display a progress bar). >> * >> * This method only affects the pixels within the getRoi(ip) >> rectangle. Pixels within the >> * rectangle, but outside the actual roi will be reset by ImageJ >> if >> SUPPORTS_MASKING >> * has been added to the flags in the setup method. >> * >> * @param ip A FloatProcessor containing the image data. It must >> have a valid snapshot. >> * @param threshold Determines how much a pixel value may >> deviate >> from the mean of the >> * neighboring pixels to remain unaltered. Threshold is in >> uncalibrated units. >> * @param filterDark Whether dark outlies should be eliminated >> (otherwise bright outliers are >> * eliminated) >> */ >> public static void doFiltering(FloatProcessor ip, float >> threshold, >> boolean filterDark) { >> boolean filterMax = // whether to eliminate maxima (not >> minima) >> (ip.isInvertedLut()&&filterDark) || >> (!ip.isInvertedLut()&&!filterDark); >> Thread thread = Thread.currentThread(); // needed to >> check >> for interrupted state >> float[] pixels = (float[])ip.getPixels(); // array of the >> pixel values of the input image >> float[] snapshotPixels = >> (float[])ip.getSnapshotPixels(); // array with an >> (unaltered) copy of the pixel values >> int width = ip.getWidth(); // width & >> height >> of the image >> int height = ip.getHeight(); >> Rectangle roi = ip.getRoi(); // the rectangle >> containing the roi (full image size if no roi) >> int xEnd = roi.x + roi.width; // loops run only over pixels >> inside >> the roi >> int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost >> line (line y=0) >> int yEnd = roi.y + roi.height; >> if (yEnd == height) yEnd--; // do not process the bottommost >> line >> (line y=height-1) >> for (int y=yStart; y<yEnd; y++) { >> if (y%100==0 && thread.isInterrupted()) return; // from time to >> time, check whether interrupted >> for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to >> pixel (x,y) in the arrays >> float neighbor1 = snapshotPixels[p+width]; // the neighbor >> at >> (x, y+1) >> float neighbor2 = snapshotPixels[p-width]; // the neighbor at >> (x, >> y-1) >> if ((filterMax && snapshotPixels[p]>neighbor1+threshold && >> snapshotPixels[p]>neighbor2+threshold) || >> (!filterMax && snapshotPixels[p]<neighbor1-threshold && >> snapshotPixels[p]<neighbor2-threshold)) >> pixels[p] = (neighbor1+neighbor2)/2; >> } >> } >> } >> >> /** This method required by the ExtendedPlugInFilter interface is not >> used here. >> * It specifies the number of calls to the run(ip) method. >> * For filters displaying a progress bar, nPasses is needed to >> display a >> * smooth progress bar when processing stacks. */ >> public void setNPasses (int nPasses) {} >> >> /** Show the "About..." text. >> * This method must be named "showAbout", then the plugin is >> added >> to the >> * Help>About Plugins menu >> */ >> private void showAbout() { >> IJ.showMessage("About Outliers...", >> "This plug-in filter alters pixels that deviate from their \n"+ >> "neighbors above and below by more than a given threshold. \n" + >> "These outliers are set to the mean of these neighbors. \n" + >> "Edge pixels at the top and bottom of the image are not \n" + >> "processed." >> ); >> } >> } >> >> ____________________________________________________________________________________________________________________ >> >> -- >> ImageJ mailing list: http://imagej.nih.gov/ij/list.html >> >> -- >> ImageJ mailing list: http://imagej.nih.gov/ij/list.html >> > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html > > -- > ImageJ mailing list: http://imagej.nih.gov/ij/list.html -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
Free forum by Nabble | Edit this page |