Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

saveDetectionMeasurements issue in batch mode #136

Closed
erexhepa opened this issue Jan 11, 2018 · 14 comments
Closed

saveDetectionMeasurements issue in batch mode #136

erexhepa opened this issue Jan 11, 2018 · 14 comments

Comments

@erexhepa
Copy link

I'm having a weird issue with the saveDetectionMeasurement().

  • I break the image in tiles that are then sent to ImageJ for processing (Java+python code).
  • The results are then reintegrated (ROI+results) from the IJ RoiManager into QuPath.
  • On the inserted objects I do some morphology and intensity calculations

Once all the tiles have been processed I call

selectAnnotations()
saveAnnotationMeasurements( outAnnoationsStatFname)
selectDetections();
saveDetectionMeasurements( outDetectionsStatFname)

When I select run script on one slide everything is fine.
When I try to run it in batch mode saveAnnotationMeasurements works and produces the same output as in single slide mode but saveDetectionMeasurement() produces only a file with the string "Name" in it.

Am I missing something on batch/single-slide mode ?

@Svidro
Copy link

Svidro commented Jan 11, 2018

Just to get things started, you do not need to select anything before saving! I always have those two commands at the end of my scripts, and they are not related to what has been selected, it is just a full list of everything that shows up when you look in "Show detection measurements" or "Show annotation measurements" in the Measure menu. I wonder if you remove the selectDetections if maybe that will help things, since selecting all of the detections might cause a slowdown that messes up the save measurements command. You also might try the guiscript=true command at the top of your script, which seems to help some of the cases where the script doesn't allow a command to finish before trying the next one.

Is it generating one file for every slide image?
Could you show us the part of your script where outDetectionsStatFname is created?

The only issues I have ever had with the saveDetectionMeasurements were when the output was over 2GB, and it doesn't sound like the issue here, so I am not sure what is happening.

@erexhepa
Copy link
Author

erexhepa commented Jan 11, 2018

I wasn't sure whether select annotations/detections had to be called before the actual processing but if it can be avoived it is great as depending on how many objects you have it might be slow.

yes it is generating one file per slide with the idea of deleting everything once the analysis is over for the slide (although this bit of code is commented now).

outAnnoationsStatFname = "H://"+strfnameTrim+"_steatosis_annotations.txt"

This is the line creating the variable holding the filename.

It is not an issue of size (I thought about that too) because if you limit the analysis to the first 10 tiles for every slide you are dealing with files of <100K. Although the complete file it should be < 20MB.

@petebankhead
Copy link
Member

I’ve tried to replicate this, but couldn’t (albeit on my Mac). Which platform are you running on?

There can indeed be trouble when trying to save very large detection measurement tables; a String is generated first then written to a file. The rationale was that this meant the same code could be used to generate data for copying to clipboard... but if the String ends up exceeding the maximum length permitted by Java, then it fails. Anyhow, I understand that's not the trouble here.

Does it make any difference is you save the detections first, then the annotations?

The option of last resort is probably to just write the export code operating on the detections directly. That would at least give maximum flexibility, and should be reasonably concise with Groovy. If you decide to go that way the following might help:

def fileOutput = new File(buildFilePath(PROJECT_BASE_DIR, 'detections.txt'))

// Get the detections
def detections = getDetectionObjects()

// Get the names, in an ordered set
def names = new LinkedHashSet<>()
detections.each {names.addAll(it.getMeasurementList().getMeasurementNames())}

// Loop through objects & names
fileOutput.text = String.join('\t', names)
df = new java.text.DecimalFormat('#.###')
detections.each { detection ->
    // Map measurements for the object to a suitably-formatted string
    def values = names.collect({name -> df.format(detection.getMeasurementList().getMeasurementValue(name))})
    fileOutput << String.join('\t', values) << System.lineSeparator()
}

print 'Done!'

@Svidro
Copy link

Svidro commented Jan 11, 2018

Ok, if the deletion part is commented out entirely, and the names are not overlapping, my only thoughts are whether the removal of the selectDetections code changes anything, as that slowdown has caused problems in another script that dealt with merging annotations (#129).

The other would be to simply include the folder name and let the function generate the filename, to see if that part is working.

saveDetectionMeasurements("H://", )

@erexhepa
Copy link
Author

erexhepa commented Jan 11, 2018

the removal of selectDetectionMeasurement() and guiscript=true didn't change anything.

I will give a try to the suggestions above and try from another machine. The current platform is a Windows but at home I have a Mac.

Will let you know. Thanks a mill for the quick feedback.

@Svidro
Copy link

Svidro commented Jan 11, 2018

Sorry my suggestions haven't been more useful! Also, I do get an empty text file with Name only when I export detection files and there are no detections present.

Is there any chance you could perform a def detections = getDetections() and then print out detections.size to make sure that the detections are there to be exported, directly before the export? Just trying to eliminate possibilities. I haven't run into this problem on Windows yet, but would love to know what is causing it in case I do!

@erexhepa
Copy link
Author

erexhepa commented Jan 12, 2018

all suggestions above did not work.

getDetections() doesn't seem to be defined or it is not visible within the java classes I import. I will try to setup IntelliJ.

However getDetectionObjects() has a size 0 (both single and batch mode). Could this be the problem ? What I realised is that the same script but on objects that are created with Qupath and not with ImageJ works fine in both batch and single slide mode.

Is there maybe a problem with the type of objects created by the ImageJ plugin that import the objects into qupath ?

@Svidro
Copy link

Svidro commented Jan 12, 2018

Right, getDetectionObjects was what I meant, thanks for figuring that out! And that sounds like we are a lot closer to figuring this out.
Unfortunately I am probably not experienced enough on the coding side to figure it out, but if you could post the command you use to call the ImageJ script (I assume you are using the Macro runner?) and the last couple of lines of the ImageJ macro where you return the information from ImageJ to QuPath, Pete might be able to tell what is going on.

@petebankhead
Copy link
Member

If I read it correctly, a macro isn't involved but Python is. Could you give a bit more details on the arrangement and how you get the ROIs from the ROI Manager into QuPath?

If you aren't doing it already, I think you might need to get into some of the helper classes like IJTools, RoiConverterIJ and ImagePlusServerBuilder to get enough control to do this smoothly.

@Svidro
Copy link

Svidro commented Jan 13, 2018

Oops, yes, he did say that in the first post. I think my wires got crossed while reading the forum post on automating the ImageJ macro runner across images. It is interesting that his import (and more importantly export) works for a single slide. I am still wondering if it not working in batch mode means that something isn't being given time to complete.

@erexhepa When running it for a single slide, once all of the imported ROIs are in place, can you then use getDetectionObjects (on it's own as a separate script or menu command) to target anything? Does the Measure->Show detection measurements populate as soon as the script is complete?

@erexhepa
Copy link
Author

This is the code handling the interaction with ImageJ. Python code is called from ImageJ to handle some normalisation and convolutional tasks. I'm looking into the IJTools, RoiConverterIJ classes to see if I can find something useful.

It is very strange because I can see the objects in QuPath and they are in the hierarchy. The gethierarchy method from QP class returns you the numbers of IJ objects plus the number of tiles but somehow during data extraction things get messed up.

Meanwhile I find what is going on I will try to do the intensity/morphology measurements in ImageJ and save the results from IJ through calls to the RoiManager.

for (annotation in getAnnotationObjects()) {

    def pathClass = annotation.getPathClass()
   
    def roi = annotation.getROI()

    tw = (int) roi.getBoundsWidth()
    th = (int) roi.getBoundsHeight()

    if( (tw>400) && (tw>400) && (roi.getScaledArea(pixelWidth, pixelHeight)>9500) && (counter<10)){
    //if( (tw>400) && (tw>400) && (roi.getScaledArea(pixelWidth, pixelHeight)>9500)){
        //print result
        RegionRequest request = RegionRequest.createInstance(path, 4, (int) roi.getBoundsX(), (int) roi.getBoundsY(),(int) roi.getBoundsWidth(), (int) roi.getBoundsHeight(), 0, 0)
        
    
                    // Read the image region
        ImagePlus imp = serverIJ.readImagePlusRegion(request).getImage(true)
        IJ.run(imp, "8-bit", "");
        IJ.run(imp, "Median...", "radius=3");
        //IJ.run(imp, "Median...", "radius=5");
        IJ.run(imp, "Statistical Region Merging", "q=10 showaverages");
        IJ.run(imp, "Invert", "");
        // python code for normalisation and structure convolution
        IJ.run(imp, "Make Binary", "");
        IJ.run(imp, "Set Measurements...", "area mean standard modal min centroid center bounding fit shape feret's integrated median skewness kurtosis add redirect=None decimal=3");
        IJ.run(imp, "Make Binary", "");
        IJ.run(imp, "Erode", "");
        IJ.run(imp, "Erode", "");
        //getHistogram(values, counts, 256)
        IJ.run(imp, "Analyze Particles...", "size=20-Infinity circularity=0.40-1.00 display clear summarize add in_situ");
        
        rm = RoiManager;
        rm = RoiManager.getInstance();       
        
        if((rm==null) || (rm.getCount()<1)){
            print("No objects found")
        }else{
            //print rm.getCount()
            //RoiManager.roiManager("count")
            //rm.runCommand(imp,"Measure");
            //rm.runCommand(imp,"Update");
            IJ.run(imp, "Send Overlay to QuPath", "choose_object_type=Detection include_measurements");

                    // Get a suitable file name
                    //String name = String.format("%s (d=%.2f, x=%d, y=%d, w=%d, h=%d, z=%d).%s", serverName, downsample, xi, yi, wi, hi, z, ext)
                    // Create an output file
                    //File file = new File(dirOutput, name)
                    // Save the image
                    //IJ.save(imp, file.getAbsolutePath())
                    // Print progress
            //imp.show()  
            rm.reset() 
                
        }
        
        //counter++
        imp2 = IJ.getImage();
        imp2.close();
    }
            
}

@petebankhead
Copy link
Member

Aaaah.... that makes sense. The Send Overlay to QuPath command doesn't support batch mode, and requests the image currently open in the viewer. Code is at https://github.com/qupath/qupath/blob/v0.1.2/qupath-extension-ij/src/main/java/qupathj/QUPath_Send_Overlay_to_QuPath.java#L90

So the results are presumably being sent to the wrong image when called in batch mode.

Because the createPathObjectsFromROIs method is public static it is hopefully enough to let you do the conversion in your script without relying on the plugin. You should be able to use serverIJ and add to the hierarchy returned by getCurrentHierarchy().

@erexhepa
Copy link
Author

👍 thanks. Should have put the code straight from the first post :).

@erexhepa
Copy link
Author

erexhepa commented Jan 15, 2018

thanks again @petebankhead. That was the issue. I'm posting the code in case somebody else is interested.

if( (tw>400) && (tw>400) && (roi.getScaledArea(pixelWidth, pixelHeight)>9500) && (counter<10)){
      RegionRequest request = RegionRequest.createInstance(path, 4, (int) roi.getBoundsX(), (int) roi.getBoundsY(),(int) roi.getBoundsWidth(), (int) roi.getBoundsHeight(), 0, 0)
      
  
                  // Read the image region
      ImagePlus imp = serverIJ.readImagePlusRegion(request).getImage(true)
      IJ.run(imp, "8-bit", "");
      IJ.run(imp, "Median...", "radius=3");

      IJ.run(imp, "Statistical Region Merging", "q=10 showaverages");
      IJ.run(imp, "Invert", "");
      IJ.run(imp, "Make Binary", "");
      IJ.run(imp, "Set Measurements...", "area mean standard modal min centroid center bounding fit shape feret's integrated median skewness kurtosis add redirect=None decimal=3");
      IJ.run(imp, "Make Binary", "");
      IJ.run(imp, "Erode", "");
      IJ.run(imp, "Erode", "");

     // code for normalisation and preprocessing prior to segmentation

      IJ.run(imp, "Analyze Particles...", "size=20-Infinity circularity=0.40-1.00 display clear summarize add in_situ");

      RoiManager manager = RoiManager.getInstance();
      if (manager == null)
          manager = new RoiManager()

      if((manager==null) || (manager.getCount()<1)){
          print("No object detected")
      }else{
             
          // call IJ roi to qupath roi conversion
          def ijROIs = QUPath_Send_Overlay_to_QuPath.createPathObjectsFromROIs(imp,
                  manager.getRoisAsArray(),
                  serverOriginal,
                  (double) 4,
                  true,true,0,0,0)

          for (annotationIJ in ijROIs) {
              def roiIter = annotationIJ.getROI()
              def pathObject3 = new PathDetectionObject(roiIter)
              addObject(pathObject3)
          }

          manager.reset()
              
      }
      
      //print QP.detectionObjects.lastIndexOf()
      
      counter++
      imp2 = IJ.getImage();
      imp2.close();   
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants