Skip to content

Commit

Permalink
Merge pull request #54 from aditiiyer/testing
Browse files Browse the repository at this point in the history
Corrected output scan/mask coordinates
  • Loading branch information
aditiiyer authored Jun 11, 2024
2 parents 02bc3b0 + dbaa949 commit 4530165
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 74 deletions.
71 changes: 51 additions & 20 deletions cerr/utils/aiPipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,20 @@
import cerr.dataclasses.scan as cerrScn

def createSessionDir(sessionPath, inputDicomPath):
# Create session directory for temporary files
"""
Function to create a directory to write temporary files when deploying AI models.
inputDicomPath
Args:
sessionPath: string for desired location of session directory
inputDicomPath: string for path to DICOM input data. The session directory is assigned a unique name
derived in aprt from the DICOM folder name.
Returns:
modInputPath: string for path to directory containing data input to AI model
modOutputPath: string for path to directory containing data output by AI model
"""

if inputDicomPath[-1] == os.sep:
_, folderNam = os.path.split(inputDicomPath[:-1])
else:
Expand Down Expand Up @@ -38,14 +50,15 @@ def createSessionDir(sessionPath, inputDicomPath):

def getAssocFilteredScanNum(scanNumV,planC):
"""
Function to return index of filtered scan derived from original input scan.
Args:
scanNumV (list): list of scan indices in planC
scanNumV: list for original scan indices in planC
planC: pyCERR's plan container object
Returns:
list: list of filtered scan indices in planC created from input list of scan numbers
filtScanNumV: list for filtered scan indices in planC created from input list
of scan numbers
"""

scanUIDs = [planC.scan[scanNum].scanUID for scanNum in scanNumV]
Expand All @@ -65,7 +78,15 @@ def getAssocFilteredScanNum(scanNumV,planC):

def getAssocWarpedScanNum(scanNumV,planC):
"""
Return warped scan created from input scanNum
Function to return index of deformed scan derived from original input scan.
Args:
scanNumV: list for original scan indices in planC
planC: pyCERR's plan container object
Returns:
warpedScanNumV: list for warped scan indices in planC created from input list
of scan numbers
"""
assocMovScanUIDs = [planC.scan[scanNum].assocMovingScanUID for scanNum in scanNumV]

Expand All @@ -79,11 +100,19 @@ def getAssocWarpedScanNum(scanNumV,planC):
warpedScanNumV = warpedScanNumV[~np.isnan(warpedScanNumV)]
return warpedScanNumV


def getAssocResampledScanNum(scanNumV,planC):
"""
Return resampled scan created from input scanNum
Function to return index of resampled scan derived from original input scan.
Args:
scanNumV: list for original scan indices in planC
planC: pyCERR's plan container object
Returns:
warpedScanNumV: list for resampled scan indices in planC created from input list
of scan numbers
"""

scanUIDs = [planC.scan[scanNum].scanUID for scanNum in scanNumV]
resampScanNumV = np.full(len(scanNumV),np.nan)

Expand All @@ -98,19 +127,21 @@ def getAssocResampledScanNum(scanNumV,planC):
return resampScanNumV

def getScanNumFromIdentifier(idDict,planC,origFlag:bool = False):
'''
Return index of scan with metadata matching user-input identifier(s).
Supported identifiers:
:param idDict: Parameter dictionary with keys specifying identifiers, and values holding expected quantity.
Supported identifiers include: 'imageType', 'seriesDescription', 'scanNum', 'scanType',
'seriesDate' (may be" first" or "last"), 'studyDate' (may be" first" or "last"),
and 'assocStructure' (use structure name to identify associated scans. Set to 'none'
to select scans with no associated structures)
:param planC
----- Optional-----
:param origFlag: Set to True to ignore 'warped', 'resampled' or 'filtered' scans (default:False).
:return: scanNumV : Vector of scan indices matching specified identifier(s).
'''
"""
Function to retrieve index of scan with metadata matching user-input identifier(s).
Args:
idDict: dictionary with keys specifying identifiers, and values holding expected quantity.
Supported identifiers include: 'imageType', 'seriesDescription', 'scanNum', 'scanType',
'seriesDate' (may be" first" or "last"), 'studyDate' (may be" first" or "last"),
and 'assocStructure' (use structure name to identify associated scans. Set to 'none'
to select scans with no associated structures)
planC: pyCERR's plan container object.
origFlag: [optional, default:False] bool for ignoring 'warped', 'resampled' or 'filtered' scans.
Returns:
scanNumV : np.array for scan indices matching specified identifier(s).
"""

# Get no. scans
numScan = len(planC.scan)
Expand Down
56 changes: 40 additions & 16 deletions cerr/utils/imageProc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,28 @@
def resizeScanAndMask(scan3M, mask4M, gridS, outputImgSizeV, method, \
limitsM=None, preserveAspectFlag=False):
"""
Script to resize images and label maps for deep learning.
Args:
scan3M: np.ndarray for 3d input scan
mask4M: np.ndarray for 4d input mask of dimension [nRows x nCols x nSlices x nStructures]
(stack of binary masks representing various structures)
gridS: tuple (xV, yV, zV) for coordinates of input scan/mask
outputImgSizeV: np.array for output image dimensions [nRows, nCols, nSlices]
method: string for method used to resize input scan. Supported values include
'padorcrop3d', 'pad3d', 'unpad3d', 'pad2d', 'unpad2d', 'padslices'
'unpadslices', 'bilinear','bicubic', and 'nearest'.
Note: Masks are resized using 'nearest' for input methods 'bilinear','bicubic', and 'nearest'.
limitsM: [optional, default=None] np.ndarray for extents of bounding box on each
slice. minr = limitsM[slcNum, 0], maxr = limitsM[slcNum, 1],
minc = limitsM[slcNum, 2], maxc = limitsM[slcNum, 3]
preserveAspectFlag: bool for flag to preserve input aspect ratio by padding prior to resizing.
Returns:
scanOut3M: np.ndarray for 3d resized scan
maskOut4M: np.ndarray for 3d resized mask
gridOutS: tuple (xV, yV, zV) for coordinates of output scan/mask
"""

# Get input image size
if scan3M.size != 0:
if scan3M is not None:
origSizeV = [scan3M.shape[0], scan3M.shape[1], scan3M.shape[2]]
else:
origSizeV = [mask4M.shape[0], mask4M.shape[1], mask4M.shape[2]]
Expand Down Expand Up @@ -196,7 +213,7 @@ def resizeScanAndMask(scan3M, mask4M, gridS, outputImgSizeV, method, \

elif methodLower in ['bilinear','bicubic','nearest']:
#3D resizing. TBD: 2D

scanOut3M = None
maskOut4M = None
orderDict = {'bilinear': 1, 'bicubic': 3, 'nearest': 0} #Resizing order

Expand All @@ -216,7 +233,6 @@ def resizeScanAndMask(scan3M, mask4M, gridS, outputImgSizeV, method, \
idx21 = 1 + (padding - scanSize[1]) // 2
idx22 = idx21 + scanSize[1] - 1
padded3M[idx11:idx12+1, idx21:idx22+1, :] = scan3M

scanOut3M = np.empty((outputImgSizeV[0], outputImgSizeV[1], scan3M.shape[2]))
for nSlc in range(padded3M.shape[2]):
scanOut3M[:, :, nSlc] = resize(padded3M[:, :, nSlc],
Expand Down Expand Up @@ -246,33 +262,41 @@ def resizeScanAndMask(scan3M, mask4M, gridS, outputImgSizeV, method, \
idx22 = idx21 + cropDim[1] - 1
maskOut4M = maskResize4M[idx11:idx12+1, idx21:idx22+1, :, :]

#Padded coordinates
xPadV = np.arange(xV[0] - (padding / 2) * voxSizeV[0],
xV[-1] + (padding / 2) * voxSizeV[0], voxSizeV[0])
yPadV = np.arange(yV[0] - (padding / 2) * voxSizeV[1],
yV[-1] + (padding / 2) * voxSizeV[1], voxSizeV[1])
# Rescaled coordinates
scaleFactor = [outputImgSizeV[1] / padSize[1], outputImgSizeV[0] / padSize[0]]
xScaleV = xV * scaleFactor[0]
yScaleV = yV * scaleFactor[1]
scaleFactor = [outputImgSizeV[1] / padSize[1],
outputImgSizeV[0] / padSize[0]]
xScaleV = xPadV * scaleFactor[0]
yScaleV = yPadV * scaleFactor[1]
xOutV = np.linspace(xScaleV.min(), xScaleV.max(), outputImgSizeV[1])
yOutV = np.linspace(yScaleV.min(), yScaleV.max(), outputImgSizeV[0])
gridOutS = (xOutV,yOutV,zV)

else:
# Resize scan
scanOut3M = np.empty((outputImgSizeV[0], outputImgSizeV[1], scan3M.shape[2]))
for nSlc in range(scan3M.shape[2]):
scanOut3M[:, :, nSlc] = resize(scan3M[:, :, nSlc],
(outputImgSizeV[0], outputImgSizeV[1]),
if scan3M is not None:
scanOut3M = np.empty((outputImgSizeV[0], outputImgSizeV[1], scan3M.shape[2]))
for nSlc in range(scan3M.shape[2]):
scanOut3M[:, :, nSlc] = resize(scan3M[:, :, nSlc],
(outputImgSizeV[:-1]),
anti_aliasing=True,
order=orderDict[methodLower])
# Resize mask
if mask4M is not None:
maskOut4M = np.zeros((*outputImgSizeV[0:2], mask4M.shape[2], mask4M.shape[3]))
for nSlc in range(mask4M.shape[2]):
maskOut4M[:, :, nSlc, :] = resize(np.squeeze(mask4M[:, :, nSlc, :]),
outputImgSizeV[0:2],
order = 0, # 'nearest' interpolation
outputImgSizeV[:-1],
order=0, # 'nearest' interpolation
anti_aliasing=False)

# Rescaled coordinates
scaleFactor = [outputImgSizeV[1] / origSizeV[1], outputImgSizeV[0] / origSizeV[0]]
scaleFactor = [outputImgSizeV[1] / origSizeV[1],\
outputImgSizeV[0] / origSizeV[0]]
xScaleV = xV * scaleFactor[0]
yScaleV = yV * scaleFactor[1]
xOutV = np.linspace(xScaleV.min(), xScaleV.max(), outputImgSizeV[1])
Expand All @@ -293,7 +317,7 @@ def resizeScanAndMask(scan3M, mask4M, gridS, outputImgSizeV, method, \
maskOut4M = np.zeros((scan3M.shape[0], scan3M.shape[1], numSlices, numLabels))
maskOut4M[:, :, :origSizeV[2], :] = mask4M

zOutV = np.arange(zV[0], zV[-1]+(zPad)*voxSizeV[2], voxSizeV[2])
zOutV = np.arange(zV[0], zV[-1]+(zPad+1)*voxSizeV[2], voxSizeV[2])
gridOutS = (xV, yV, zOutV)

elif methodLower=='unpadslices':
Expand Down
Loading

0 comments on commit 4530165

Please sign in to comment.