Previous changeset 0:37acf42a824b (2021-03-12) Next changeset 2:96d0d969ebc9 (2022-09-16) |
Commit message:
"planemo upload for repository https://github.com/ohsu-comp-bio/S3segmenter commit c8f72e04db2cc6cc26f0359d5aa3b1a972bc6d53" |
modified:
S3segmenter.py macros.xml s3segmenter.xml |
added:
save_tifffile_pyramid.py |
b |
diff -r 37acf42a824b -r 41e8efe8df43 S3segmenter.py --- a/S3segmenter.py Fri Mar 12 00:18:40 2021 +0000 +++ b/S3segmenter.py Fri Mar 11 23:37:49 2022 +0000 |
[ |
b"@@ -29,6 +29,9 @@\n import datetime\n from joblib import Parallel, delayed\n from rowit import WindowView, crop_with_padding_mask\n+from save_tifffile_pyramid import save_pyramid\n+import subprocess\n+import ome_types\n \n \n def imshowpair(A,B):\n@@ -101,7 +104,7 @@\n padded[padded == 1] = maxima.flatten()\n return padded.astype(np.bool)\n \n-def S3AreaSegmenter(nucleiPM, images, TMAmask, threshold,outputPath):\n+def S3AreaSegmenter(nucleiPM, images, TMAmask, threshold,fileprefix,outputPath):\n nucleiCenters = nucleiPM[:,:,0]\n TMAmask= (nucleiCenters>np.amax(nucleiCenters)*0.8)*TMAmask\n area = []\n@@ -112,17 +115,62 @@\n threshold = threshold_otsu(image_gauss)\n mask=resize(image_gauss>threshold,(images.shape[1],images.shape[2]),order = 0)*TMAmask\n area.append(np.sum(np.sum(mask)))\n- np.savetxt(outputPath + os.path.sep + 'area.csv',(np.transpose(np.asarray(area))),fmt='%10.5f') \n+ os.mk \n+ np.savetxt(outputPath + os.path.sep + fileprefix + '_area.csv',(np.transpose(np.asarray(area))),fmt='%10.5f') \n return TMAmask\n- \n+\n+def getMetadata(path,commit):\n+ with tifffile.TiffFile(path) as tif:\n+ if not tif.ome_metadata:\n+ try:\n+ x_res_tag = tif.pages[0].tags['XResolution'].value\n+ y_res_tag = tif.pages[0].tags['YResolution'].value\n+ physical_size_x = x_res_tag[0] / x_res_tag[1]\n+ physical_size_y = y_res_tag[0] / y_res_tag[1]\n+ except KeyError:\n+ physical_size_x = 1\n+ physical_size_y = 1\n+ metadata_args = dict(\n+ pixel_sizes=(physical_size_y, physical_size_x),\n+ pixel_size_units=('\xc2\xb5m', '\xc2\xb5m'),\n+ software= 's3segmenter v' + commit\n+ )\n+ else: \n+ metadata=ome_types.from_xml(tif.ome_metadata)\n+ metadata = metadata.images[0].pixels\n+ metadata_args = dict(\n+ pixel_sizes=(metadata.physical_size_y,metadata.physical_size_x),\n+ pixel_size_units=('\xc2\xb5m', '\xc2\xb5m'),\n+ software= 's3segmenter v' + commit\n+ )\n+ return metadata_args\n+\n+def S3NucleiBypass(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFilter,nucleiRegion): \n+ foregroundMask = nucleiPM\n+ P = regionprops(foregroundMask, nucleiImage)\n+ prop_keys = ['mean_intensity', 'label','area']\n+ def props_of_keys(prop, keys):\n+ return [prop[k] for k in keys]\n+ \n+ mean_ints, labels, areas = np.array(Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) \n+\t\t\t\t\t\tfor prop in P\n+\t\t\t\t\t\t)\n+\t\t ).T\n+ del P\n+ maxArea = (logSigma[1]**2)*3/4\n+ minArea = (logSigma[0]**2)*3/4\n+ passed = np.logical_and(areas > minArea, areas < maxArea)\n+ \n+ foregroundMask *= np.isin(foregroundMask, labels[passed])\n+ np.greater(foregroundMask, 0, out=foregroundMask)\n+ foregroundMask = label(foregroundMask, connectivity=1).astype(np.int32)\n+ return foregroundMask\n \n def S3NucleiSegmentationWatershed(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFilter,nucleiRegion):\n nucleiContours = nucleiPM[:,:,1]\n nucleiCenters = nucleiPM[:,:,0]\n- \n mask = resize(TMAmask,(nucleiImage.shape[0],nucleiImage.shape[1]),order = 0)>0\n- \n- if nucleiRegion == 'localThreshold':\n+ if nucleiRegion == 'localThreshold' or nucleiRegion == 'localMax':\n Imax = peak_local_max(extrema.h_maxima(255-nucleiContours,logSigma[0]),indices=False)\n Imax = label(Imax).astype(np.int32)\n foregroundMask = watershed(nucleiContours, Imax, watershed_line=True)\n@@ -146,27 +194,8 @@\n foregroundMask *= np.isin(foregroundMask, labels[passed])\n np.greater(foregroundMask, 0, out=foregroundMask)\n foregroundMask = label(foregroundMask, connectivity=1).astype(np.int32)\n- elif nucleiRegion =='bypass':\n- foregroundMask = nucleiPM[:,:,0]\n- P = "..b"efix,'nucleiRing',args.saveFig,args.saveMask)\n- exportMasks(cytoplasmMask,cyto,outputPath,filePrefix,'cytoRing',args.saveFig,args.saveMask)\n- exportMasks(cellMask,cyto,outputPath,filePrefix,'cellRing',args.saveFig,args.saveMask)\n+ exportMasks(nucleiMaskTemp,nucleiCrop,outputPath,filePrefix,'nucleiRing',commit,metadata,args.saveFig,args.saveMask)\n+ exportMasks(cytoplasmMask,cyto,outputPath,filePrefix,'cytoRing',commit,metadata,args.saveFig,args.saveMask)\n+ exportMasks(cellMask,cyto,outputPath,filePrefix,'cellRing',commit,metadata,args.saveFig,args.saveMask)\n \n elif args.segmentCytoplasm == 'ignoreCytoplasm':\n- exportMasks(nucleiMask,nucleiCrop,outputPath,filePrefix,'nuclei')\n+ exportMasks(nucleiMask,nucleiCrop,outputPath,filePrefix,'nuclei',commit,metadata)\n cellMask = nucleiMask\n- exportMasks(nucleiMask,nucleiCrop,outputPath,filePrefix,'cell')\n+ exportMasks(nucleiMask,nucleiCrop,outputPath,filePrefix,'cell',commit,metadata)\n cytoplasmMask = nucleiMask\n \n- \n- if (np.min(args.detectPuncta)>-1):\n- if len(args.detectPuncta) != len(args.punctaSigma):\n- args.punctaSigma = args.punctaSigma[0] * np.ones(len(args.detectPuncta))\n+ detectPuncta = args.detectPuncta\n+ if (np.min(detectPuncta)>0):\n+ detectPuncta[:] = [number - 1 for number in detectPuncta] #convert 1-based indexing to 0-based indexing \n+ if len(detectPuncta) != len(args.punctaSigma):\n+ args.punctaSigma = args.punctaSigma[0] * np.ones(len(detectPuncta))\n \n \n- if len(args.detectPuncta) != len(args.punctaSD):\n- args.punctaSD = args.punctaSD[0] * np.ones(len(args.detectPuncta))\n+ if len(detectPuncta) != len(args.punctaSD):\n+ args.punctaSD = args.punctaSD[0] * np.ones(len(detectPuncta))\n \n counter=0\n- for iPunctaChan in args.detectPuncta:\n+ for iPunctaChan in detectPuncta:\n punctaChan = tifffile.imread(imagePath,key = iPunctaChan)\n punctaChan = punctaChan[int(PMrect[0]):int(PMrect[0]+PMrect[2]), int(PMrect[1]):int(PMrect[1]+PMrect[3])]\n spots=S3punctaDetection(punctaChan,cellMask,args.punctaSigma[counter],args.punctaSD[counter])\n- cellspotmask = nucleiMask#tifffile.imread(maskPath) #REMOVE THIS LATER\n+ cellspotmask = nucleiMask\n P = regionprops(cellspotmask,intensity_image = spots ,cache=False)\n numSpots = []\n for prop in P:\n numSpots.append(np.uint16(np.round(prop.mean_intensity * prop.area)))\n- np.savetxt(outputPath + os.path.sep + 'numSpots_chan' + str(iPunctaChan) +'.csv',(np.transpose(np.asarray(numSpots))),fmt='%10.5f') \n+ np.savetxt(outputPath + os.path.sep + 'numSpots_chan' + str(iPunctaChan+1) +'.csv',(np.transpose(np.asarray(numSpots))),fmt='%10.5f') \n edges = 1-(cellMask>0)\n stacked_img=np.stack((np.uint16((spots+edges)>0)*np.amax(punctaChan),punctaChan),axis=0)\n- tifffile.imsave(outputPath + os.path.sep + filePrefix + os.path.sep + 'punctaChan'+str(iPunctaChan) + 'Outlines.tif',stacked_img)\n- counter=counter+1 \n- #fix bwdistance watershed\n+ \n+ \n+ outputPathPuncta = outputPath + os.path.sep + filePrefix + os.path.sep + 'punctaChan'+str(iPunctaChan+1) + 'Outlines.ome.tif'\n+ \n+ # metadata_args = dict(\n+ # pixel_sizes=(metadata.physical_size_y, metadata.physical_size_x),\n+ # pixel_size_units=('\xc2\xb5m', '\xc2\xb5m'),\n+ # software= 's3segmenter v' + commit\n+ # )\n+ save_pyramid(\n+ stacked_img,\n+ outputPathPuncta,\n+ channel_names=['puncta outlines', 'image channel'],\n+ is_mask=False,\n+ **metadata\n+ ) \n+ \n+ counter=counter+1 \n+\n" |
b |
diff -r 37acf42a824b -r 41e8efe8df43 macros.xml --- a/macros.xml Fri Mar 12 00:18:40 2021 +0000 +++ b/macros.xml Fri Mar 11 23:37:49 2022 +0000 |
b |
@@ -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> |
b |
diff -r 37acf42a824b -r 41e8efe8df43 s3segmenter.xml --- a/s3segmenter.xml Fri Mar 12 00:18:40 2021 +0000 +++ b/s3segmenter.xml Fri Mar 11 23:37:49 2022 +0000 |
b |
@@ -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> |
b |
diff -r 37acf42a824b -r 41e8efe8df43 save_tifffile_pyramid.py --- /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 + ]) |