Skip to content

Instantly share code, notes, and snippets.

@cudmore
Last active May 28, 2020 19:09
Show Gist options
  • Save cudmore/bbe14996485ad8d72c307bc020c2f0c8 to your computer and use it in GitHub Desktop.
Save cudmore/bbe14996485ad8d72c307bc020c2f0c8 to your computer and use it in GitHub Desktop.
Sample code to use Dask to view a tiling of .tif files. See comment for usage.
"""
Author: Robert Cudmore
Date: 20200528
Purpose:
Open a Napari viewer for a grid of _ch1.tif and _ch2.tif files
This is using dask arrays and blocks
Command Line Usage
Modify parameters in __main__ block and run with
python myDaskNApari.py
Using in a custon script
import myDaskNapari
myGridParams = myDaskNapari.getDaskGridDict()
myGridParams['path'] = '/Users/cudmore/box/data/nathan/20200518'
myGridParams['prefixStr'] = '20200518__A01_G001_'
myGridParams['channelList'] = [1, 2]
myGridParams['commonShape'] = (64,512,512)
myGridParams['commonVoxelSize'] = (1, 0.621480865, 0.621480865)
myGridParams['trimPercent'] = 15
myGridParams['trimPixels'] = None # calculated
myGridParams['nRow'] = 8
myGridParams['nCol'] = 6
myDaskNapari.openDaskNapari(myGridParams)
"""
import os, sys, math, json
from collections import OrderedDict
import numpy as np
import tifffile
import dask
import dask.array as da
import napari
def myFileRead(filename,commonShape, trimPixels):
"""
File loading triggered from dask from_delayed and delayed
handle missing files in dask array by returning correct shape
"""
if os.path.isfile(filename):
stackData = tifffile.imread(filename)
else:
stackData = np.zeros(commonShape, dtype=np.uint8)
if trimPixels is not None:
thisHeight = stackData.shape[1] - trimPixels
thisWidth = stackData.shape[2] - trimPixels
stackData = stackData[:, 0:thisHeight, 0:thisWidth]
return stackData
def myGetBlock(path, prefixStr, finalPrefixStr, commonShape, trimPercent, trimPixels, nRow, nCol, channel):
"""
Get a dask block representing a tile/grid of .tif files
Assuming acquisition follows a snake pattern, for example
Parameters:
path: path to raw folder with _ch1 and _ch2 .tif files
like '/Users/cudmore/box/data/nathan/20200518'
prefixStr:
like '20200518__A01_G001_'
finalPrefixStr: used for computed analysis in analysis2/
like 'finalMask'
commonShape: tuple of (slices, x pixels, yPixels)
like (64,512,512)
trimPercent: Integer of percent overlap
trimPixels: Number of pixels to remove in lower/right of x/y to remove grid overlap
Pass None to calculate from trimPercent
nRow, nCol: number of rows and columns in the grid
channel: channel number, either 1 or 2
Returns:
A dask block of (nRow, nCol)
A dask block is really a numpy ndarray of shape:
(commonShape[0], commonShape[1]*nRow, commonShape[2]*nCol)
"""
if finalPrefixStr:
finalPrefixStr = '_' + finalPrefixStr
if channel == 1:
postfixStr = '_ch1' + finalPrefixStr + '.tif'
elif channel == 2:
postfixStr = '_ch2' + finalPrefixStr + '.tif'
elif channel == 3:
postfixStr = '_ch3' + finalPrefixStr + '.tif'
else:
print('error: myGetBlock() got bad channel:', channel)
common_dtype = np.uint8 # assuming all stack are 8-bit
# trip bottom/right (e.g. x/y) of common stack shape
if trimPixels is None:
trimPixels = math.floor( trimPercent * commonShape[1] / 100 )
trimPixels = math.floor(trimPixels / 2)
if trimPixels > 0:
commonShape = (commonShape[0], commonShape[1]-trimPixels, commonShape[2]-trimPixels)
# make nRow X nCol grid of integers
tmpIntegerGrid = np.arange(1,nRow*nCol+1) # Values are generated within the half-open interval [start, stop)
tmpIntegerGrid = np.reshape(tmpIntegerGrid, (nRow, nCol))
# reverse numbers in every other row (to replicate 'snake' image acquisition)
integerGrid = tmpIntegerGrid.copy()
integerGrid[1::2] = tmpIntegerGrid[1::2,::-1]
# make a list of file names following order of snake pattern in integerGrid
filenames = [prefixStr + str(x).zfill(4) + postfixStr for x in integerGrid.ravel()]
filenames = [os.path.join(path, x) for x in filenames]
# check that all files exist, display will fail when loading file that does not exist
for idx, file in enumerate(filenames):
if not os.path.isfile(file):
print('error: myGetBlock() did not find file:', file)
#lazy_arrays = [dask.delayed(tifffile.imread)(fn) for fn in filenames]
lazy_arrays = [dask.delayed(myFileRead)(fn, commonShape, trimPixels) for fn in filenames]
lazy_arrays = [da.from_delayed(x, shape=commonShape, dtype=common_dtype)
for x in lazy_arrays]
# reshape 1d list into list of lists (nCol items list per nRow lists)
lazy_arrays = [lazy_arrays[i:i+nCol] for i in range(0, len(lazy_arrays), nCol)]
#
# make a block
x = da.block(lazy_arrays)
return x
def getDaskGridDict():
"""
Used by external scripts
"""
myGridParams = OrderedDict()
# file info
myGridParams['path'] = ''
myGridParams['prefixStr'] = ''
myGridParams['finalPostfixStr'] = ''
# stack info
myGridParams['channelList'] = [] # e.g. [1,2] or [1] or [2]
myGridParams['commonShape'] = None # pixels (slices, x, y)
myGridParams['commonVoxelSize'] = None # voxel size in um/pixel (slices, x, y)
# grid info
# percent of overlap between tiles, final trim pixels is
myGridParams['trimPercent'] = 15
myGridParams['trimPixels'] = None
# size of the grid
myGridParams['nRow'] = None
myGridParams['nCol'] = None
return myGridParams
def openDaskNapari(myGridParams):
"""
Given grid parameters corresponding to _ch1 and _ch2 stacks in folder (path)
1) construct a dask block with all files
2) open a Napari viewer
"""
print('myDaskNapari()')
print(json.dumps(myGridParams, indent=4))
# extract information from myGridParams
path = myGridParams['path']
prefixStr = myGridParams['prefixStr']
finalPostfixStr = myGridParams['finalPostfixStr'] # always '' for raw data
channelList = myGridParams['channelList']
commonShape = myGridParams['commonShape']
commonVoxelSize = myGridParams['commonVoxelSize']
trimPercent = myGridParams['trimPercent']
trimPixels = myGridParams['trimPixels']
nRow = myGridParams['nRow']
nCol = myGridParams['nCol']
rawBlockList = []
for channel in channelList:
rawBlock = myGetBlock(path, prefixStr, finalPostfixStr, commonShape, trimPercent, trimPixels, nRow, nCol, channel=channel)
rawBlockList.append(rawBlock)
# for napari
tmpPath, windowTitle = os.path.split(path)
scale = commonVoxelSize
#
# napari
with napari.gui_qt():
viewer = napari.Viewer(title='dask: ' + windowTitle)
for idx, rawBlock in enumerate(rawBlockList):
channelIdx = channelList[idx]
if channelIdx == 1:
color = 'green'
elif channelIdx == 2:
color = 'red'
minContrast = 0
maxContrast = 255
name = 'ch' + str(channelIdx) + ' raw'
myImageLayer = viewer.add_image(rawBlock, scale=scale, colormap=color,
contrast_limits=(minContrast, maxContrast), opacity=0.6, visible=True,
name=name)
if __name__ == '__main__':
#
# specify parameters for a given folder with a grid
# file info
path = '/Users/cudmore/box/data/nathan/20200518'
prefixStr = '20200518__A01_G001_'
# stack info
channelList = [1, 2]
commonShape = (64,512,512)
commonVoxelSize = (1, 0.621480865, 0.621480865)
# grid info
trimPercent = 15
nRow = 8
nCol = 6
#
# main
trimPixels = None
#trimPixels = math.floor( trimPercent * commonShape[1] / 100 )
#trimPixels = math.floor(trimPixels / 2)
myGridParams = OrderedDict()
myGridParams['path'] = path
myGridParams['prefixStr'] = prefixStr
myGridParams['finalPostfixStr'] = '' #finalPostfixStr # always empty for raw data
myGridParams['channelList'] = channelList
myGridParams['commonShape'] = commonShape
myGridParams['commonVoxelSize'] = commonVoxelSize
myGridParams['trimPercent'] = trimPercent
myGridParams['trimPixels'] = trimPixels
myGridParams['nRow'] = nRow
myGridParams['nCol'] = nCol
openDaskNapari(myGridParams)
import myDaskNapari
myGridParams = myDaskNapari.getDaskGridDict()
myGridParams['path'] = '/Users/cudmore/box/data/nathan/20200518'
myGridParams['prefixStr'] = '20200518__A01_G001_'
myGridParams['channelList'] = [1, 2]
myGridParams['commonShape'] = (64,512,512)
myGridParams['commonVoxelSize'] = (1, 0.621480865, 0.621480865)
myGridParams['trimPercent'] = 15
myGridParams['trimPixels'] = None # calculated
myGridParams['nRow'] = 8
myGridParams['nCol'] = 6
myDaskNapari.openDaskNapari(myGridParams)
"""
This (17,7) grid is SUPER SLOW on my home machine, thus I tweaked it to (12,7)
"""
import myDaskNapari
myGridParams = myDaskNapari.getDaskGridDict()
myGridParams['path'] = '/Users/cudmore/box/data/nathan/20200519'
myGridParams['prefixStr'] = '20200519__A01_G001_'
myGridParams['channelList'] = [1, 2]
myGridParams['commonShape'] = (62,512,512)
myGridParams['commonVoxelSize'] = (1, 0.621480865, 0.621480865)
myGridParams['trimPercent'] = 15
myGridParams['trimPixels'] = None # calculated
myGridParams['nRow'] = 12 #17
myGridParams['nCol'] = 7
myDaskNapari.openDaskNapari(myGridParams)
@cudmore
Copy link
Author

cudmore commented May 28, 2020

Download all three files (using provided names), tweak parameters in myDaskNapariScript.py to point to a local hard-drive folder with _ch1.tif and _ch2.tif.

Make sure path, prefixStr, stack shape (commonShape), voxel size (commonVoxelSize), and nRow and nCol are correct.

Run in a virtual environment

python -m venv dask_env
source dask_env/bin/activate
pip install tifffile
pip install numpy
pip install dask
pip install napari
python myDaskNapariScript.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment