Mercurial > repos > perssond > basic_illumination
comparison imagej_basic_ashlar_filepattern.py @ 0:cad3339b566b draft default tip
"planemo upload for repository https://github.com/ohsu-comp-bio/basic-illumination commit d06e0682d1847fae0d5a464d7aa9e47e40d31fe7-dirty"
| author | perssond |
|---|---|
| date | Tue, 08 Dec 2020 20:57:09 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:cad3339b566b |
|---|---|
| 1 # @String(label="Enter a filename pattern describing the TIFFs to process") pattern | |
| 2 # @File(label="Select the output location", style="directory") output_dir | |
| 3 # @String(label="Experiment name (base name for output files)") experiment_name | |
| 4 # @Float(label="Flat field smoothing parameter (0 for automatic)", value=0.1) lambda_flat | |
| 5 # @Float(label="Dark field smoothing parameter (0 for automatic)", value=0.01) lambda_dark | |
| 6 | |
| 7 import sys | |
| 8 import os | |
| 9 import re | |
| 10 import collections | |
| 11 from ij import IJ, WindowManager, ImagePlus, ImageStack | |
| 12 from ij.io import Opener | |
| 13 from ij.macro import Interpreter | |
| 14 import BaSiC_ as Basic | |
| 15 | |
| 16 | |
| 17 def enumerate_filenames(pattern): | |
| 18 """Return filenames matching pattern (a str.format pattern containing | |
| 19 {channel} and {tile} placeholders). | |
| 20 | |
| 21 Returns a list of lists, where the top level is indexed by channel number | |
| 22 and the bottom level is sorted filenames for that channel. | |
| 23 | |
| 24 """ | |
| 25 (base, pattern) = os.path.split(pattern) | |
| 26 regex = re.sub(r'{([^:}]+)(?:[^}]*)}', r'(?P<\1>.*?)', | |
| 27 pattern.replace('.', '\.')) | |
| 28 tiles = set() | |
| 29 channels = set() | |
| 30 num_images = 0 | |
| 31 # Dict[channel: int, List[filename: str]] | |
| 32 filenames = collections.defaultdict(list) | |
| 33 for f in os.listdir(base): | |
| 34 match = re.match(regex, f) | |
| 35 if match: | |
| 36 gd = match.groupdict() | |
| 37 tile = int(gd['tile']) | |
| 38 channel = int(gd['channel']) | |
| 39 tiles.add(tile) | |
| 40 channels.add(channel) | |
| 41 filenames[channel].append(os.path.join(base, f)) | |
| 42 num_images += 1 | |
| 43 if len(tiles) * len(channels) != num_images: | |
| 44 raise Exception("Missing some image files") | |
| 45 filenames = [ | |
| 46 sorted(filenames[channel]) | |
| 47 for channel in sorted(filenames.keys()) | |
| 48 ] | |
| 49 return filenames | |
| 50 | |
| 51 | |
| 52 def main(): | |
| 53 | |
| 54 Interpreter.batchMode = True | |
| 55 | |
| 56 if (lambda_flat == 0) ^ (lambda_dark == 0): | |
| 57 print ("ERROR: Both of lambda_flat and lambda_dark must be zero," | |
| 58 " or both non-zero.") | |
| 59 return | |
| 60 lambda_estimate = "Automatic" if lambda_flat == 0 else "Manual" | |
| 61 | |
| 62 #import pdb; pdb.set_trace() | |
| 63 print "Loading images..." | |
| 64 filenames = enumerate_filenames(pattern) | |
| 65 num_channels = len(filenames) | |
| 66 num_images = len(filenames[0]) | |
| 67 image = Opener().openImage(filenames[0][0]) | |
| 68 width = image.width | |
| 69 height = image.height | |
| 70 image.close() | |
| 71 | |
| 72 # The internal initialization of the BaSiC code fails when we invoke it via | |
| 73 # scripting, unless we explicitly set a the private 'noOfSlices' field. | |
| 74 # Since it's private, we need to use Java reflection to access it. | |
| 75 Basic_noOfSlices = Basic.getDeclaredField('noOfSlices') | |
| 76 Basic_noOfSlices.setAccessible(True) | |
| 77 basic = Basic() | |
| 78 Basic_noOfSlices.setInt(basic, num_images) | |
| 79 | |
| 80 # Pre-allocate the output profile images, since we have all the dimensions. | |
| 81 ff_image = IJ.createImage("Flat-field", width, height, num_channels, 32); | |
| 82 df_image = IJ.createImage("Dark-field", width, height, num_channels, 32); | |
| 83 | |
| 84 print("\n\n") | |
| 85 | |
| 86 # BaSiC works on one channel at a time, so we only read the images from one | |
| 87 # channel at a time to limit memory usage. | |
| 88 for channel in range(num_channels): | |
| 89 print "Processing channel %d/%d..." % (channel + 1, num_channels) | |
| 90 print "===========================" | |
| 91 | |
| 92 stack = ImageStack(width, height, num_images) | |
| 93 opener = Opener() | |
| 94 for i, filename in enumerate(filenames[channel]): | |
| 95 print "Loading image %d/%d" % (i + 1, num_images) | |
| 96 image = opener.openImage(filename) | |
| 97 stack.setProcessor(image.getProcessor(), i + 1) | |
| 98 input_image = ImagePlus("input", stack) | |
| 99 | |
| 100 # BaSiC seems to require the input image is actually the ImageJ | |
| 101 # "current" image, otherwise it prints an error and aborts. | |
| 102 WindowManager.setTempCurrentImage(input_image) | |
| 103 basic.exec( | |
| 104 input_image, None, None, | |
| 105 "Estimate shading profiles", "Estimate both flat-field and dark-field", | |
| 106 lambda_estimate, lambda_flat, lambda_dark, | |
| 107 "Ignore", "Compute shading only" | |
| 108 ) | |
| 109 input_image.close() | |
| 110 | |
| 111 # Copy the pixels from the BaSiC-generated profile images to the | |
| 112 # corresponding channel of our output images. | |
| 113 ff_channel = WindowManager.getImage("Flat-field:%s" % input_image.title) | |
| 114 ff_image.slice = channel + 1 | |
| 115 ff_image.getProcessor().insert(ff_channel.getProcessor(), 0, 0) | |
| 116 ff_channel.close() | |
| 117 df_channel = WindowManager.getImage("Dark-field:%s" % input_image.title) | |
| 118 df_image.slice = channel + 1 | |
| 119 df_image.getProcessor().insert(df_channel.getProcessor(), 0, 0) | |
| 120 df_channel.close() | |
| 121 | |
| 122 print("\n\n") | |
| 123 | |
| 124 template = '%s/%s-%%s.tif' % (output_dir, experiment_name) | |
| 125 ff_filename = template % 'ffp' | |
| 126 IJ.saveAsTiff(ff_image, ff_filename) | |
| 127 ff_image.close() | |
| 128 df_filename = template % 'dfp' | |
| 129 IJ.saveAsTiff(df_image, df_filename) | |
| 130 df_image.close() | |
| 131 | |
| 132 print "Done!" | |
| 133 | |
| 134 | |
| 135 main() |
