Last active
November 30, 2020 13:44
-
-
Save NicoKiaru/a2d19870e5e74140b0c1c9576a8b89da to your computer and use it in GitHub Desktop.
[Display ROIs in BigDataViewer] Display ROIs of the RoiManager linked to an ImagePlus using BigDataViewer ( ImageJ / FIJI ) #Fiji #ROI #BigDataViewer #BIOP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Allows to visualize the rois of a RoiManager linked to an ImagePlus | |
* into bigdataviewer. | |
* | |
* REQUIREMENT : install the bigdataviewer-playground update site ( still in development! expect things to break!! ) | |
* Update site : https://biop.epfl.ch/Fiji-Bdv-Playground/ | |
* Source code : https://github.com/bigdataviewer/bigdataviewer-playground | |
* | |
* | |
* A lazy image is generated which display a small square region around each ROI | |
* B&C + color settings are taken from the ImagePlus | |
* They can be modified in BigDataViewer by displaying the side panel (look for a blue arrow at the right side of Bdv) | |
* | |
* Some measurements are performed for each roi and a table is generated which can be sorted accoring to each of this measurement | |
* | |
* Look at the groovy code in order to add the measurements you're interested in. | |
* - see for instance RoiInfo.measureFunctions['Name'] = { roiinfo -> roiinfo.roi.getName() } | |
* | |
* The order of the measurements displayed in the table is set by the measurementOrder variable (array of String) | |
* | |
* Unsupported (TODO): | |
* - Time series | |
* - 32-bit float images | |
* - RGB images | |
* | |
* Limitiation : it would be nice to put the table within bigdataviewer side panel but there's a NullPointerException | |
* with bdvh.getCarPanel() which will be solved in the next FIJI releases | |
* | |
* @author : [email protected], BIOP, EPFL, 2020 | |
*/ | |
//---- Inputs | |
#@int(label = "Number of pixels surrounding each ROI (even number)") pixPerRoi | |
// imp is the current active image | |
#@ImagePlus imp | |
// creates or get an existing bdv window | |
#@bdv.util.BdvHandle(required = false) bdvh | |
//------------------------------------ MAIN | |
// ----- State : list of roi in the order set by the user (changes on table click) | |
List<RoiInfo> currentList; | |
//---- Variable initialisation | |
if (pixPerRoi%2 == 1) pixPerRoi+=1; | |
// Reset bdv sources | |
def sources = bdvh.getViewerPanel().getState().getSources() | |
sources.each{src -> bdvh.getViewerPanel().removeSource(src.getSpimSource()) } | |
// Initializes the list of roiinfo, one roiinfo object per roi contained in the roi manager | |
// - listed in the natural order = the order given by the RoiManager | |
final ArrayList<RoiInfo> roiNaturalOrder = new ArrayList<>() | |
// Fetch roi manager | |
roiManager = RoiManager.getRoiManager(); | |
nRois = roiManager.getCount() | |
// Initializes roiNaturalOrder with roi of the RoiManager | |
roiManager.getRoisAsArray().eachWithIndex { roi, idx -> | |
roiInfo = new RoiInfo() | |
roiInfo.roi = roi | |
roiInfo.idx = idx | |
roiInfo.px = roi.getContourCentroid()[0] | |
roiInfo.py = roi.getContourCentroid()[1] | |
roiInfo.pz = roi.getZPosition(); | |
roiNaturalOrder.add(roiInfo) | |
} | |
// Split channels of ImagePlus (more convenient) splitted channels are never shown | |
impChannels = ChannelSplitter.split(imp) | |
// Dictionary of ImagePlus (one per channel) | |
fluoImagesPlus = [:] | |
// Dictionary of Img (image class compatible for bigdataviewer) | |
fluoImages = [:] | |
// initiliazes previous varaible | |
for (int channel = 0; channel<impChannels.length; channel++) { | |
fluoImagesPlus[channel] = impChannels[channel] | |
fluoImages[channel] = getImage(fluoImagesPlus[channel]) | |
} | |
// ------------ Measurements ------------------- | |
measurementOrder = [:] | |
idxM = -1 | |
RoiInfo.measureFunctions['Index'] = { roiinfo -> roiinfo.idx } | |
measurementOrder[++idxM] = 'Index' | |
RoiInfo.measureFunctions['Name'] = { roiinfo -> roiinfo.roi.getName() } | |
measurementOrder[++idxM] = 'Name' | |
RoiInfo.measureFunctions['Area'] = { roiinfo -> roiinfo.roi.getStatistics().area} | |
measurementOrder[++idxM] = 'Area' | |
RoiInfo.measureFunctions['ZPos'] = { roiinfo -> roiinfo.pz} | |
measurementOrder[++idxM] = 'ZPos' | |
fluoImages.each { chName, img -> | |
RoiInfo.measureFunctions['Mean '+chName] = { roiinfo -> | |
roiinfo.roi.setPosition((int)roiinfo.pz) | |
roiinfo.roi.setPosition(1,(int)roiinfo.pz,1) | |
roiinfo.roi.setImage(fluoImagesPlus[chName]) | |
fluoImagesPlus[chName].setPosition((int)roiinfo.pz) | |
roiinfo.roi.getStatistics().mean | |
} | |
measurementOrder[++idxM] = 'Mean '+chName | |
RoiInfo.measureFunctions['Max '+chName] = { roiinfo -> | |
roiinfo.roi.setPosition((int)roiinfo.pz) | |
roiinfo.roi.setPosition(1,(int)roiinfo.pz,1) | |
roiinfo.roi.setImage(fluoImagesPlus[chName]) | |
fluoImagesPlus[chName].setPosition((int)roiinfo.pz) | |
roiinfo.roi.getStatistics().max | |
} | |
measurementOrder[++idxM] = 'Max '+chName | |
RoiInfo.measureFunctions['Min '+chName] = { roiinfo -> | |
roiinfo.roi.setPosition((int)roiinfo.pz) | |
roiinfo.roi.setPosition(1,(int)roiinfo.pz,1) | |
roiinfo.roi.setImage(fluoImagesPlus[chName]) | |
fluoImagesPlus[chName].setPosition((int)roiinfo.pz) | |
roiinfo.roi.getStatistics().min | |
} | |
measurementOrder[++idxM] = 'Min '+chName | |
RoiInfo.measureFunctions['stdDev '+chName] = { roiinfo -> | |
roiinfo.roi.setPosition((int)roiinfo.pz) | |
roiinfo.roi.setPosition(1,(int)roiinfo.pz,1) | |
roiinfo.roi.setImage(fluoImagesPlus[chName]) | |
fluoImagesPlus[chName].setPosition((int)roiinfo.pz) | |
roiinfo.roi.getStatistics().stdDev | |
} | |
measurementOrder[++idxM] = 'stdDev '+chName | |
} | |
// Actually measure roi properties | |
roiNaturalOrder.each{ it.measure() } | |
// ------------ BigDataViewer window : Display Rois according to their natural order -------------------- | |
displayRois(roiNaturalOrder, bdvh) | |
// ------------ Table window : Display Rois measurement according to their natural order -------------------- | |
displayRoiMeasures(roiNaturalOrder, roiNaturalOrder, bdvh) | |
//------------------------------------ END OF MAIN | |
// --------------- METHODS | |
/** | |
* Gets an extended Img (ImgLib2 structure) from a ImagePlus | |
*/ | |
RandomAccessibleInterval getImage(ImagePlus imp) { | |
def img = ImageJFunctions.wrap(imp) | |
return Views.expandZero(img,[pixPerRoi,pixPerRoi,0] as long[]); | |
} | |
void setNewList(List<RoiInfo> newList) { | |
currentList = newList; | |
} | |
void displayRoiMeasures(List<RoiInfo> list, List<RoiInfo> completeOrderedlist, BdvHandle bdvh) { //List<BacteriaInfo> completeOrderedlist, | |
str = "" | |
if (list.size()>0) { | |
// JTable | |
columnNames = new String[measurementOrder.keySet().size()] | |
for (int i = 0;i<measurementOrder.keySet().size(); i++) { | |
columnNames[i] = measurementOrder.get(i) | |
} | |
//columnNames = list.get(0).values.keySet() as String[] | |
indexColumnIndex = -1 | |
columnNames.eachWithIndex{ name, idx -> | |
if (name.equals("Index")) indexColumnIndex = idx | |
} | |
Object[][] data = new Object[list.size()][RoiInfo.measureFunctions.keySet().size()] | |
formatter = new DecimalFormat("#.##") | |
list.eachWithIndex{ roi, idxRoi -> | |
columnNames.eachWithIndex { p, idxP -> | |
data[idxRoi][idxP] = list.get(idxRoi).values[columnNames[idxP]] | |
} | |
} | |
TableModel model = new DefaultTableModel(data, columnNames) { | |
public Class getColumnClass(int column) { | |
Class returnValue; | |
if ((column >= 0) && (column < getColumnCount())) { | |
returnValue = getValueAt(0, column).getClass(); | |
} else { | |
returnValue = Object.class; | |
} | |
return returnValue; | |
} | |
}; | |
JTable table = new JTable(model); | |
RowSorter<TableModel> sorter = new TableRowSorter<TableModel>(model); | |
table.setRowSorter(sorter); | |
table.getRowSorter().addRowSorterListener({e -> | |
if (e.getType()==RowSorterEvent.Type.SORTED) { | |
if (bdvh!=null) { | |
// Get | |
List<RoiInfo> newOrderedList = new ArrayList<>() | |
for (int i = 0; i<list.size() ; i++) { | |
currentIndex = sorter.convertRowIndexToModel(i) | |
newOrderedList.add(completeOrderedlist.get(currentIndex)) | |
} | |
SwingUtilities.invokeLater({ | |
sources = bdvh.getViewerPanel().getState().getSources() | |
sources.each{src -> bdvh.getViewerPanel().removeSource(src.getSpimSource()) } | |
displayRois(newOrderedList, bdvh) | |
}); | |
} | |
} | |
}); | |
JScrollPane scrollPane = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS) | |
table.setFillsViewportHeight(true) | |
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF) | |
table.getSelectionModel().addListSelectionListener( {event -> | |
if (bdvh!=null) { | |
AffineTransform3D view = new AffineTransform3D() | |
bdvh.getViewerPanel().getState().getViewerTransform(view) | |
scaleX = Affine3DHelpers.extractScale(view,0) | |
at3D = new AffineTransform3D() | |
at3D.translate(0,-table.getSelectedRow()*pixPerRoi,0) | |
at3D.scale(scaleX) | |
bdvh.getViewerPanel().setCurrentViewerTransform(at3D) | |
idxSelected = table.getSelectedRow() | |
if (idxSelected>=0) { | |
imp.setRoi(currentList.get(idxSelected).roi) | |
println("pz = "+currentList.get(idxSelected).pz) | |
println("int pz = "+(int)(currentList.get(idxSelected).pz)) | |
roiManager.select(currentList.get(idxSelected).idx) | |
imp.setPosition(1,(int)(currentList.get(idxSelected).pz),1) | |
} | |
} | |
}); | |
// Returns null exception, wait for next Fiji release | |
/*bdvh.getCardPanel().addCard("Roi Explorer", | |
//ScijavaSwingUI.getPanel(scijavaCtx, SlicerAdjusterCommand.class, "reslicedAtlas", reslicedAtlas), | |
scrollPane, | |
true);*/ | |
f=new JFrame("Roi Data "); | |
f.add(scrollPane); | |
f.setSize(600,800); | |
f.setVisible(true); | |
} | |
} | |
//-------------------------------- BDV Display | |
BdvHandle displayRois(List<RoiInfo> list, BdvHandle bdvh) { | |
setNewList(list) | |
def transform = new AffineTransform3D() | |
transform.translate(pixPerRoi,0,-pixPerRoi / 2.0) | |
fluoImages.each { chName, img -> | |
def imgReordered = getReorderedImage(img, list) | |
def bssf = BdvFunctions.show(VolatileViews.wrapAsVolatile(imgReordered), imp.getTitle()+"_ch_"+chName, BdvOptions.options().addTo(bdvh).sourceTransform(transform)) | |
int color = fluoImagesPlus[chName].getStack( ).getProcessor( 1 ).getColorModel( ).getRGB( 255 ); | |
bssf.setColor(new ARGBType(color)) | |
bssf.setDisplayRange(fluoImagesPlus[chName].getDisplayRangeMin(),fluoImagesPlus[chName].getDisplayRangeMax()) | |
transform.translate(pixPerRoi,0,0) | |
} | |
iniView = new AffineTransform3D() | |
iniView.scale(3) | |
bdvh.getViewerPanel().setCurrentViewerTransform(iniView) | |
return bdvh | |
} | |
//------------------------------- Technical Bdv View of ROIS | |
Img getReorderedImage(RandomAccessibleInterval rai_in, ArrayList<RoiInfo> rois) { | |
if (Util.getTypeFromInterval(rai_in).getClass().equals(UnsignedShortType.class)) { | |
return getReorderedImageShort(rai_in, rois) | |
} else { | |
return getReorderedImageByte(rai_in, rois) | |
} | |
} | |
Img getReorderedImageShort(RandomAccessibleInterval rai_in, ArrayList<RoiInfo> roisInfo) { | |
def cellDimensions = [ pixPerRoi, pixPerRoi, pixPerRoi ] as int[] | |
final DiskCachedCellImgOptions factoryOptions = options() | |
.cellDimensions( cellDimensions ) | |
//.cacheType( DiskCachedCellImgOptions.CacheType.BOUNDED ) | |
.maxCacheSize( 10 ) | |
// Creates cached image factory of Type UnsignedShort | |
final DiskCachedCellImgFactory<UnsignedShortType> factory = new DiskCachedCellImgFactory<>( new UnsignedShortType(), factoryOptions ); | |
final Img<UnsignedShortType> img = factory.create(new FinalInterval([pixPerRoi, pixPerRoi*nRois, pixPerRoi] as long[]), { cell -> | |
int cellPosX = cell.min( 0 ) / pixPerRoi | |
int cellPosY = cell.min( 1 ) / pixPerRoi | |
int idxRoi = cellPosY * 1 + cellPosX | |
if (idxRoi<roisInfo.size()) { | |
int px = roisInfo.get(idxRoi).px | |
int py = roisInfo.get(idxRoi).py | |
int pz = roisInfo.get(idxRoi).pz | |
def viewOnRoi = Views.interval( rai_in, [ px - pixPerRoi / 2 , py - pixPerRoi / 2 , pz-1 - pixPerRoi / 2 ] as long[], | |
[ px + pixPerRoi / 2-1, py + pixPerRoi / 2-1, pz-1 + pixPerRoi / 2 ] as long [] ); | |
def cursorOnRoi = Views.flatIterable( viewOnRoi ).cursor(); | |
def Cursor<UnsignedShortType> out = Views.flatIterable(cell).cursor() | |
while (out.hasNext()) { | |
out.next().set(cursorOnRoi.next()) | |
} | |
} | |
}, options().initializeCellsAsDirty(true)); | |
return img; | |
} | |
Img getReorderedImageByte(RandomAccessibleInterval rai_in, ArrayList<RoiInfo> roisInfo) { | |
def cellDimensions = [ pixPerRoi, pixPerRoi, pixPerRoi ] as int[] | |
final DiskCachedCellImgOptions factoryOptions = options() | |
.cellDimensions( cellDimensions ) | |
//.cacheType( DiskCachedCellImgOptions.CacheType.BOUNDED ) | |
.maxCacheSize( 10 ) | |
// Creates cached image factory of Type UnsignedShort | |
final DiskCachedCellImgFactory<UnsignedByteType> factory = new DiskCachedCellImgFactory<>( new UnsignedByteType(), factoryOptions ); | |
final Img<UnsignedByteType> img = factory.create(new FinalInterval([pixPerRoi, pixPerRoi*nRois, pixPerRoi] as long[]), { cell -> | |
int cellPosX = cell.min( 0 ) / pixPerRoi | |
int cellPosY = cell.min( 1 ) / pixPerRoi | |
int idxRoi = cellPosY * 1 + cellPosX | |
if (idxRoi<roisInfo.size()) { | |
int px = roisInfo.get(idxRoi).px | |
int py = roisInfo.get(idxRoi).py | |
int pz = roisInfo.get(idxRoi).pz | |
def viewOnRoi = Views.interval( rai_in, [ px - pixPerRoi / 2 , py - pixPerRoi / 2, pz-1 - pixPerRoi / 2 ] as long[], | |
[ px + pixPerRoi / 2-1, py + pixPerRoi / 2-1, pz-1 + pixPerRoi / 2] as long [] ); | |
def cursorOnRoi = Views.flatIterable( viewOnRoi ).cursor(); | |
def Cursor<UnsignedByteType> out = Views.flatIterable(cell).cursor() | |
while (out.hasNext()) { | |
out.next().set(cursorOnRoi.next()) | |
} | |
} | |
}, options().initializeCellsAsDirty(true)); | |
return img; | |
} | |
// ------------------------- Roi Info Class | |
class RoiInfo { | |
int idx | |
Roi roi | |
double px | |
double py | |
double pz | |
String originalFile | |
Map<String, Object> values = new HashMap<>() | |
static Map<String, Function<RoiInfo, Object>> measureFunctions = new HashMap<>() | |
public void measure() { | |
RoiInfo.measureFunctions.each{ name, f -> | |
values.put(name, f(this)) | |
} | |
} | |
} | |
import ij.IJ | |
import ij.ImagePlus | |
import ij.gui.Roi | |
import ij.plugin.frame.RoiManager | |
import ij.plugin.ChannelSplitter | |
import java.io.File | |
import java.nio.file.Files | |
import java.nio.file.Path | |
import java.util.stream.Stream | |
import java.nio.file.Paths | |
import java.util.stream.Collectors | |
import java.util.function.Function | |
import java.util.ArrayList | |
import java.text.DecimalFormat | |
import java.awt.BorderLayout | |
import static net.imglib2.cache.img.DiskCachedCellImgOptions.options | |
import net.imglib2.cache.img.DiskCachedCellImgFactory | |
import net.imglib2.cache.img.DiskCachedCellImgOptions | |
import net.imglib2.img.Img | |
import net.imglib2.type.numeric.integer.UnsignedByteType | |
import net.imglib2.type.numeric.integer.UnsignedShortType | |
import net.imglib2.type.numeric.ARGBType | |
import net.imglib2.realtransform.AffineTransform3D | |
import net.imglib2.view.Views | |
import net.imglib2.Cursor | |
import net.imglib2.FinalInterval | |
import net.imglib2.img.display.imagej.ImageJFunctions | |
import net.imglib2.* | |
import net.imglib2.img.Img | |
import net.imglib2.util.Intervals | |
import net.imglib2.util.Util | |
import net.imglib2.view.Views | |
import net.imglib2.realtransform.AffineTransform3D | |
import net.imglib2.util.Util | |
import bdv.util.volatiles.VolatileViews | |
import bdv.util.BdvFunctions | |
import bdv.util.BdvOptions | |
import bdv.util.BdvHandle | |
import bdv.util.BdvSource | |
import bdv.util.volatiles.VolatileViews | |
import bdv.util.Affine3DHelpers | |
import javax.swing.JTable | |
import javax.swing.JScrollPane | |
import javax.swing.* | |
import javax.swing.ListSelectionModel | |
import javax.swing.JFrame; | |
import javax.swing.JScrollPane; | |
import javax.swing.JTable; | |
import javax.swing.RowSorter; | |
import javax.swing.table.DefaultTableModel; | |
import javax.swing.table.TableModel; | |
import javax.swing.table.TableRowSorter; | |
import javax.swing.SwingUtilities | |
import javax.swing.event.RowSorterEvent |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment