diff --git a/plugins/BIOP/ArgoLight_default_argoslide_params.csv b/plugins/BIOP/ArgoLight_default_argoslide_params.csv index b706e08..664d55b 100644 --- a/plugins/BIOP/ArgoLight_default_argoslide_params.csv +++ b/plugins/BIOP/ArgoLight_default_argoslide_params.csv @@ -1,3 +1,4 @@ ArgoSIM,false,5,100,21 ArgoSLJ488,false,15,660,45 ArgoSLG482,true,15,570,39 +ArgoHM,false,15,660,45 diff --git a/plugins/BIOP/ArgoLight_default_params.csv b/plugins/BIOP/ArgoLight_default_params.csv index 1958036..802bfd0 100644 --- a/plugins/BIOP/ArgoLight_default_params.csv +++ b/plugins/BIOP/ArgoLight_default_params.csv @@ -1,5 +1,5 @@ -OMERO Host,omero-server-poc.epfl.ch +OMERO Host,omero-server-test.epfl.ch OMERO Port,4064 -ArgoSlides,ArgoSIM,ArgoSLJ488,ArgoSLG482 +ArgoSlides,ArgoSIM,ArgoSLJ488,ArgoSLG482,ArgoHM Root folder,D:\Remy\ArgoLight\new protocole\Raw Data Saving folder,D:\Remy\ArgoLight\new protocole diff --git a/pom.xml b/pom.xml index ba7fbb1..952f4b6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.scijava pom-scijava - 43.0.0 + 44.0.0 diff --git a/src/main/java/ch/epfl/biop/command/ArgoLightCommand.java b/src/main/java/ch/epfl/biop/command/ArgoLightCommand.java index 1e598df..64694c7 100644 --- a/src/main/java/ch/epfl/biop/command/ArgoLightCommand.java +++ b/src/main/java/ch/epfl/biop/command/ArgoLightCommand.java @@ -18,30 +18,14 @@ import loci.plugins.BF; import loci.plugins.in.ImporterOptions; import net.imagej.ImageJ; +import omero.gateway.exception.DSAccessException; +import org.scijava.Cancelable; import org.scijava.command.Command; +import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; +import org.scijava.thread.ThreadService; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.DefaultComboBoxModel; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JDialog; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JRadioButton; -import javax.swing.JRootPane; -import javax.swing.JSeparator; -import javax.swing.JSpinner; -import javax.swing.JTextField; -import javax.swing.SpinnerModel; -import javax.swing.SpinnerNumberModel; -import javax.swing.UIManager; +import javax.swing.*; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dialog; @@ -65,6 +49,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; /** * This plugin runs image analysis pipeline on ArgoLight slide, pattern B, to measure the quality of objectives over @@ -73,7 +60,15 @@ * or locally. */ @Plugin(type = Command.class, menuPath = "Plugins>BIOP>ArgoLight analysis tool") -public class ArgoLightCommand implements Command { +public class ArgoLightCommand implements Command, Cancelable { + + @Parameter + private ThreadService threadService; + private final AtomicBoolean canceled = new AtomicBoolean(false); + private String cancelReason; + private Future processingFuture; + + private String userHost; private String userPort; private List omeroMicroscopes = Collections.emptyList(); @@ -82,7 +77,7 @@ public class ArgoLightCommand implements Command { private List localMicroscopes = Collections.emptyList(); private List userArgoSlides; private String defaultArgoSlide; - private Map> argoSlidesParameters = Collections.emptyMap(); + private final Map> argoSlidesParameters = new HashMap<>(); private String userRootFolder; private String userSaveFolder; private double userSigma; @@ -95,7 +90,6 @@ public class ArgoLightCommand implements Command { private boolean isDefaultThresholdMethod; private boolean isDefaultParticleThresh; private boolean isDefaultRingRadius; - private boolean startsProcessing = false; private JDialog mainDialog; private JDialog settingsDialog; @@ -140,6 +134,7 @@ public class ArgoLightCommand implements Command { final private Font stdFont = new Font("Calibri", Font.PLAIN, 17); final private Font titleFont = new Font("Calibri", Font.BOLD, 22); + private enum CONNECTION_STATE{ CONNECTED("Disconnect"), DISCONNECTED("Connect"); @@ -238,7 +233,8 @@ private void runProcessing(boolean isOmeroRetriever, String omeroFolderName, boo List argoParams = argoSlidesParameters.get(argoSlide); // run analysis - if (nImages > 0) + if (nImages > 0) { + checkCanceled(); Processing.run(retriever, saveHeatMaps, sender, isDefaultSigma ? defaultSigma : userSigma, isDefaultMedianRadius ? defaultMedianRadius : userMedianRadius, @@ -248,8 +244,9 @@ private void runProcessing(boolean isOmeroRetriever, String omeroFolderName, boo argoSlide, Integer.parseInt(argoParams.get(argoSpacingPos)), Integer.parseInt(argoParams.get(argoFoVPos)), - Integer.parseInt(argoParams.get(argoNRingsPos))); - else { + Integer.parseInt(argoParams.get(argoNRingsPos)), + ArgoLightCommand.this); + } else { IJLogger.warn("Parent container : "+rawTarget + ", microscope " + microscope + " does not contain any images"); showWarningMessage("No Images", " Parent container : "+rawTarget + ", microscope '" + microscope + "', does not contain any images." + "

" + @@ -267,13 +264,7 @@ private void runProcessing(boolean isOmeroRetriever, String omeroFolderName, boo } catch (Exception e){ finalPopupMessage = false; IJLogger.error("Unexpected issue occurred", e); - } finally { - if(this.client.isConnected()) { - this.client.disconnect(); - IJLogger.info("Disconnected from OMERO "); - } } - IJLogger.info("ArgoLight Analysis Tool exited"); if(finalPopupMessage) { showInfoMessage("Processing Done", "All images have been analyzed and results saved"); @@ -292,6 +283,32 @@ private boolean connectToOmero(Client client, String username, char[] password){ } } + @Override + public boolean isCanceled() { + return canceled.get(); + } + + @Override + public void cancel(String reason) { + this.cancelReason = reason; + this.canceled.set(true); + + if (processingFuture != null) { + processingFuture.cancel(true); + } + IJLogger.warn("Processing cancelled: " + reason); + } + + @Override + public String getCancelReason() { + return cancelReason; + } + + public void checkCanceled() { + if (canceled.get()) { + throw new CancellationException(cancelReason); + } + } /** * build the main user interface @@ -691,6 +708,8 @@ private void createGui(){ // change the default button (when pressing enter with the keyboard) omeroPane.getRootPane().setDefaultButton(bOk); cbProject.requestFocus(); + }else{ + return; } }else{ if (this.client.isConnected()) { @@ -730,34 +749,89 @@ private void createGui(){ cbProject.setSelectedItem(omeroProjects); }); - bOk.addActionListener(e->{ - startsProcessing = true; - mainDialog.dispose(); + JButton bCancel = new JButton("Cancel"); + bCancel.setFont(stdFont); + bCancel.addActionListener(e->{ + if(bCancel.getText().equals("Cancel")) { + mainDialog.dispose(); + if (this.client.isConnected()) { + this.client.disconnect(); + IJLogger.info("Disconnected from OMERO "); + } + IJLogger.info("ArgoLight Analysis Tool exited"); + }else{ + cancel("Aborted by user !"); + } + }); - char[] password = tfPassword.getPassword(); + bOk.addActionListener(e->{ + // freeze UI + cbMicroscope.setEnabled(false); + cbArgoSlide.setEnabled(false); + bArgoSlideSettings.setEnabled(false); + bLivePreview.setEnabled(false); + chkSaveHeatMap.setEnabled(false); + chkAllImages.setEnabled(false); + rbOmeroSender.setEnabled(false); + rbLocalSender.setEnabled(false); + rbOmeroRetriever.setEnabled(false); + rbLocalRetriever.setEnabled(false); + cbProject.setEnabled(false); + rbOmeroDataset.setEnabled(false); + rbOmeroProject.setEnabled(false); + bGeneralSettings.setEnabled(false); + bProcessingSettings.setEnabled(false); + bConnectToOmero.setEnabled(false); + bOk.setEnabled(false); + bCancel.setText("Abort"); String folderName; if(rbOmeroProject.isSelected() && rbOmeroRetriever.isSelected()) folderName = (String)cbDataset.getSelectedItem(); else folderName = (String)cbProject.getSelectedItem(); - runProcessing(rbOmeroRetriever.isSelected(), - folderName, - rbOmeroProject.isSelected(), - tfRootFolder.getText(), - ((String)cbMicroscope.getSelectedItem()), - ((String)cbArgoSlide.getSelectedItem()), - rbOmeroSender.isSelected(), - tfSavingFolder.getText(), - chkSaveHeatMap.isSelected(), - chkAllImages.isSelected(), - chkRemovePreviousRun.isSelected()); - }); - JButton bCancel = new JButton("Cancel"); - bCancel.setFont(stdFont); - bCancel.addActionListener(e->{ - mainDialog.dispose(); + // send processing in another thread to get the Logs in the log window + processingFuture = threadService.run(() -> { + canceled.set(false); + try { + runProcessing(rbOmeroRetriever.isSelected(), + folderName, + rbOmeroProject.isSelected(), + tfRootFolder.getText(), + ((String) cbMicroscope.getSelectedItem()), + ((String) cbArgoSlide.getSelectedItem()), + rbOmeroSender.isSelected(), + tfSavingFolder.getText(), + chkSaveHeatMap.isSelected(), + chkAllImages.isSelected(), + chkRemovePreviousRun.isSelected()); + } catch (CancellationException e1) { + IJLogger.warn("Processing stopped by user."); + } + + // release UI + SwingUtilities.invokeLater(() -> { + cbMicroscope.setEnabled(true); + cbArgoSlide.setEnabled(true); + bArgoSlideSettings.setEnabled(true); + bLivePreview.setEnabled(true); + chkSaveHeatMap.setEnabled(true); + chkAllImages.setEnabled(true); + rbOmeroSender.setEnabled(true); + rbLocalSender.setEnabled(true); + rbOmeroRetriever.setEnabled(true); + rbLocalRetriever.setEnabled(true); + cbProject.setEnabled(true); + rbOmeroDataset.setEnabled(true); + rbOmeroProject.setEnabled(true); + bGeneralSettings.setEnabled(true); + bProcessingSettings.setEnabled(true); + bConnectToOmero.setEnabled(true); + bOk.setEnabled(true); + bCancel.setText("Cancel"); + }); + }); }); // build everything together @@ -1003,7 +1077,7 @@ private void createGui(){ omeroPane.setBorder(BorderFactory.createEmptyBorder(20,20,20,20)); mainDialog.getContentPane().add(omeroPane); mainDialog.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL); - mainDialog.setResizable(false); + mainDialog.setResizable(true); if (JDialog.isDefaultLookAndFeelDecorated()) { boolean supportsWindowDecorations = @@ -1024,13 +1098,11 @@ private void createGui(){ mainDialog.setVisible(true); mainDialog.dispose(); - if(!startsProcessing) { - if (this.client.isConnected()) { - this.client.disconnect(); - IJLogger.info("Disconnected from OMERO "); - } - IJLogger.info("ArgoLight Analysis Tool exited"); + if (this.client.isConnected()) { + this.client.disconnect(); + IJLogger.info("Disconnected from OMERO "); } + IJLogger.info("ArgoLight Analysis Tool exited"); } @@ -1146,7 +1218,7 @@ private void createArgoSettingsPane(String argoSlide){ // replace old values argoSlidesParameters.clear(); - argoSlidesParameters = newMap; + argoSlidesParameters.putAll(newMap); } } @@ -1249,8 +1321,9 @@ else if(!userSelectedFile.getAbsolutePath().endsWith(".csv")) if(readyToSave) { String argoSlidesList = String.join(",", argoSettings.keySet()); tfArgoslide.setText(argoSlidesList); - argoSlidesParameters = tempMap; - saveArgoSlideParams(); + argoSlidesParameters.clear(); + argoSlidesParameters.putAll(tempMap); + saveArgoSlideParams(tempMap); setDefaultArgoParams(); }else{ showErrorMessage("ArgoSlide settings", "The provided CSV is not correctly formatted. " + @@ -2328,7 +2401,7 @@ private void setDefaultArgoParams(){ // replace old parameters argoSlidesParameters.clear(); - argoSlidesParameters = cleanArgoSlides; + argoSlidesParameters.putAll(cleanArgoSlides); } /** @@ -2526,9 +2599,10 @@ private Map> saveUserDefinedArgoSlideParams(String argoSli argoslideParamList.add(String.valueOf(argoFov)); argoslideParamList.add(String.valueOf(argoNRings)); tempMap.put(argoSlide, argoslideParamList); - argoSlidesParameters = tempMap; - return saveArgoSlideParams(); + saveArgoSlideParams(tempMap); + + return tempMap; } /** @@ -2536,7 +2610,7 @@ private Map> saveUserDefinedArgoSlideParams(String argoSli * * @return */ - private Map> saveArgoSlideParams() { + private void saveArgoSlideParams(Map> argoSlidesParametersMap) { File directory = new File(folderName); if(!directory.exists()) @@ -2546,8 +2620,8 @@ private Map> saveArgoSlideParams() { File file = new File(directory.getAbsoluteFile() + File.separator + argoSlideFileName); // write the file BufferedWriter buffer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)); - for(String argoSlideKey : argoSlidesParameters.keySet()){ - String argoSlideParamCSV = String.join(",", argoSlidesParameters.get(argoSlideKey)); + for(String argoSlideKey : argoSlidesParametersMap.keySet()){ + String argoSlideParamCSV = String.join(",", argoSlidesParametersMap.get(argoSlideKey)); buffer.write(argoSlideKey+","+ argoSlideParamCSV + "\n"); } // close the file @@ -2556,7 +2630,6 @@ private Map> saveArgoSlideParams() { } catch (IOException e) { showWarningMessage("CSV writing","Couldn't write the csv for ArgoSlide parameters."); } - return argoSlidesParameters; } public static void main(String... args){ diff --git a/src/main/java/ch/epfl/biop/processing/ArgoSlideOldProcessing.java b/src/main/java/ch/epfl/biop/processing/ArgoSlideOldProcessing.java index a994a35..45c6d8c 100644 --- a/src/main/java/ch/epfl/biop/processing/ArgoSlideOldProcessing.java +++ b/src/main/java/ch/epfl/biop/processing/ArgoSlideOldProcessing.java @@ -1,5 +1,6 @@ package ch.epfl.biop.processing; +import ch.epfl.biop.command.ArgoLightCommand; import ch.epfl.biop.image.ImageChannel; import ch.epfl.biop.image.ImageFile; import ch.epfl.biop.utils.IJLogger; @@ -31,7 +32,7 @@ public class ArgoSlideOldProcessing { final private static int argoNPoints = 21; // on each row/column final private static String thresholdingMethod = "Huang dark"; - public static void run(ImageFile imageFile){ + public static void run(ImageFile imageFile, ArgoLightCommand argoLightCommand){ ImagePlus imp = imageFile.getImage(); // pixel size of the image final double pixelSizeImage = imp.getCalibration().pixelWidth; @@ -52,6 +53,7 @@ public static void run(ImageFile imageFile){ imageFile.addKeyValue("thresholding_method", thresholdingMethod); RoiManager roiManager = new RoiManager(); + argoLightCommand.checkCanceled(); for(int c = 0; c < NChannels; c++){ // reset all windows @@ -65,6 +67,7 @@ public static void run(ImageFile imageFile){ imp.setPosition(c+1,1,1); channel.setProcessor(imp.getProcessor()); channel.show(); + argoLightCommand.checkCanceled(); // get the central cross Roi crossRoi = getCentralCross(channel, roiManager, pixelSizeImage); @@ -74,6 +77,7 @@ public static void run(ImageFile imageFile){ // add the cross ROI on the image roiManager.addRoi(crossRoi); channel.setRoi(crossRoi); + argoLightCommand.checkCanceled(); double sigma = 0.14 * argoSpacing / (pixelSizeImage * Math.sqrt(2)); List gridPoints = getGridPoint2(channel, crossRoi, pixelSizeImage, sigma); @@ -97,6 +101,7 @@ public static void run(ImageFile imageFile){ double rotationAngle = getRotationAngle(gridPoints, crossRoi); imageChannel.setRotationAngle(rotationAngle*180/Math.PI); IJLogger.info("Channel "+c,"Rotation angle theta = "+rotationAngle*180/Math.PI + "°"); + argoLightCommand.checkCanceled(); // get the ideal grid List idealGridPoints = getIdealGridPoints(crossRoi, (int)Math.sqrt(gridPoints.size() + 1), xStepAvg, yStepAvg, rotationAngle); @@ -105,6 +110,7 @@ public static void run(ImageFile imageFile){ // sort the computed grid points according to ideal grid order gridPoints = sortFromReference(Arrays.asList(roiManager.getRoisAsArray()), idealGridPoints); + argoLightCommand.checkCanceled(); // display all points (grid and ideal) roiManager.reset(); @@ -121,11 +127,15 @@ public static void run(ImageFile imageFile){ imageChannel.addIdealRings(idealGridPointsRoi); roiManager.runCommand(channel,"Show All without labels"); + argoLightCommand.checkCanceled(); // compute metrics imageChannel.addFieldDistortion(computeFieldDistortion(gridPoints, idealGridPoints, pixelSizeImage)); + argoLightCommand.checkCanceled(); imageChannel.addFieldUniformity(computeFieldUniformity(gridPoints,channel,ovalRadius)); + argoLightCommand.checkCanceled(); imageChannel.addFWHM(computeFWHM(gridPoints,channel, lineLength, roiManager, pixelSizeImage)); + argoLightCommand.checkCanceled(); imageFile.addChannel(imageChannel); } @@ -133,9 +143,11 @@ public static void run(ImageFile imageFile){ roiManager.reset(); roiManager.close(); IJ.run("Close All", ""); - - if(NChannels > 1) + argoLightCommand.checkCanceled(); + if(NChannels > 1) { imageFile.computePCC(); + } + argoLightCommand.checkCanceled(); } diff --git a/src/main/java/ch/epfl/biop/processing/ArgoSlideProcessing.java b/src/main/java/ch/epfl/biop/processing/ArgoSlideProcessing.java index f10767b..fea11de 100644 --- a/src/main/java/ch/epfl/biop/processing/ArgoSlideProcessing.java +++ b/src/main/java/ch/epfl/biop/processing/ArgoSlideProcessing.java @@ -1,5 +1,6 @@ package ch.epfl.biop.processing; +import ch.epfl.biop.command.ArgoLightCommand; import ch.epfl.biop.image.ImageChannel; import ch.epfl.biop.image.ImageFile; import ch.epfl.biop.utils.IJLogger; @@ -66,7 +67,8 @@ public class ArgoSlideProcessing { * @param argoNPoints number of rings in the same line */ public static void run(ImageFile imageFile, double userSigma, double userMedianRadius, String userThresholdingMethod, - double userParticleThreshold, double userRingRadius, String argoSlide, int argoSpacing, int argoFOV, int argoNPoints){ + double userParticleThreshold, double userRingRadius, String argoSlide, int argoSpacing, + int argoFOV, int argoNPoints, ArgoLightCommand argoLightCommand){ final ImagePlus imp = imageFile.getImage(); // pixel size of the image @@ -107,19 +109,22 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR IJLogger.info("Detection parameters","Particle threshold : "+particleThreshold + " pix"); RoiManager roiManager = RoiManager.getRoiManager(); + argoLightCommand.checkCanceled(); for(int c = 0; c < NChannels; c++){ // reset all windows IJ.run("Close All", ""); roiManager.reset(); + argoLightCommand.checkCanceled(); ImageChannel imageChannel = new ImageChannel(c, imp.getWidth(), imp.getHeight(), pixelSizeImage); // extract the current channel ImagePlus channel = IJ.createHyperStack(imp.getTitle() + "_ch" + c, imp.getWidth(), imp.getHeight(), 1, 1, 1, imp.getBitDepth()); - imp.setPosition(c+1,1,1); + imp.setPosition(c+1,(int)((imp.getNSlices() + 1)/2),(int)((imp.getNFrames() + 1)/2)); channel.setProcessor(imp.getProcessor()); channel.show(); + argoLightCommand.checkCanceled(); // get the central cross Roi crossRoi = Processing.getCentralCross(channel, roiManager, pixelSizeImage, userThresholdingMethod, argoFOV); @@ -131,6 +136,7 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR imageChannel.setCenterCross(crossRoi); IJLogger.info("Channel "+c,"Cross = " +crossRoi); + argoLightCommand.checkCanceled(); // add the cross ROI on the image roiManager.addRoi(crossRoi); @@ -144,6 +150,7 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR "Cannot compute metrics"); throw new RuntimeException(); } + argoLightCommand.checkCanceled(); // display all points (grid and ideal) roiManager.reset(); @@ -178,6 +185,7 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR imageChannel.setRotationAngle(rotationAngle); IJLogger.info("Channel "+c,"Rotation angle theta = "+rotationAngle*180/Math.PI + "°"); + argoLightCommand.checkCanceled(); // create grid point ROIs gridPoints.forEach(pR-> {roiManager.addRoi(new OvalRoi((pR.getX()-4*ovalRadius+0.5), pR.getY()-4*ovalRadius+0.5, 8*ovalRadius, 8*ovalRadius));}); @@ -188,6 +196,7 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR // sort the computed grid points according to ideal grid order gridPoints = Processing.sortFromReference(Arrays.asList(roiManager.getRoisAsArray()), idealGridPoints); + argoLightCommand.checkCanceled(); // display all points (grid and ideal) roiManager.reset(); @@ -208,15 +217,20 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR } idealGridPointsRoi.forEach(roiManager::addRoi); imageChannel.addIdealRings(idealGridPointsRoi); + argoLightCommand.checkCanceled(); // compute metrics + argoLightCommand.checkCanceled(); imageChannel.addFieldDistortion(Processing.computeFieldDistortion(gridPoints, idealGridPoints, pixelSizeImage)); - imageChannel.addFieldUniformity(Processing.computeFieldUniformity(gridPoints, channel,ovalRadius)); + argoLightCommand.checkCanceled(); + imageChannel.addFieldUniformity(Processing.computeFieldUniformity(gridPoints, channel, ovalRadius)); // add tags to the image imageFile.addTags(Tools.FIELD_DISTORTION_TAG, Tools.FIELD_UNIFORMITY_TAG); + argoLightCommand.checkCanceled(); } if(!imageFile.getImagedFoV().equals(Tools.FULL_FOV)){ roiManager.reset(); + argoLightCommand.checkCanceled(); List fwhmGridPointsRoiList = new ArrayList<>(); // create grid point ROIs @@ -228,6 +242,7 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR fwhmGridPointsRoiList.add(roi); } + argoLightCommand.checkCanceled(); // save ROIs imageChannel.addGridRings(fwhmGridPointsRoiList); // add grid point centers @@ -236,6 +251,8 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR imageChannel.addFWHM(Processing.computeFWHM(smallerGrid, channel, lineLength, pixelSizeImage)); // add tag to image imageFile.addTags(Tools.FWHM_TAG); + + argoLightCommand.checkCanceled(); } roiManager.runCommand(channel,"Show All without labels"); imageFile.addChannel(imageChannel); @@ -243,8 +260,11 @@ public static void run(ImageFile imageFile, double userSigma, double userMedianR roiManager.reset(); roiManager.close(); IJ.run("Close All", ""); + argoLightCommand.checkCanceled(); - if(NChannels > 1) + if(NChannels > 1) { imageFile.computePCC(); + } + argoLightCommand.checkCanceled(); } } diff --git a/src/main/java/ch/epfl/biop/processing/Processing.java b/src/main/java/ch/epfl/biop/processing/Processing.java index 2ea332c..9c43fe7 100644 --- a/src/main/java/ch/epfl/biop/processing/Processing.java +++ b/src/main/java/ch/epfl/biop/processing/Processing.java @@ -1,5 +1,6 @@ package ch.epfl.biop.processing; +import ch.epfl.biop.command.ArgoLightCommand; import ch.epfl.biop.image.ImageChannel; import ch.epfl.biop.image.ImageFile; import ch.epfl.biop.retrievers.Retriever; @@ -15,6 +16,7 @@ import ij.measure.ResultsTable; import ij.plugin.frame.RoiManager; import ij.process.ImageStatistics; +import org.scijava.Cancelable; import java.awt.Rectangle; import java.awt.geom.AffineTransform; @@ -27,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CancellationException; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -51,8 +54,10 @@ public class Processing { * @param argoFOV FoV of the pattern B of the ArgoSlide in um * @param argoNPoints number of rings in the same line */ - public static void run(Retriever retriever, boolean savingHeatMaps, Sender sender, double userSigma, double userMedianRadius, String userThresholdingMethod, - double userParticleThreshold, double userRingRadius, String argoSlide, int argoSpacing, int argoFOV, int argoNPoints){ + public static void run(Retriever retriever, boolean savingHeatMaps, Sender sender, double userSigma, + double userMedianRadius, String userThresholdingMethod, + double userParticleThreshold, double userRingRadius, String argoSlide, + int argoSpacing, int argoFOV, int argoNPoints, ArgoLightCommand argoLightCommand){ Map>> summaryMap = new HashMap<>(); List headers = new ArrayList<>(); List IDs = retriever.getIDs(); @@ -82,18 +87,21 @@ public static void run(Retriever retriever, boolean savingHeatMaps, Sender sende // choose the right ArgoLight processing if (!imageFile.getArgoSlideName().contains("ArgoSimOld")) { ArgoSlideProcessing.run(imageFile, userSigma, userMedianRadius, userThresholdingMethod, - userParticleThreshold, userRingRadius, argoSlide, argoSpacing, argoFOV, argoNPoints); + userParticleThreshold, userRingRadius, argoSlide, argoSpacing, argoFOV, argoNPoints, + argoLightCommand); } else { - ArgoSlideOldProcessing.run(imageFile); + ArgoSlideOldProcessing.run(imageFile, argoLightCommand); isOldProtocol = true; } IJLogger.info("End of processing"); + argoLightCommand.checkCanceled(); IJLogger.info("Sending results ... "); // send image results (metrics, rings, tags, key-values) sender.initialize(imageFile, retriever); sender.sendTags(imageFile.getTags()); + argoLightCommand.checkCanceled(); sendResults(sender, imageFile, savingHeatMaps, isOldProtocol, argoSpacing); // metrics summary to populate parent table @@ -102,6 +110,8 @@ public static void run(Retriever retriever, boolean savingHeatMaps, Sender sende if (!allChannelMetrics.values().isEmpty()) summaryMap.put(uniqueID, allChannelMetrics.values().iterator().next()); + } catch (CancellationException e) { + throw e; } catch (Exception e) { IJLogger.error("An error occurred during processing ; cannot analyse the image " + imgTitle, e); } @@ -109,6 +119,7 @@ public static void run(Retriever retriever, boolean savingHeatMaps, Sender sende } // populate parent table with summary results + argoLightCommand.checkCanceled(); sender.populateParentTable(retriever, summaryMap, headers, !retriever.isProcessingAllRawImages()); } @@ -123,8 +134,9 @@ private static void sendResults(Sender sender, ImageFile imageFile, boolean savi Map keyValues = imageFile.getKeyValues(); // send PCC table - if (imageFile.getNChannels() > 1) + if (imageFile.getNChannels() > 1) { sender.sendPCCTable(imageFile.getPCC(), imageFile.getNChannels()); + } List> distortionValues = new ArrayList<>(); List> uniformityValues = new ArrayList<>(); diff --git a/src/main/java/ch/epfl/biop/senders/LocalSender.java b/src/main/java/ch/epfl/biop/senders/LocalSender.java index c954739..9bdbf05 100644 --- a/src/main/java/ch/epfl/biop/senders/LocalSender.java +++ b/src/main/java/ch/epfl/biop/senders/LocalSender.java @@ -30,6 +30,8 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.zip.ZipEntry; @@ -209,6 +211,28 @@ public void sendGridPoints(List rois, int channelId, String roiTitle) { @Override public void sendResultsTable(List> values, List channelIdList, boolean createNewTable, String tableName){ IJLogger.info("Sending "+tableName+" table"); + + // Make sure that the different columns have the same number of rows i.e. all channels properly detected + // If it's not the case, each column with nRows < nMaxRow will have -1 instead + SortedSet sizes = new TreeSet<>(); + for(List val: values){ + sizes.add(val.size()); + } + + if(sizes.size() > 1){ + IJLogger.warn("Not all rings have been detected on all the channels. Only measurements for channels " + + "with all rings detected will be sent."); + int maxSize = sizes.last(); + for(List val: values){ + if(val.size() != maxSize){ + val.clear(); + for(int i = 0; i< maxSize; i++){ + val.add(-1d); + } + } + } + } + String text = createNewTable(values, channelIdList); File file = new File(this.imageFolder + File.separator + tableName + "_" + Tools.PARENT_TABLE_SUFFIX + ".csv"); @@ -314,19 +338,22 @@ public void sendTags(List tags) { return; } + // filter unique tags + List uniqueTags = tags.stream().distinct().collect(Collectors.toList()); + // add tags on OMERO if images was initially on the OMERO server - for(String tag : tags) { + for(String tag : uniqueTags) { try { // get the corresponding tag in the list of available tags if exists List rawTag = groupTags.stream().filter(t -> t.getName().equals(tag)).collect(Collectors.toList()); // check if the tag is already applied to the current image - boolean isTagAlreadyExists = imageTags + boolean isTagAlreadyExisting = imageTags .stream() .anyMatch(t -> t.getName().equals(tag)); // add the tag to the current image if it is not already the case - if (!isTagAlreadyExists) { + if (!isTagAlreadyExisting) { imageWrapper.link(this.client, rawTag.isEmpty() ? new TagAnnotationWrapper(new TagAnnotationData(tag)) : rawTag.get(0)); IJLogger.info("Adding tag","The tag " + tag + " has been successfully applied on the image " + imageWrapper.getId()); } else diff --git a/src/main/java/ch/epfl/biop/senders/OMEROSender.java b/src/main/java/ch/epfl/biop/senders/OMEROSender.java index 1f13f85..0d019f8 100644 --- a/src/main/java/ch/epfl/biop/senders/OMEROSender.java +++ b/src/main/java/ch/epfl/biop/senders/OMEROSender.java @@ -42,6 +42,8 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.LongStream; @@ -93,19 +95,22 @@ private void sendTags(List tags, ImageWrapper imageWrapper) { return; } + // filter unique tags + List uniqueTags = tags.stream().distinct().collect(Collectors.toList()); + // loop on tags to add - for(String tag : tags) { + for(String tag : uniqueTags) { try { // get the corresponding tag in the list of available tags if exists List rawTag = groupTags.stream().filter(t -> t.getName().equals(tag)).collect(Collectors.toList()); // check if the tag is already applied to the current image - boolean isTagAlreadyExists = imageTags + boolean isTagAlreadyExisting = imageTags .stream() .anyMatch(t -> t.getName().equals(tag)); // add the tag to the current image if it is not already the case - if (!isTagAlreadyExists) { + if (!isTagAlreadyExisting) { imageWrapper.link(this.client, rawTag.isEmpty() ? new TagAnnotationWrapper(new TagAnnotationData(tag)) : rawTag.get(0)); IJLogger.info("Adding tag","The tag " + tag + " has been successfully applied on the image " + imageWrapper.getId()); } else @@ -339,6 +344,27 @@ public void sendResultsTable(List> values, List channelIdL // get the last OMERO table TableWrapper table = getOmeroTable(this.client, this.imageWrapper, tableName); + // Make sure that the different columns have the same number of rows i.e. all channels properly detected + // If it's not the case, each column with nRows < nMaxRow will have -1 instead + SortedSet sizes = new TreeSet<>(); + for(List val: values){ + sizes.add(val.size()); + } + + if(sizes.size() > 1){ + IJLogger.warn("Not all rings have been detected on all the channels. Only measurements for channels " + + "with all rings detected will be sent."); + int maxSize = sizes.last(); + for(List val: values){ + if(val.size() != maxSize){ + val.clear(); + for(int i = 0; i< maxSize; i++){ + val.add(-1d); + } + } + } + } + // populate existing table or create a new one if(!createNewTable && table != null) tableWrapper = addNewColumnsToTable(table, values, channelIdList, this.date); @@ -436,7 +462,7 @@ private TableWrapper createNewTable(List> values, List cha List> measurements = new ArrayList<>(); int i = 0; - if(values.size() > 0) { + if(!values.isEmpty()) { // add the first column with the image data (linkable on OMERO) columns.add(new TableDataColumn("Image ID", i++, ImageData.class)); List imageData = new ArrayList<>();