|
| 1 | +from ctk_cli import CLIArgumentParser |
| 2 | +import histomicstk as htk |
| 3 | +import numpy as np |
| 4 | +import json |
| 5 | +import scipy as sp |
| 6 | +import skimage.io |
| 7 | +import skimage.measure |
| 8 | + |
| 9 | +import logging |
| 10 | +logging.basicConfig() |
| 11 | + |
| 12 | +stainColorMap = { |
| 13 | + 'hematoxylin': [0.65, 0.70, 0.29], |
| 14 | + 'eosin': [0.07, 0.99, 0.11], |
| 15 | + 'dab': [0.27, 0.57, 0.78], |
| 16 | + 'null': [0.0, 0.0, 0.0] |
| 17 | +} |
| 18 | + |
| 19 | + |
| 20 | +def main(args): |
| 21 | + |
| 22 | + # |
| 23 | + # Read Input Image |
| 24 | + # |
| 25 | + print('>> Reading input image') |
| 26 | + |
| 27 | + imInput = skimage.io.imread(args.inputImageFile)[:, :, :3] |
| 28 | + |
| 29 | + # |
| 30 | + # Perform color normalization |
| 31 | + # |
| 32 | + print('>> Performing color normalization') |
| 33 | + |
| 34 | + # transform input image to LAB color space |
| 35 | + imInputLAB = htk.RudermanLABFwd(imInput) |
| 36 | + |
| 37 | + # compute mean and stddev of input in LAB color space |
| 38 | + Mu = np.zeros(3) |
| 39 | + Sigma = np.zeros(3) |
| 40 | + |
| 41 | + for i in range(3): |
| 42 | + Mu[i] = imInputLAB[:, :, i].mean() |
| 43 | + Sigma[i] = (imInputLAB[:, :, i] - Mu[i]).std() |
| 44 | + |
| 45 | + # perform reinhard normalization |
| 46 | + imNmzd = htk.ReinhardNorm(imInput, Mu, Sigma) |
| 47 | + |
| 48 | + # |
| 49 | + # Perform color deconvolution |
| 50 | + # |
| 51 | + print('>> Performing color deconvolution') |
| 52 | + |
| 53 | + stainColor_1 = stainColorMap[args.stain_1] |
| 54 | + stainColor_2 = stainColorMap[args.stain_2] |
| 55 | + stainColor_3 = stainColorMap[args.stain_3] |
| 56 | + |
| 57 | + W = np.array([stainColor_1, stainColor_2, stainColor_3]).T |
| 58 | + |
| 59 | + imDeconvolved = htk.ColorDeconvolution(imNmzd, W) |
| 60 | + |
| 61 | + imNucleiStain = imDeconvolved.Stains[:, :, 0].astype(np.float) |
| 62 | + |
| 63 | + # |
| 64 | + # Perform nuclei segmentation |
| 65 | + # |
| 66 | + print('>> Performing nuclei segmentation') |
| 67 | + |
| 68 | + # segment foreground |
| 69 | + imFgndMask = sp.ndimage.morphology.binary_fill_holes( |
| 70 | + imNucleiStain < args.foreground_threshold) |
| 71 | + |
| 72 | + # run adaptive multi-scale LoG filter |
| 73 | + imLog = htk.cLoG(imNucleiStain, imFgndMask, |
| 74 | + SigmaMin=args.min_radius * np.sqrt(2), |
| 75 | + SigmaMax=args.max_radius * np.sqrt(2)) |
| 76 | + |
| 77 | + imNucleiSegMask, Seeds, Max = htk.MaxClustering( |
| 78 | + imLog, imFgndMask, args.local_max_search_radius) |
| 79 | + |
| 80 | + # filter out small objects |
| 81 | + imNucleiSegMask = htk.FilterLabel( |
| 82 | + imNucleiSegMask, Lower=args.min_nucleus_area).astype(np.int) |
| 83 | + |
| 84 | + # |
| 85 | + # Generate annotations |
| 86 | + # |
| 87 | + objProps = skimage.measure.regionprops(imNucleiSegMask) |
| 88 | + |
| 89 | + print 'Number of nuclei = ', len(objProps) |
| 90 | + |
| 91 | + # create basic schema |
| 92 | + annotation = { |
| 93 | + "name": "Nuclei", |
| 94 | + "description": "Nuclei bounding boxes from a segmentation algorithm", |
| 95 | + "attributes": { |
| 96 | + "algorithm": { |
| 97 | + "color_normalization": "ReinhardNorm", |
| 98 | + "color_deconvolution": "ColorDeconvolution", |
| 99 | + "nuclei_segmentation": ["cLOG", |
| 100 | + "MaxClustering", |
| 101 | + "FilterLabel"] |
| 102 | + } |
| 103 | + }, |
| 104 | + "elements": [] |
| 105 | + } |
| 106 | + |
| 107 | + # add each nucleus as an element into the annotation schema |
| 108 | + for i in range(len(objProps)): |
| 109 | + |
| 110 | + c = [objProps[i].centroid[1], objProps[i].centroid[0], 0] |
| 111 | + width = objProps[i].bbox[3] - objProps[i].bbox[1] + 1 |
| 112 | + height = objProps[i].bbox[2] - objProps[i].bbox[0] + 1 |
| 113 | + |
| 114 | + cur_bbox = { |
| 115 | + "type": "rectangle", |
| 116 | + "center": c, |
| 117 | + "width": width, |
| 118 | + "height": height, |
| 119 | + } |
| 120 | + |
| 121 | + annotation["elements"].append(cur_bbox) |
| 122 | + |
| 123 | + # |
| 124 | + # Save output segmentation mask |
| 125 | + # |
| 126 | + print('>> Outputting nuclei segmentation mask') |
| 127 | + |
| 128 | + skimage.io.imsave(args.outputNucleiMaskFile, imNucleiSegMask) |
| 129 | + |
| 130 | + # |
| 131 | + # Save output annotation |
| 132 | + # |
| 133 | + print('>> Outputting nuclei annotation') |
| 134 | + |
| 135 | + with open(args.outputNucleiAnnotationFile, 'w') as annotationFile: |
| 136 | + json.dump(annotation, annotationFile, indent=2, sort_keys=False) |
| 137 | + |
| 138 | + |
| 139 | +if __name__ == "__main__": |
| 140 | + main(CLIArgumentParser().parse_args()) |
0 commit comments