changeset 1:41e8efe8df43 draft

"planemo upload for repository https://github.com/ohsu-comp-bio/S3segmenter commit c8f72e04db2cc6cc26f0359d5aa3b1a972bc6d53"
author watsocam
date Fri, 11 Mar 2022 23:37:49 +0000
parents 37acf42a824b
children 96d0d969ebc9
files S3segmenter.py macros.xml s3segmenter.xml save_tifffile_pyramid.py
diffstat 4 files changed, 333 insertions(+), 231 deletions(-) [+]
line wrap: on
line diff
--- a/S3segmenter.py	Fri Mar 12 00:18:40 2021 +0000
+++ b/S3segmenter.py	Fri Mar 11 23:37:49 2022 +0000
@@ -29,6 +29,9 @@
 import datetime
 from joblib import Parallel, delayed
 from rowit import WindowView, crop_with_padding_mask
+from save_tifffile_pyramid import save_pyramid
+import subprocess
+import ome_types
 
 
 def imshowpair(A,B):
@@ -101,7 +104,7 @@
         padded[padded == 1] = maxima.flatten()
         return padded.astype(np.bool)
 
-def S3AreaSegmenter(nucleiPM, images, TMAmask, threshold,outputPath):
+def S3AreaSegmenter(nucleiPM, images, TMAmask, threshold,fileprefix,outputPath):
     nucleiCenters = nucleiPM[:,:,0]
     TMAmask= (nucleiCenters>np.amax(nucleiCenters)*0.8)*TMAmask
     area = []
@@ -112,17 +115,62 @@
             threshold = threshold_otsu(image_gauss)
         mask=resize(image_gauss>threshold,(images.shape[1],images.shape[2]),order = 0)*TMAmask
         area.append(np.sum(np.sum(mask)))
-    np.savetxt(outputPath + os.path.sep + 'area.csv',(np.transpose(np.asarray(area))),fmt='%10.5f')  
+    os.mk    
+    np.savetxt(outputPath + os.path.sep + fileprefix + '_area.csv',(np.transpose(np.asarray(area))),fmt='%10.5f')  
     return TMAmask
-            
+
+def getMetadata(path,commit):
+    with tifffile.TiffFile(path) as tif:
+        if not tif.ome_metadata:
+            try:
+                x_res_tag = tif.pages[0].tags['XResolution'].value
+                y_res_tag = tif.pages[0].tags['YResolution'].value
+                physical_size_x = x_res_tag[0] / x_res_tag[1]
+                physical_size_y = y_res_tag[0] / y_res_tag[1]
+            except KeyError:
+                physical_size_x = 1
+                physical_size_y = 1
+            metadata_args = dict(
+            pixel_sizes=(physical_size_y, physical_size_x),
+            pixel_size_units=('µm', 'µm'),
+            software= 's3segmenter v' + commit
+            )
+        else: 
+            metadata=ome_types.from_xml(tif.ome_metadata)
+            metadata = metadata.images[0].pixels
+            metadata_args = dict(
+            pixel_sizes=(metadata.physical_size_y,metadata.physical_size_x),
+            pixel_size_units=('µm', 'µm'),
+            software= 's3segmenter v' + commit
+            )
+        return metadata_args
+
+def S3NucleiBypass(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFilter,nucleiRegion):        
+        foregroundMask =  nucleiPM
+        P = regionprops(foregroundMask, nucleiImage)
+        prop_keys = ['mean_intensity', 'label','area']
+        def props_of_keys(prop, keys):
+            return [prop[k] for k in keys]
+        
+        mean_ints, labels, areas = np.array(Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) 
+						for prop in P
+						)
+		        ).T
+        del P
+        maxArea = (logSigma[1]**2)*3/4
+        minArea = (logSigma[0]**2)*3/4
+        passed = np.logical_and(areas > minArea, areas < maxArea)
+        
+        foregroundMask *= np.isin(foregroundMask, labels[passed])
+        np.greater(foregroundMask, 0, out=foregroundMask)
+        foregroundMask = label(foregroundMask, connectivity=1).astype(np.int32)
+        return foregroundMask
 
 def S3NucleiSegmentationWatershed(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFilter,nucleiRegion):
     nucleiContours = nucleiPM[:,:,1]
     nucleiCenters = nucleiPM[:,:,0]
-    
     mask = resize(TMAmask,(nucleiImage.shape[0],nucleiImage.shape[1]),order = 0)>0
-    
-    if nucleiRegion == 'localThreshold':
+    if nucleiRegion == 'localThreshold' or nucleiRegion == 'localMax':
         Imax =  peak_local_max(extrema.h_maxima(255-nucleiContours,logSigma[0]),indices=False)
         Imax = label(Imax).astype(np.int32)
         foregroundMask =  watershed(nucleiContours, Imax, watershed_line=True)
@@ -146,27 +194,8 @@
         foregroundMask *= np.isin(foregroundMask, labels[passed])
         np.greater(foregroundMask, 0, out=foregroundMask)
         foregroundMask = label(foregroundMask, connectivity=1).astype(np.int32)
-    elif nucleiRegion =='bypass':
-        foregroundMask =  nucleiPM[:,:,0]
-        P = regionprops(foregroundMask, nucleiImage)
-        prop_keys = ['mean_intensity', 'label','area']
-        def props_of_keys(prop, keys):
-            return [prop[k] for k in keys]
-        
-        mean_ints, labels, areas = np.array(Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) 
-						for prop in P
-						)
-		        ).T
-        del P
-        maxArea = (logSigma[1]**2)*3/4
-        minArea = (logSigma[0]**2)*3/4
-        passed = np.logical_and(areas > minArea, areas < maxArea)
-        
-        foregroundMask *= np.isin(foregroundMask, labels[passed])
-        np.greater(foregroundMask, 0, out=foregroundMask)
-        foregroundMask = label(foregroundMask, connectivity=1).astype(np.int32)
+    
     else:
-     
         if len(logSigma)==1:
              nucleiDiameter  = [logSigma*0.5, logSigma*1.5]
         else:
@@ -289,41 +318,45 @@
     cytoplasmMask = np.subtract(finalCellMask,nucleiMask)
     return cytoplasmMask,nucleiMask,finalCellMask
     
-def exportMasks(mask,image,outputPath,filePrefix,fileName,saveFig=True,saveMasks = True):
+def exportMasks(mask,image,outputPath,filePrefix,fileName,commit,metadata_args,saveFig=True,saveMasks = True):
     outputPath =outputPath + os.path.sep + filePrefix
     if not os.path.exists(outputPath):
         os.makedirs(outputPath)
+    previewPath = outputPath + os.path.sep + 'qc'        
+    if not os.path.exists(previewPath):
+        os.makedirs(previewPath)
+   
     if saveMasks ==True:
-        kwargs={}
-        kwargs['bigtiff'] = True
-        kwargs['photometric'] = 'minisblack'
-        resolution = np.round(1)
-        kwargs['resolution'] = (resolution, resolution, 'cm')
-        kwargs['metadata'] = None
-        kwargs['description'] = '!!xml!!'
-        imsave(outputPath + os.path.sep + fileName + 'Mask.tif',mask, plugin="tifffile")
-        
+        save_pyramid(
+            mask,
+            outputPath + os.path.sep + fileName + '.ome.tif',
+            channel_names=fileName,
+            is_mask=True,
+            **metadata_args
+        )     
     if saveFig== True:
         mask=np.uint8(mask>0)
         edges = find_boundaries(mask,mode = 'outer')
         stacked_img=np.stack((np.uint16(edges)*np.amax(image),image),axis=0)
-        tifffile.imsave(outputPath + os.path.sep + fileName + 'Outlines.tif',stacked_img)
+        save_pyramid(
+            stacked_img,
+            previewPath + os.path.sep + fileName + 'Outlines.ome.tif',
+            channel_names=[f'{fileName} outlines', 'Segmentation image'],
+            is_mask=False,
+            **metadata_args
+        )
         
 def S3punctaDetection(spotChan,mask,sigma,SD):
     Ilog = -gaussian_laplace(np.float32(spotChan),sigma)
-    fgm=peak_local_max(Ilog, indices=False,footprint=np.ones((3, 3)))
+    tissueMask = spotChan >0
+    fgm=peak_local_max(Ilog, indices=False,footprint=np.ones((3, 3)))*tissueMask
     test=Ilog[fgm==1]
     med = np.median(test)
     mad = np.median(np.absolute(test - med))
     thresh = med + 1.4826*SD*mad
     return (Ilog>thresh)*fgm*(mask>0)
-    
-#    stacked_img=np.stack((spots*4095,nucleiCrop),axis=0)
-#    tifffile.imsave('D:/Seidman/ZeissTest Sets/registration/spottest.tif',stacked_img)
-       
-        
-    # assign nan to tissue mask
-
+ 
+     
 
 if __name__ == '__main__':
     parser=argparse.ArgumentParser()
@@ -339,128 +372,44 @@
     parser.add_argument("--crop",choices=['interactiveCrop','autoCrop','noCrop','dearray','plate'], default = 'noCrop')
     parser.add_argument("--cytoMethod",choices=['hybrid','distanceTransform','bwdistanceTransform','ring'],default = 'distanceTransform')
     parser.add_argument("--nucleiFilter",choices=['IntPM','LoG','Int','none'],default = 'IntPM')
-    parser.add_argument("--nucleiRegion",choices=['watershedContourDist','watershedContourInt','watershedBWDist','dilation','localThreshold','bypass','pixellevel'], default = 'watershedContourInt')
+    parser.add_argument("--nucleiRegion",choices=['watershedContourDist','watershedContourInt','watershedBWDist','dilation','localThreshold','localMax','bypass','pixellevel'], default = 'watershedContourInt')
     parser.add_argument("--pixelThreshold",type = float, default = -1)
     parser.add_argument("--segmentCytoplasm",choices = ['segmentCytoplasm','ignoreCytoplasm'],default = 'ignoreCytoplasm')
     parser.add_argument("--cytoDilation",type = int, default = 5)
     parser.add_argument("--logSigma",type = int, nargs = '+', default = [3, 60])
-    parser.add_argument("--CytoMaskChan",type=int, nargs = '+', default=[1])
-    parser.add_argument("--pixelMaskChan",type=int, nargs = '+', default=[1])
-    parser.add_argument("--TissueMaskChan",type=int, nargs = '+', default=-1)
-    parser.add_argument("--detectPuncta",type=int, nargs = '+', default=[-1])
-    parser.add_argument("--punctaSigma", nargs = '+', type=float, default=[1])
+    parser.add_argument("--CytoMaskChan",type=int, nargs = '+', default=[2])
+    parser.add_argument("--pixelMaskChan",type=int, nargs = '+', default=[2])
+    parser.add_argument("--TissueMaskChan",type=int, nargs = '+', default=0)
+    parser.add_argument("--detectPuncta",type=int, nargs = '+', default=[0])
+    parser.add_argument("--punctaSigma", nargs = '+', type=float, default=[0])
     parser.add_argument("--punctaSD", nargs = '+', type=float, default=[4])
     parser.add_argument("--saveMask",action='store_false')
     parser.add_argument("--saveFig",action='store_false')
     args = parser.parse_args()
     
-    # gather filename information
-    #exemplar001
-#    imagePath = 'D:/LSP/cycif/testsets/exemplar-001/registration/exemplar-001small.ome.tif'
-#    outputPath = 'D:/LSP/cycif/testsets/exemplar-001/segmentation'
-##    nucleiClassProbPath = 'D:/LSP/cycif/testsets/exemplar-001/probability_maps/exemplar-001_NucleiPM_0.tif'
-##    contoursClassProbPath = 'D:/LSP/cycif/testsets/exemplar-001/probability_maps/exemplar-001_ContoursPM_0.tif'
-#    contoursClassProbPath =''
-#    stackProbPath = 'D:/LSP/cycif/testsets/exemplar-001_Probabilities_0.tif'
-#    maskPath = 'D:/LSP/cycif/testsets/exemplar-001/dearray/masks/A1_mask.tif'
-#    args.cytoMethod = 'hybrid'
-#    args.nucleiRegion = 'localThreshold'
-#    args.segmentCytoplasm = 'segmentCytoplasm'
-	
-#	    exemplar002
-#    imagePath = 'D:/LSP/cycif/testsets/exemplar-002/dearray/1.tif'
-#    outputPath = 'D:/LSP/cycif/testsets/exemplar-002/segmentation'
-#    nucleiClassProbPath = ''#'D:/LSP/cycif/testsets/exemplar-002/prob_map/1_NucleiPM_1.tif'
-#    contoursClassProbPath = ''#'D:/LSP/cycif/testsets/exemplar-002/prob_map/1_ContoursPM_1.tif'
-#    stackProbPath = 'D:/LSP/cycif/testsets/exemplar-002/probability-maps/unmicst_1new_Probabilities_1.tif'
-#    maskPath = 'D:/LSP/cycif/testsets/exemplar-002/dearrayPython/masks/1_mask.tif'
-#    args.probMapChan =1
-#    args.cytoMethod = 'hybrid'
-#    args.mask = 'TMA'
-#    args.crop = 'dearray'
-#    args.crop = 'autoCrop'
-#    args.segmentCytoplasm = 'segmentCytoplasm'
-	#PTCL
-#    imagePath = 'D:/LSP/cycif/testsets/PTCL/dearrayPython/1.tif'
-#    outputPath = 'D:/LSP/cycif/testsets/PTCL/dearrayPython/segmentation'
-#    nucleiClassProbPath = ''#'D:/LSP/cycif/testsets/exemplar-002/prob_map/1_NucleiPM_1.tif'
-#    contoursClassProbPath = ''#'D:/LSP/cycif/testsets/exemplar-002/prob_map/1_ContoursPM_1.tif'
-#    stackProbPath = 'D:/LSP/cycif/testsets/PTCL/prob_maps/1_Probabilities_40.tif'
-#    maskPath = 'D:/LSP/cycif/testsets/exemplar-002/dearrayPython/masks/1_mask.tif'   
-#    args.crop = 'dearray'
-#    args.segmentCytoplasm = 'segmentCytoplasm'
-#	
-	    #punctatest
-#    imagePath = 'Z:/IDAC/Clarence/Seidman/ZeissMouseTestSet/31082020_XXWT_Txnip550_Ddx3x647_WGA488_40x_1.tif'
-#    outputPath = 'Z:/IDAC/Clarence/Seidman/ZeissMouseTestSet/segmentation'
-##    nucleiClassProbPath = 'Z:/IDAC/Clarence/Seidman/ZeissMouseTestSet/probability-maps/test_NucleiPM_1.tif'
-##    contoursClassProbPath = 'Z:/IDAC/Clarence/Seidman/DanMouse/probability-maps/test_ContoursPM_1.tif'
-#    contoursClassProbPath =''
-#    stackProbPath = 'Z:/IDAC/Clarence/Seidman/ZeissMouseTestSet/probability-maps/31082020_XXWT_Txnip550_Ddx3x647_WGA488_40x_1_Probabilities_2.tif'
-#    maskPath = 'D:/Seidman/ZeissTest Sets/segmentation/13042020_15AP_FAP488_LINC550_DCN647_WGA_40x_1/cellMask.tif'
-#    args.nucleiRegion = 'localThreshold'
-#    args.logSigma = [45, 300]
-#    args.segmentCytoplasm = 'ignoreCytoplasm'
-#    args.detectPuncta = [1]
-#    args.punctaSigma = [1]
-#    args.punctaSD = [3]
-    
-    
-	#plate 
-#    imagePath = 'Y:/sorger/data/computation/Jeremy/caitlin-ddd-cycif-registered/Plate1/E3_fld_1/registration/E3_fld_1.ome.tif'
-#    outputPath = 'Y:/sorger/data/computation/Jeremy/caitlin-ddd-cycif-registered/Plate1/E3_fld_1/segmentation'
-#    nucleiClassProbPath = 'Y:/sorger/data/computation/Jeremy/caitlin-ddd-cycif-registered/Plate1/E3_fld_1/prob_maps/E3_fld_1_NucleiPM_1.tif'
-#    contoursClassProbPath = 'Y:/sorger/data/computation/Jeremy/caitlin-ddd-cycif-registered/Plate1/E3_fld_1/prob_maps/E3_fld_1_ContoursPM_1.tif'
-#    maskPath = 'D:/LSP/cycif/testsets/exemplar-001/dearray/masks/A1_mask.tif'
-#    args.crop = 'plate'
-#    args.cytoMethod ='hybrid'
-        
-    #large tissue
-#    imagePath =  'D:/WD-76845-097.ome.tif'
-#    outputPath = 'D:/'
-##    nucleiClassProbPath = 'D:/WD-76845-097_NucleiPM_28.tif'
-#    contoursClassProbPath = ""#'D:/WD-76845-097_ContoursPM_28.tif'
-#    stackProbPath = 'D:/ilastik/WD-76845-097_Probabilities_0.tif'
-#    maskPath = 'D:/LSP/cycif/testsets/exemplar-001/dearray/masks/A1_mask.tif'
-#    args.nucleiRegion = 'localThreshold'
-#    args.crop = 'autoCrop' 
-#    args.TissueMaskChan =[0]
-	
-	    #maskRCNN
-#    imagePath =  'D:/Seidman/s3segtest/registration/1.tif'
-#    outputPath = 'D:/Seidman/s3segtest/segmentation'
-#    stackProbPath = 'D:/Seidman/s3segtest/1_Probabilities_0.tif'
-#    contoursClassProbPath = ''
-#    maskPath = 'D:/LSP/cycif/testsets/exemplar-001/dearray/masks/A1_mask.tif'
-#    args.nucleiRegion = 'bypass'
-#    args.crop = 'noCrop' 
-#    args.TissueMaskChan =[0]
-#    args.logSigma = [45,300]
-        
-#    #pixellevel
-#    imagePath =  'D:/Olesja/D2107_spleen_DAPI-EdU_01/Layer0/D2107_spleen_DAPI-EdU_01.btf'
-#    outputPath = 'D:/Olesja/D2107_spleen_DAPI-EdU_01/segmentation'
-#    stackProbPath = 'D:/Seidman/s3segtest/1_Probabilities_0.tif'
-#    contoursClassProbPath = 'D:/Olesja/D2107_spleen_DAPI-EdU_01/prob_maps3Class/D2107_spleen_DAPI-EdU_01_ContoursPM_0.tif'
-#    nucleiClassProbPath = 'D:/Olesja/D2107_spleen_DAPI-EdU_01/prob_maps3Class/D2107_spleen_DAPI-EdU_01_NucleiPM_0.tif'
-#    maskPath = 'D:/LSP/cycif/testsets/exemplar-001/dearray/masks/A1_mask.tif'
-#    args.nucleiRegion = 'pixellevel'
-#    args.crop = 'noCrop' 
-#    args.TissueMaskChan =[0]
-#    args.pixelThreshold = 0.06
-           
     imagePath = args.imagePath
     outputPath = args.outputPath
     nucleiClassProbPath = args.nucleiClassProbPath
     contoursClassProbPath = args.contoursClassProbPath
     stackProbPath = args.stackProbPath
     maskPath = args.maskPath
-       
+    
+    commit = '1.3.11'#subprocess.check_output(['git', 'describe', '--tags']).decode('ascii').strip()
+    metadata = getMetadata(imagePath,commit)
+    
     fileName = os.path.basename(imagePath)
     filePrefix = fileName[0:fileName.index('.')]
-    
+ 
+    # convert 1-based indexing to 0-based indexing
+    CytoMaskChan = args.CytoMaskChan
+    CytoMaskChan[:] = [number - 1 for number in CytoMaskChan]
+    pixelMaskChan = args.pixelMaskChan
+    pixelMaskChan[:] = [number - 1 for number in pixelMaskChan]
+ 
+     
     if not os.path.exists(outputPath):
         os.makedirs(outputPath)
+        
     
     # get channel used for nuclei segmentation
 
@@ -471,29 +420,25 @@
     elif len(stackProbPath)>0:
         legacyMode = 0
         probPrefix = os.path.basename(stackProbPath)
-        index = re.search('%s(.*)%s' % ('Probabilities', '.tif'), stackProbPath).group(1)
-        if len(index)==0:
-            nucMaskChan = args.probMapChan
-        else:
-            nucMaskChan  = int(re.sub("\D", "", index))
     else:
         print('NO PROBABILITY MAP PROVIDED')
-    if args.probMapChan ==-1:
-        if nucMaskChan ==-1:
-            sys.exit('INVALID NUCLEI CHANNEL SELECTION. SELECT CHANNEL USING --probMapChan')
-        else:
-            print('extracting nuclei channel from filename')
+
+    if args.probMapChan==-1:
+        print('Using first channel by default!')
+        nucMaskChan = 0
     else:
         nucMaskChan = args.probMapChan
+        nucMaskChan = nucMaskChan -1 #convert 1-based indexing to 0-based indexing      
 
-    if args.TissueMaskChan==-1:
-        TissueMaskChan = copy.copy(args.CytoMaskChan)
+    if args.TissueMaskChan==0:
+        TissueMaskChan = copy.copy(CytoMaskChan)
         TissueMaskChan.append(nucMaskChan)
     else:
         TissueMaskChan = args.TissueMaskChan[:]
+        TissueMaskChan[:] = [number - 1 for number in TissueMaskChan]#convert 1-based indexing to 0-based indexing        
         TissueMaskChan.append(nucMaskChan)
-         
-    #crop images if needed
+
+	#crop images if needed
     if args.crop == 'interactiveCrop':
         nucleiCrop = tifffile.imread(imagePath,key = nucMaskChan)
         r=cv2.selectROI(resize(nucleiCrop,(nucleiCrop.shape[0] // 30, nucleiCrop.shape[1] // 30)))
@@ -519,9 +464,12 @@
         nucleiPM = np.dstack((nucleiPM,nucleiProbMaps[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3])]))
     else:
         nucleiProbMaps = imread(stackProbPath)
-        nucleiPM = nucleiProbMaps[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3]),0:2]
+        if len(nucleiProbMaps.shape)==2:
+            nucleiPM = nucleiProbMaps[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3])]
+        else:
+            nucleiPM = nucleiProbMaps[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3]),:]
         PMSize = nucleiProbMaps.shape
-
+      
     # mask the core/tissue
     if args.crop == 'dearray':
         TMAmask = tifffile.imread(maskPath)
@@ -546,84 +494,99 @@
                 count+=1
         TMAmask = np.max(tissue,axis = 0)
 
- #       tissue_gauss = tissueCrop
-#        tissue_gauss1 = tissue_gauss.astype(float)
-#        tissue_gauss1[tissue_gauss>np.percentile(tissue_gauss,99)]=np.nan
-#        TMAmask = np.log2(tissue_gauss+1)>threshold_otsu(np.log2(tissue_gauss+1))
-        #imshow(TMAmask)
+
         del tissue_gauss, tissue
-
+      
     # nuclei segmentation
     if args.nucleiRegion == 'pixellevel':
-        pixelTissue = np.empty((len(args.pixelMaskChan),nucleiCrop.shape[0],nucleiCrop.shape[1]),dtype=np.uint16)
+        pixelTissue = np.empty((len(pixelMaskChan),nucleiCrop.shape[0],nucleiCrop.shape[1]),dtype=np.uint16)
         count=0
-        for iChan in args.pixelMaskChan:
+        for iChan in pixelMaskChan:
                 pixelCrop = tifffile.imread(imagePath,key=iChan)
                 pixelTissue[count,:,:] = pixelCrop[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3])]
                 count+=1
-        nucleiMask = S3AreaSegmenter(nucleiPM, pixelTissue, TMAmask,args.pixelThreshold,outputPath)
+        nucleiMask = S3AreaSegmenter(nucleiPM, pixelTissue, TMAmask,args.pixelThreshold,filePrefix,outputPath)
+    elif args.nucleiRegion == 'bypass':    
+        nucleiMask = S3NucleiBypass(nucleiPM,nucleiCrop,args.logSigma,TMAmask,args.nucleiFilter,args.nucleiRegion)
     else:
-		   nucleiMask = S3NucleiSegmentationWatershed(nucleiPM,nucleiCrop,args.logSigma,TMAmask,args.nucleiFilter,args.nucleiRegion)
+        nucleiMask = S3NucleiSegmentationWatershed(nucleiPM,nucleiCrop,args.logSigma,TMAmask,args.nucleiFilter,args.nucleiRegion)
     del nucleiPM
     # cytoplasm segmentation
     if args.segmentCytoplasm == 'segmentCytoplasm':
         count =0
         if args.crop == 'noCrop' or args.crop == 'dearray' or args.crop == 'plate':
-            cyto=np.empty((len(args.CytoMaskChan),nucleiCrop.shape[0],nucleiCrop.shape[1]),dtype=np.uint16)    
-            for iChan in args.CytoMaskChan:
+            cyto=np.empty((len(CytoMaskChan),nucleiCrop.shape[0],nucleiCrop.shape[1]),dtype=np.uint16)    
+            for iChan in CytoMaskChan:
                 cyto[count,:,:] =  tifffile.imread(imagePath, key=iChan)
                 count+=1
         elif args.crop =='autoCrop':
-            cyto=np.empty((len(args.CytoMaskChan),int(rect[2]),int(rect[3])),dtype=np.int16)
-            for iChan in args.CytoMaskChan:
+            cyto=np.empty((len(CytoMaskChan),int(rect[2]),int(rect[3])),dtype=np.int16)
+            for iChan in CytoMaskChan:
                 cytoFull= tifffile.imread(imagePath, key=iChan)
                 cyto[count,:,:] = cytoFull[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3])]
                 count+=1                
         else:
-            cyto=np.empty((len(args.CytoMaskChan),rect[3],rect[2]),dtype=np.int16)
-            for iChan in args.CytoMaskChan:
+            cyto=np.empty((len(CytoMaskChan),rect[3],rect[2]),dtype=np.int16)
+            for iChan in CytoMaskChan:
                 cytoFull= tifffile.imread(imagePath, key=iChan)
                 cyto[count,:,:] = cytoFull[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3])]
                 count+=1
         cyto = np.amax(cyto,axis=0)
         cytoplasmMask,nucleiMaskTemp,cellMask = S3CytoplasmSegmentation(nucleiMask,cyto,TMAmask,args.cytoMethod,args.cytoDilation)
-        exportMasks(nucleiMaskTemp,nucleiCrop,outputPath,filePrefix,'nuclei',args.saveFig,args.saveMask)
-        exportMasks(cytoplasmMask,cyto,outputPath,filePrefix,'cyto',args.saveFig,args.saveMask)
-        exportMasks(cellMask,cyto,outputPath,filePrefix,'cell',args.saveFig,args.saveMask)
+        exportMasks(nucleiMaskTemp,nucleiCrop,outputPath,filePrefix,'nuclei',commit,metadata,args.saveFig,args.saveMask)
+        exportMasks(cytoplasmMask,cyto,outputPath,filePrefix,'cyto',commit,metadata,args.saveFig,args.saveMask)
+        exportMasks(cellMask,cyto,outputPath,filePrefix,'cell',commit,metadata,args.saveFig,args.saveMask)
   
         cytoplasmMask,nucleiMaskTemp,cellMask = S3CytoplasmSegmentation(nucleiMask,cyto,TMAmask,'ring',args.cytoDilation)
-        exportMasks(nucleiMaskTemp,nucleiCrop,outputPath,filePrefix,'nucleiRing',args.saveFig,args.saveMask)
-        exportMasks(cytoplasmMask,cyto,outputPath,filePrefix,'cytoRing',args.saveFig,args.saveMask)
-        exportMasks(cellMask,cyto,outputPath,filePrefix,'cellRing',args.saveFig,args.saveMask)
+        exportMasks(nucleiMaskTemp,nucleiCrop,outputPath,filePrefix,'nucleiRing',commit,metadata,args.saveFig,args.saveMask)
+        exportMasks(cytoplasmMask,cyto,outputPath,filePrefix,'cytoRing',commit,metadata,args.saveFig,args.saveMask)
+        exportMasks(cellMask,cyto,outputPath,filePrefix,'cellRing',commit,metadata,args.saveFig,args.saveMask)
         
     elif args.segmentCytoplasm == 'ignoreCytoplasm':
-        exportMasks(nucleiMask,nucleiCrop,outputPath,filePrefix,'nuclei')
+        exportMasks(nucleiMask,nucleiCrop,outputPath,filePrefix,'nuclei',commit,metadata)
         cellMask = nucleiMask
-        exportMasks(nucleiMask,nucleiCrop,outputPath,filePrefix,'cell')
+        exportMasks(nucleiMask,nucleiCrop,outputPath,filePrefix,'cell',commit,metadata)
         cytoplasmMask = nucleiMask
         
-        
-    if (np.min(args.detectPuncta)>-1):
-        if len(args.detectPuncta) != len(args.punctaSigma):
-            args.punctaSigma = args.punctaSigma[0] * np.ones(len(args.detectPuncta))
+    detectPuncta = args.detectPuncta
+    if (np.min(detectPuncta)>0):
+        detectPuncta[:] = [number - 1 for number in detectPuncta] #convert 1-based indexing to 0-based indexing    
+        if len(detectPuncta) != len(args.punctaSigma):
+            args.punctaSigma = args.punctaSigma[0] * np.ones(len(detectPuncta))
  
   
-        if len(args.detectPuncta) != len(args.punctaSD):
-            args.punctaSD = args.punctaSD[0] * np.ones(len(args.detectPuncta))
+        if len(detectPuncta) != len(args.punctaSD):
+            args.punctaSD = args.punctaSD[0] * np.ones(len(detectPuncta))
   
         counter=0
-        for iPunctaChan in args.detectPuncta:
+        for iPunctaChan in detectPuncta:
             punctaChan = tifffile.imread(imagePath,key = iPunctaChan)
             punctaChan = punctaChan[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3])]
             spots=S3punctaDetection(punctaChan,cellMask,args.punctaSigma[counter],args.punctaSD[counter])
-            cellspotmask = nucleiMask#tifffile.imread(maskPath) #REMOVE THIS LATER
+            cellspotmask = nucleiMask
             P = regionprops(cellspotmask,intensity_image = spots ,cache=False)
             numSpots = []
             for prop in P:
                 numSpots.append(np.uint16(np.round(prop.mean_intensity * prop.area)))
-            np.savetxt(outputPath + os.path.sep + 'numSpots_chan' + str(iPunctaChan) +'.csv',(np.transpose(np.asarray(numSpots))),fmt='%10.5f')    
+            np.savetxt(outputPath + os.path.sep + 'numSpots_chan' + str(iPunctaChan+1) +'.csv',(np.transpose(np.asarray(numSpots))),fmt='%10.5f')    
             edges = 1-(cellMask>0)
             stacked_img=np.stack((np.uint16((spots+edges)>0)*np.amax(punctaChan),punctaChan),axis=0)
-            tifffile.imsave(outputPath + os.path.sep + filePrefix + os.path.sep + 'punctaChan'+str(iPunctaChan) + 'Outlines.tif',stacked_img)
-            counter=counter+1        
-        #fix bwdistance watershed
+             
+            
+            outputPathPuncta = outputPath + os.path.sep + filePrefix + os.path.sep + 'punctaChan'+str(iPunctaChan+1) + 'Outlines.ome.tif'
+            
+            # metadata_args = dict(
+            #     pixel_sizes=(metadata.physical_size_y, metadata.physical_size_x),
+            #     pixel_size_units=('µm', 'µm'),
+            #     software= 's3segmenter v' + commit
+            #     )
+            save_pyramid(
+                stacked_img,
+                outputPathPuncta,
+                channel_names=['puncta outlines', 'image channel'],
+                is_mask=False,
+                **metadata
+                )     
+            
+            counter=counter+1    
+
--- a/macros.xml	Fri Mar 12 00:18:40 2021 +0000
+++ b/macros.xml	Fri Mar 11 23:37:49 2022 +0000
@@ -2,12 +2,14 @@
 <macros>
     <xml name="requirements">
         <requirements>
-            <requirement type="package" version="3.6">python</requirement>
+            <container type="docker">labsyspharm/s3segmenter:@VERSION@</container>
+            <requirement type="package" version="3.7">python</requirement>
             <requirement type="package">scikit-learn</requirement>
-            <requirement type="package">scikit-image</requirement>
+            <requirement version="0.14.2" type="package">scikit-image</requirement>
             <requirement type="package">matplotlib</requirement>
-            <requirement type="package">tifffile</requirement>
+            <requirement version="2021.6.6" type="package">tifffile</requirement>
             <requirement type="package">opencv</requirement>
+            <!-- <requirement type="package">ome_types</requirement> -->
         </requirements>
     </xml>
 
@@ -19,6 +21,6 @@
         </citations>
     </xml>
 
-    <token name="@VERSION@">1.2.1</token>
+    <token name="@VERSION@">1.3.12</token>
     <token name="@CMD_BEGIN@">python3 $__tool_directory__/S3segmenter.py</token>
 </macros>
--- a/s3segmenter.xml	Fri Mar 12 00:18:40 2021 +0000
+++ b/s3segmenter.xml	Fri Mar 11 23:37:49 2022 +0000
@@ -1,4 +1,4 @@
-<tool id="s3segmenter" name="s3segmenter" version="@VERSION@.3" profile="17.09">
+<tool id="s3segmenter" name="s3segmenter" version="@VERSION@.0" profile="17.09">
     <description>S3segmenter is a Python-based set of functions that generates single cell (nuclei and cytoplasm) label masks.</description>
     <macros>
         <import>macros.xml</import>
@@ -39,12 +39,22 @@
         --stackProbPath ./Probabilities.tif
         #end if
 
-        --mask $mask
         --probMapChan $probMapChan
-        --crop $crop
+        --crop $crop_select.crop
+        
+        #if $crop_select.crop == "dearray"
+          --maskPath $crop_select.maskPath
+        #end if
+
         --cytoMethod $cytoMethod
         --nucleiFilter $nucleiFilter
-        --nucleiRegion $nucleiRegion
+        --nucleiRegion $nucleiRegion_select.nucleiRegion
+
+        #if $nucleiRegion_select.nucleiRegion == "pixellevel"
+          --pixelThreshold $nucleiRegion_select.pixelThreshold
+          --pixelMaskChan $nucleiRegion_select.pixelMaskChan
+        #end if
+
         --segmentCytoplasm $segmentCytoplasm
         --cytoDilation $adv.cytoDilation
         --logSigma $adv.logSigma
@@ -72,18 +82,20 @@
         <param name="contoursClassProbPath" type="data" format="tiff" optional="true" label="Contours Class Probabilities"/>
         <param name="nucleiClassProbPath" type="data" format="tiff" optional="true" label="Nuclei Class Probabilities"/>
         <param name="stackProbPath" type="data" format="tiff" optional="true" label="Stack Probabilities"/>
-        <param name="mask" type="select" label="Choose mask: TMA, tissue, none.">
-            <option selected="true" value="tissue">tissue</option>
-            <option value="TMA">TMA</option>
-            <option value="none">none</option>
-        </param>
         <param name="probMapChan" type="integer" value="-1" label="Probability Maps Channel"/>
-        <param name="crop" type="select" label="Crop Strategy">
-            <option selected="true" value="noCrop">No Crop</option>
-            <option value="autoCrop">Automatic Crop</option>
-            <option value="dearray">De-array</option>
-            <option value="plate">Plate</option>
-        </param>
+
+        <conditional name="crop_select">
+            <param name="crop" type="select" label="Crop Strategy">
+                <option selected="true" value="noCrop">No Crop</option>
+                <option value="autoCrop">Automatic Crop</option>
+                <option value="dearray">De-array</option>
+                <option value="plate">Plate</option>
+            </param>
+            <when value="dearray">
+                <param name="maskPath" type="data" format="tiff" label="TMA Mask File"/>
+            </when>
+        </conditional>
+
         <param name="cytoMethod" type="select" label="Cyto Method">
             <option value="hybrid">Hybrid</option>
             <option selected="true" value="distanceTransform">Distance Transform</option>
@@ -96,13 +108,24 @@
             <option value="Int">Int</option>
             <option value="none">none</option>
         </param>
-        <param name="nucleiRegion" type="select" label="Nuclei Region">
-            <option value="watershedContourDist">watershedContourDist</option>
-            <option selected="true" value="watershedContourInt">watershedContourInt</option>
-            <option value="watershedBWDist">watershedBWDist</option>
-            <option value="dilation">dilation</option>
-            <option value="localThreshold">localThreshold</option>
-        </param>
+
+        <conditional name="nucleiRegion_select">
+            <param name="nucleiRegion" type="select" label="Nuclei Region">
+                <option value="watershedContourDist">watershedContourDist</option>
+                <option selected="true" value="watershedContourInt">watershedContourInt</option>
+                <option value="watershedBWDist">watershedBWDist</option>
+                <option value="dilation">dilation</option>
+                <option value="localThreshold">localThreshold</option>
+                <option value="localMax">localMax</option>
+                <option value="bypass">bypass</option>
+                <option value="pixellevel">pixellevel</option>
+            </param>
+            <when value="pixellevel">
+                <param name="pixelThreshold" type="float" value="-1.0" Label="Pixel Threshold"/>
+                <param name="pixelMaskChan" type="text" value="2" Label="Pixel Mask Channel"/>
+            </when>
+        </conditional>
+
         <param name="segmentCytoplasm" type="select" label="Segment Cytoplasm">
             <option value="segmentCytoplasm">segmentCytoplasm</option>
             <option selected="true" value="ignoreCytoplasm">ignoreCytoplasm</option>
@@ -113,7 +136,7 @@
         <section name="adv" title="Advanced Options" expanded="false">
             <param name="cytoDilation" type="integer" value="5" label="Cyto Dilation"/>
             <param name="logSigma" type="text" value="3 60" label="logSigma"/>
-            <param name="CytoMaskChan" type="text" value="1" label="Cyto Mask Channel"/>
+            <param name="CytoMaskChan" type="text" value="2" label="Cyto Mask Channel"/>
             <!-- Bug with S3Segmenter code, expects int not list
             <param name="TissueMaskChan" type="text" value="-1" label="Tissue Mask Channel"/>
             -->
@@ -125,16 +148,16 @@
 
     </inputs>
     <outputs>
-        <data format="tiff" name="cell_mask" from_work_dir="*/cellMask.tif" label="cellMasks">
+        <data format="tiff" name="cell_mask" from_work_dir="image/cell.ome.tif" label="cellMasks">
             <filter>saveMask is True</filter>
         </data>
-        <data format="tiff" name="nuclei_mask" from_work_dir="*/nucleiMask.tif" label="nucleiMasks">
+        <data format="tiff" name="nuclei_mask" from_work_dir="image/nuclei.ome.tif" label="nucleiMasks">
             <filter>saveMask is True</filter>
         </data>
-        <data format="tiff" name="cell_outlines" from_work_dir="*/cellOutlines.tif" label="cellOutlines">
+	<data format="tiff" name="cell_outlines" from_work_dir="image/qc/cellOutlines.ome.tif" label="cellOutlines">
             <filter>saveFig is True</filter>
         </data>
-        <data format="tiff" name="nuclei_outlines" from_work_dir="*/nucleiOutlines.tif" label="nucleiOutlines">
+	<data format="tiff" name="nuclei_outlines" from_work_dir="image/qc/nucleiOutlines.ome.tif" label="nucleiOutlines">
             <filter>saveFig is True</filter>
         </data>
     </outputs>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/save_tifffile_pyramid.py	Fri Mar 11 23:37:49 2022 +0000
@@ -0,0 +1,114 @@
+import numpy as np
+import tifffile
+import skimage.transform
+
+PHYSICAL_SIZE_UNIT = ['Ym', 'Zm', 'Em', 'Pm', 'Tm', 'Gm', 'Mm', 'km', 'hm', 'dam', 'm', 'dm', 'cm', 'mm', 'µm', 'nm', 'pm', 'fm', 'am', 'zm', 'ym', 'Å', 'thou', 'li', 'in', 'ft', 'yd', 'mi', 'ua', 'ly', 'pc', 'pt', 'pixel', 'reference frame']
+
+def normalize_image_shape(img):
+    assert img.ndim in (2, 3), (
+        'image must be 2D (Y, X) or 3D (C, Y, X)'
+    )
+    if img.ndim == 2:
+        img = img.reshape(1, *img.shape)
+    assert np.argmin(img.shape) == 0, (
+        '3D image must be in (C, Y, X) order'
+    )
+    return img
+
+def save_pyramid(
+    out_img, output_path,
+    pixel_sizes=(1, 1),
+    pixel_size_units=('µm', 'µm'),
+    channel_names=None,
+    software=None,
+    is_mask=False
+):
+    assert '.ome.tif' in str(output_path)
+    assert len(pixel_sizes) == len(pixel_size_units) == 2
+    assert out_img.ndim in (2, 3), (
+        'image must be either 2D (Y, X) or 3D (C, Y, X)'
+    )
+    
+    img_shape_ori = out_img.shape
+    out_img = normalize_image_shape(out_img)
+    img_shape = out_img.shape
+
+    size_x, size_y = np.array(pixel_sizes, dtype=float)
+    unit_x, unit_y = pixel_size_units
+
+    assert (unit_x in PHYSICAL_SIZE_UNIT) & (unit_y in PHYSICAL_SIZE_UNIT), (
+        f'pixel_size_units must be a tuple of the followings: '
+        f'{", ".join(PHYSICAL_SIZE_UNIT)}'
+    )
+
+    n_channels = img_shape[0]
+    if channel_names == None:
+        channel_names = [f'Channel {i}' for i in range(n_channels)]
+    else:
+        if type(channel_names) == str:
+            channel_names = [channel_names]
+        n_channel_names = len(channel_names)
+        assert n_channel_names == n_channels, (
+            f'number of channel_names ({n_channel_names}) must match '
+            f'number of channels ({n_channels})'
+        )
+    
+    if software == None:
+        software = ''
+        
+    metadata = {
+        'Creator': software,
+        'Pixels': {
+            'PhysicalSizeX': size_x,
+            'PhysicalSizeXUnit': unit_x,
+            'PhysicalSizeY': size_y,
+            'PhysicalSizeYUnit': unit_y,
+        },
+        'Channel': {'Name': channel_names},
+        
+    }
+
+    max_size = np.max(img_shape)
+    subifds = np.ceil(np.log2(max_size / 1024)).astype(int)
+    
+    # use optimal tile size for disk space
+    tile_size = 16*np.ceil(
+        np.array(img_shape[1:]) / (2**subifds) / 16
+    ).astype(int)
+    options = {
+        'tile': tuple(tile_size)
+    }
+
+    with tifffile.TiffWriter(output_path, bigtiff=True) as tiff_out:
+        tiff_out.write(
+            data=out_img,
+            metadata=metadata,
+            software=software,
+            subifds=subifds,
+            **options
+        )
+        for i in range(subifds):
+            if i == 0:
+                down_2x_img = downsize_img_channels(out_img, is_mask=is_mask)
+            else:
+                down_2x_img = downsize_img_channels(down_2x_img, is_mask=is_mask)
+            tiff_out.write(
+                data=down_2x_img,
+                subfiletype=1,
+                **options
+            )
+
+    out_img = out_img.reshape(img_shape_ori)
+    return
+
+def downsize_channel(img, is_mask):
+    if is_mask:
+        return img[::2, ::2]
+    else:
+        return skimage.transform.downscale_local_mean(img, (2, 2)).astype(img.dtype)
+
+def downsize_img_channels(img, is_mask):
+    return np.array([
+        downsize_channel(c, is_mask=is_mask)
+        for c in img
+    ])