Skip to content

Instantly share code, notes, and snippets.

@NicoKiaru
Last active November 30, 2020 13:44
Show Gist options
  • Save NicoKiaru/a2d19870e5e74140b0c1c9576a8b89da to your computer and use it in GitHub Desktop.
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
/**
* 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