Skip to content

Instantly share code, notes, and snippets.

@lacan
Last active November 11, 2023 13:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lacan/6f70f60e98fa3e44fa23f1d85c303c82 to your computer and use it in GitHub Desktop.
Save lacan/6f70f60e98fa3e44fa23f1d85c303c82 to your computer and use it in GitHub Desktop.
[Parallel Histrogram-Based Normalization] This performs Histogram Based Normalization on all slices of an open stack based on the selected reference slice #fiji #imageJ #histogram #BIOP
#@ ImagePlus image
#@ Integer ( label = "Channel to correct (in case of multichannel images)", value = 1 ) ch_correct
#@ Integer ( label = "Reference Slice", value = 1 ) ref_slice
#@ Boolean ( label = "Use Manual Normalization Parameters Below", value = false ) is_normalize
#@ Integer ( Label = "Minimum" ) the_min
#@ Integer ( Label = "Maximum" ) the_max
// This variable will help us at the end to add the normalized channel back
def all_images = []
// We process only one channel
if( image.getNChannels() > 1 ) {
all_images = ChannelSplitter.split( image )
image = all_images[ch_correct-1]
}
// Perform Normalization before starting, if requested
// We only normalize the reference slice of the stack. This is also the reference histogram to match
println( "Using Slice " + ref_slice + " as reference" )
def ref_ip = image.getStack().getProcessor(ref_slice).convertToFloat().duplicate()
if ( is_normalize ) {
def stats = ref_ip.getStatistics()
ref_ip.subtract(stats.min)
ref_ip.multiply(1/(stats.max-stats.min))
ref_ip.multiply(the_max-the_min)
ref_ip.add(the_min)
// Convert to shorts as, we cannot match histograms for float images
ref_ip = ref_ip.convertToShort(false)
}
// Pick up the reference histogram
ref_hist = ref_ip.getHistogram()
// Do the histogram matching
def matchedImage= matchSliceHistograms(ref_hist, image)
def newImage = matchedImage
// If there are multtiple channels, re-merge them into a single image with the new corrected channel
if(image.getNChannels() > 1) {
all_images[ch_correct-1] = matchedImage
newImage = RGBStackMerge.mergeChannels(all_images, false)
newImage.setDisplayMode(image.getDisplayMode())
newImage.setLuts(image.getLuts())
}
// This function performs the histotram matching in parallel
def matchSliceHistograms( def refhist, def imp ) {
// Get the stack
def stack = imp.getStack()
// Produce the final stack which will hold the normalized image
def final_imp = IJ.createHyperStack( "Normalized-" + imp.getTitle(), imp.getWidth(), imp.getHeight(), imp.getNChannels(), imp.getNSlices(), imp.getNFrames(), imp.getBitDepth() )
// Create the Histogram Matcher
def matcher = new HistogramMatcher()
// Get all cores minus one for the process
def cores = Runtime.getRuntime().availableProcessors() > 1 ? Runtime.getRuntime().availableProcessors() - 1 : 1
GParsExecutorsPool.withPool( cores ) {
(1..stack.getSize()).eachParallel{ slice ->
println( "Processing Slice "+slice )
def hist_old = stack.getProcessor( slice ).getHistogram()
// Finally match the histograms with this great little class
def newHist = matcher.matchHistograms( hist_old, refhist )
// We need to duplicate it, otherwise it will not work. there must a reference issue
def ip = stack.getProcessor( slice ).duplicate()
// This applies the matched to the current processor
ip.applyTable( newHist )
stack.setProcessor( ip, slice )
}
}
final_imp.setStack(stack)
final_imp.show()
}
// Imports
import histogram2.HistogramMatcher
import ij.plugin.ChannelSplitter
import ij.plugin.RGBStackMerge
import ij.IJ
import groovyx.gpars.GParsExecutorsPool
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment