Skip to content

Commit 529e4dc

Browse files
committed
update transformix to contain padding,resizing,and trimming
1 parent 76d16b2 commit 529e4dc

File tree

2 files changed

+160
-104
lines changed

2 files changed

+160
-104
lines changed

HDIreg/transformix.py

Lines changed: 154 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#Import external modules
1818
from hdiutils.HDIimport import hdi_reader
19+
from hdiutils.HDIexport import hdi_exporter
1920

2021
def GetCropCoords(coords_csv, correction = 80):
2122
"""
@@ -300,22 +301,19 @@ def MultiTransformix(in_im, out_dir, tps):
300301
return res_name
301302

302303

303-
#in_im="/Users/joshuahess/Desktop/Test/new_im_2_processed.nii"
304-
#out_dir="/Users/joshuahess/Desktop/Test"
305-
#tps="/Users/joshuahess/Desktop/Test/TransformParameters.0.txt"
304+
#in_im=r"D:\Josh_Hess\01_10_19_MSI_peak_pick_20191025\Test\new_im_2_processed.nii"
305+
#out_dir=r"D:\Josh_Hess\01_10_19_MSI_peak_pick_20191025\Test"
306+
#tps=r"D:\Josh_Hess\01_10_19_MSI_peak_pick_20191025\Test\TransformParameters.0.txt"
306307
#in_target_size = None
307308
#crops = None
308-
309-
310-
311-
309+
#Transformix(in_im, out_dir, tps, in_target_size, crops)
312310

313311
#Create class structure for transformix implementation
314312
class Transformix():
315313
"""Python class for transformix
316314
"""
317315

318-
def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
316+
def __init__(self, in_im, out_dir, tps, target_size = None, pad = None, trim = None, crops = None, out_ext = ".nii"):
319317
"""
320318
initialize class instance and run transformix with the input parameters
321319
@@ -325,7 +323,7 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
325323
326324
tps: list of paths to transform parameters -- let them be in order!
327325
328-
in_target_size: tuple indicating the target size for any rescaling applied
326+
target_size: tuple indicating the target size for any rescaling applied
329327
to input image prior to transforming
330328
331329
crops: None if no cropping and subsequent transforming to be done.
@@ -345,11 +343,18 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
345343
self.out_dir = Path(out_dir)
346344
self.tps = [Path(t) for t in tps]
347345
self.command = "transformix"
348-
self.baseName = self.in_im.stem
346+
self.baseName = self.in_im.stem.replace(".ome","")
349347
self.ext = "".join(self.in_im.suffixes)
350-
351-
#Load the images to check for dimension number
352-
print('Loading images...')
348+
self.intermediate = False
349+
self.out_ext = out_ext
350+
# Check for input list or none
351+
if target_size!=None:
352+
# convert it to tuple from list (command line parser)
353+
target_size = tuple(target_size)
354+
# Check for input extension or none
355+
if self.out_ext==None:
356+
# convert it to be whatever extension the input image contains as default
357+
self.out_ext = self.ext
353358
#Load images
354359
niiIn = hdi_reader.HDIreader(
355360
path_to_data=in_im,
@@ -359,51 +364,54 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
359364
mask=None,
360365
save_mem=False
361366
)
362-
#Print update
363-
print('Done loading')
364-
365-
# here we will get the extension of the image and will convert it to the nift-1
366-
# format if it is not already in that format. While users can supply their own
367-
# nifti formatted image to the pipeline, this ensures that other file formats
368-
# can be used, although, it creates additionally overhead
369-
# get the extension of the image
370-
# ensure the input to transformix is nifti
371-
if self.ext != ".nii":
372-
# get the shape of the image
373-
shp = len(niiIn.hdi.data.image_shape)
374-
# create new name for the temporary image
375-
tmp_nm = os.path.join(out_dir, next(tempfile._get_candidate_names())+".nii")
376-
# export nifti intermediate
377-
print('Creating nifti-1 intermediate for registration')
378-
# export nifti
379-
if shp > 2:
380-
# Create nifti object -- transpose axes because of the transformation!
381-
nii_im = nib.Nifti1Image(niiIn.hdi.data.image.transpose(1, 0, 2), affine=np.eye(4))
382-
else:
383-
# Create nifti object -- transpose axes because of the transformation!
384-
nii_im = nib.Nifti1Image(niiIn.hdi.data.image.T, affine=np.eye(4))
385-
#Save the nifti image
386-
nib.save(nii_im,str(tmp_nm))
387-
# remove the nifit memory
388-
nii_im = None
389-
# update the image name
390-
print('Using nifti-1 intermediate for registration')
391-
# update the input image
392-
self.in_im = Path(tmp_nm)
393-
394-
#Load the images to check for dimension number
395-
print('Loading images...')
396-
#Load images
397-
niiIn = nib.load(str(self.in_im))
398-
#Print update
399-
print('Done loading')
400-
401367
#Check to see if there is single channel input (grayscale)
402-
if niiIn.ndim == 2:
368+
if len(niiIn.hdi.data.image_shape) > 2:
403369
#Update multichannel class option
370+
self.multichannel = True
371+
# otherwise this is a single channel image
372+
else:
373+
# multichannel is false
404374
self.multichannel = False
405-
#Remove loaded image to clear memory
406-
niiIn = None
375+
376+
#Check to see if there is single channel input (grayscale)
377+
if not self.multichannel:
378+
# here we will get the extension of the image and will convert it to the nift-1
379+
# format if it is not already in that format. While users can supply their own
380+
# nifti formatted image to the pipeline, this ensures that other file formats
381+
# can be used, although, it creates additionally overhead
382+
# here we supply all preprocessing commands that were used to preprocess or morph
383+
# the array size of the input image through the hdiprep workflow. Transformix
384+
# must be run on images with the same size as the elastix registration
385+
if ((self.out_ext!=".nii") or (target_size!=None) or (pad!=None)):
386+
# get the shape of the image
387+
shp = len(niiIn.hdi.data.image_shape)
388+
# create new name for the temporary image
389+
tmp_nm = os.path.join(out_dir, next(tempfile._get_candidate_names())+".nii")
390+
# export nifti intermediate
391+
print('Creating nifti-1 intermediate for registration')
392+
# check for image resizing
393+
if (target_size != None) and (crops==None):
394+
# transform the image
395+
niiIn.hdi.data.image = resize(niiIn.hdi.data.image,target_size)
396+
# check for padding
397+
if pad!=None:
398+
# pad the single-channel
399+
niiIn.hdi.data.image = np.pad(image,[(pad[0], pad[0]), (pad[1], pad[1])],mode='constant')
400+
# Create nifti oject -- transpose axes because of the transformation!
401+
nii_im = nib.Nifti1Image(niiIn.hdi.data.image.T, affine=np.eye(4))
402+
#Save the nifti image
403+
nib.save(nii_im,str(tmp_nm))
404+
# remove the nifit memory
405+
nii_im = None
406+
# update the image name
407+
print('Using nifti-1 intermediate for registration')
408+
# update the input image
409+
self.in_im = Path(tmp_nm)
410+
# update the intermediate flag
411+
self.intermediate = True
412+
#Remove loaded image to clear memory
413+
niiIn = None
414+
407415
#Print update
408416
print('Detected single channel input images...')
409417
#Update the fixed channels
@@ -428,47 +436,70 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
428436
res_name = Path(os.path.join(self.out_dir,"result"+self.in_im.suffix))
429437

430438
#Create a new name
431-
new_name = Path(os.path.join(self.out_dir,self.baseName+'_result'+self.in_im.suffix))
432-
#Get the resulting image to rename (so we don't overwrite results)
433-
res_name.rename(new_name)
434-
435-
436-
#Otherwise there is multichannel input
439+
new_name = Path(os.path.join(self.out_dir,self.baseName+'_result'+self.out_ext))
440+
441+
# check if the output format needs to be switched -- set by the user
442+
if (self.out_ext!=".nii") or (trim!=None):
443+
# use HDIreader for now to parse image and exporter to export
444+
niiIn = hdi_reader.HDIreader(
445+
path_to_data=in_im,
446+
path_to_markers=None,
447+
flatten=False,
448+
subsample=None,
449+
mask=None,
450+
save_mem=False
451+
)
452+
# check the trim
453+
if trim!=None:
454+
# trim the image borders
455+
niiIn.hdi.data.image = niiIn.hdi.data.image[trim:-trim,trim:-trim]
456+
# export new data
457+
hdi_exporter.HDIexporter(niiIn.hdi.data.image,new_name)
458+
else:
459+
# simply rename the file that is already in the nifti format
460+
res_name.rename(new_name)
461+
462+
# Otherwise there is multichannel input. here we run multichannel registration
463+
# and all operations for preprocessing are performed on a per slice basis to
464+
# save disk space (dont have to export the full z stack at once). For now
465+
# all processing steps are tailored for nifti formats. In the future this
466+
# should easily be changed to allow for any data format
437467
else:
438-
#Update multichannel class option
439-
self.multichannel = True
440468
# print update
441469
print('Detected multichannel input')
442470

443-
#Check to see if cropping the resulting image
444-
if crops is None:
445-
446-
#create a temporary directory using the context manager for channel-wise images
471+
# Check to see if cropping the resulting image
472+
if crops==None:
473+
# create a temporary directory using the context manager for channel-wise images
447474
with tempfile.TemporaryDirectory(dir=self.out_dir) as tmpdirname:
448-
#Print update
475+
# Print update
449476
print('Created temporary directory', tmpdirname)
450-
#Read the image
451-
niiIn = niiIn.get_fdata()
452-
453-
#Iterate through the channels
454-
for i in range(niiIn.shape[2]):
455-
#Print update
477+
# Iterate through the channels
478+
for i in range(niiIn.hdi.data.image.shape[2]):
479+
# Print update
456480
print('Working on slice '+str(i))
457-
#Create a name for a temporary image
458-
im_name = Path(os.path.join(tmpdirname,self.in_im.stem+str(i)+self.in_im.suffix))
459-
#Update the list of names for image channels
481+
# Create a name for a temporary image
482+
im_name = Path(os.path.join(tmpdirname,self.in_im.stem+str(i)+".nii"))
483+
# Update the list of names for image channels
460484
self.in_channels.append(im_name)
485+
# set a temporary channel to work with throughout the data prep stage
486+
slice_in = niiIn.hdi.data.image[:,:,i]
461487

462-
#Check to see if the path exists
488+
# Check to see if the path exists
463489
if not im_name.is_file():
464-
465-
#Check to see if there is a target size for the image
466-
if in_target_size is not None:
467-
#Resize the image
468-
niiIn = resize(niiIn,in_target_size)
469-
470-
#Create a nifti image from this slice
471-
nii_im = nib.Nifti1Image(niiIn[:,:,i], affine=np.eye(4))
490+
# Check to see if there is a target size for the image
491+
if target_size!=None:
492+
# Resize the image
493+
slice_in = resize(slice_in,target_size)
494+
# check for padding
495+
if pad!=None:
496+
# pad the single-channel
497+
slice_in = np.pad(slice_in,[(pad[0], pad[0]), (pad[1], pad[1])],mode='constant')
498+
499+
# Create a nifti image from this slice
500+
nii_im = nib.Nifti1Image(slice_in.T, affine=np.eye(4))
501+
# remove memory
502+
slice_in = None
472503
#Save the nifti image
473504
nib.save(nii_im,str(im_name))
474505
#Remove the nifti slice to clear memory
@@ -480,7 +511,6 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
480511
res_name = MultiTransformix(in_im = im_name, out_dir = tmpdirname, tps = self.tps)
481512

482513
else:
483-
484514
#Create a temporary command to be sent to the shell
485515
tmp_command = self.command + ' -in ' + str(im_name) + ' -out ' + str(tmpdirname)
486516
#Add full tissue transform paramaeters
@@ -489,10 +519,10 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
489519
RunTransformix(tmp_command)
490520

491521
#Get a temporary result name for the output of transformix (assumes nifti for now)
492-
res_name = Path(os.path.join(tmpdirname,"result"+self.in_im.suffix))
522+
res_name = Path(os.path.join(tmpdirname,"result"+".nii"))
493523

494524
#Create a new name
495-
new_name = Path(os.path.join(tmpdirname,self.in_im.stem+str(i)+'_result'+self.in_im.suffix))
525+
new_name = Path(os.path.join(tmpdirname,self.in_im.stem+str(i)+'_result'+".nii"))
496526
#Get the resulting image to rename (so we don't overwrite results)
497527
res_name.rename(new_name)
498528
#Update the list of output channel names
@@ -503,13 +533,33 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
503533
#Concatenate the output channels into a single result file in the output directory
504534
full_result = nib.concat_images([str(i) for i in self.out_channels])
505535
#create a filename for the full nifti results
506-
full_name = Path(os.path.join(self.out_dir,self.in_im.stem+"_result.nii"))
507-
#Write the results to a nifti file
508-
nib.save(full_result,str(full_name))
509-
510-
#Cropping is true
536+
full_name = Path(os.path.join(self.out_dir,self.baseName+"_result"+self.out_ext))
537+
538+
# check if the output format needs to be switched -- set by the user
539+
if (self.out_ext!=".nii") or (trim!=None):
540+
# check the trim
541+
if trim!=None:
542+
# trim the image borders
543+
full_result = full_result.get_fdata()[trim:-trim,trim:-trim,:]
544+
# export new data
545+
hdi_exporter.HDIexporter(full_result.transpose(1,0,2),full_name)
546+
else:
547+
# export the non trimmed image
548+
hdi_exporter.HDIexporter(full_result.get_fdata().transpose(1,0,2),full_name)
549+
else:
550+
# export new data using the aggregated nifti objects
551+
# doesnt need to be formally read in because it is memory
552+
# mapped to the full_result object
553+
hdi_exporter.HDIexporter(full_result.get_fdata().transpose(1,0,2),full_name)
554+
555+
# Cropping is true
556+
# for now this is not supported in the nextflow version of miaaim.
557+
# TODO: add this to nextflow. This can be rin python and strings
558+
# together multiple transformations from images and crops within
559+
# those images so that very large arrays do not have to be fully
560+
# exported (e.g. for MSI data that contains thousands of channels
561+
# and low resolution on full tissues)
511562
else:
512-
513563
#Read the image
514564
niiIn = niiIn.get_fdata()
515565

@@ -547,10 +597,10 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
547597
if not im_name.is_file():
548598

549599
#Check to see if there is a target size for the image
550-
if in_target_size != None:
600+
if target_size != None:
551601
#print("Got the resize")
552602
#Create a nifti image from this slice
553-
nii_im = nib.Nifti1Image(resize(niiIn[:,:,i],in_target_size), affine=np.eye(4))
603+
nii_im = nib.Nifti1Image(resize(niiIn[:,:,i],target_size), affine=np.eye(4))
554604
#print(" resized")
555605
#No resize
556606
else:
@@ -636,19 +686,21 @@ def __init__(self, in_im, out_dir, tps, in_target_size = None, crops = None):
636686
pads = pars['fixed_pad']
637687
#Extract only the needed region
638688
roi_results = roi_results.get_fdata()[pads:-pads,pads:-pads,:]
639-
#Create nifti object
640-
roi_results = nib.Nifti1Image(roi_results[:,:,:], affine=np.eye(4))
641689
#create a filename for the full nifti results
642-
roi_name = Path(os.path.join(str(self.out_dir),str(roi)+"_result.nii"))
643-
#Write the results to a nifti file
644-
nib.save(roi_results,str(roi_name))
690+
roi_name = Path(os.path.join(str(self.out_dir),str(roi)+"_result"+self.out_ext))
691+
# use the exported from hdiutils
692+
hdi_exporter.HDIexporter(full_result.get_fdata().transpose(1,0,2),roi_name)
645693

646694
#Remove roi results from memory
647695
roi_results = None
648696

649697
#Now delete the temporary folder stored for the single channel ROI results
650698
shutil.rmtree(tmpfolds[roi], ignore_errors=True)
651699

700+
# remove the temporary image if there was a nifti-1 intermediate
701+
if self.intermediate:
702+
# remove using pathlib
703+
self.in_im.unlink()
652704
#Print update
653705
print("Finished")
654706

parse_input.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@ def ParseCommandTransformix():
3737
parser.add_argument('--in_im')
3838
parser.add_argument('--out_dir')
3939
parser.add_argument('--tps', nargs='*')
40-
parser.add_argument('--in_target_size')
40+
parser.add_argument('--target_size', type=int, nargs='*')
41+
parser.add_argument('--pad')
42+
parser.add_argument('--trim', type=int)
4143
parser.add_argument('--crops')
44+
parser.add_argument('--out_ext')
4245
args = parser.parse_args()
4346
#Create a dictionary object to pass to the next function
4447
dict = {'in_im': args.in_im, 'out_dir': args.out_dir, 'tps': args.tps,\
45-
'in_target_size': args.in_target_size, 'crops': args.crops}
48+
'target_size': args.target_size, 'pad': args.pad, 'trim': args.trim,\
49+
'crops': args.crops, 'out_ext': args.out_ext}
4650
#Print the dictionary object
4751
print(dict)
4852
#Return the dictionary

0 commit comments

Comments
 (0)