"""Functions for FIJI and alignment.
There are different alignment procedures for FIJI for Linear Stack Alignment with Sift
as well as bUnwarpJ alignemnt. These function build the FIJI macro to be run
in FIJI and return a stack of aligned images.
Author:
Tim Cote, ANL, Fall 2019.
"""
import os
from os import path
from util import join, flatten_order_list, check_setup, read_fls, pull_image_files
from typing import Any, Dict, List, Optional, Tuple, Union
# ============================================================= #
# Declare Original Types #
# ============================================================= #
Check_Setup = Tuple[bool, Optional[str], Optional[str], Optional[List[str]], Optional[List[str]]]
Pre_LS_Align = Tuple[str, Tuple[List[List[str]], Optional[str], Optional[List[str]]]]
# ============================================================= #
# Path manipulation and converting strings into macros #
# ============================================================= #
[docs]def fijify_macro(unready_macro: str) -> str:
"""Converts macro into FIJI format and returns macro.
The Fiji format uses ';' as end lines, and sometimes contains
additional whitespace. This function accounts for that, edits
the macro string, and returns it in the correct format.
Args:
unready_macro: The un-formatted FIJI macro.
Returns:
fiji_ready_macro: The correctly formatted FIJI macro.
"""
fiji_ready_macro = ''
for line in unready_macro.splitlines():
# Make strings without ';' appear on same line.
# Remove excess whitespace
line = line.strip()
whitespace = ''
if ';' in line:
whitespace = '\n'
elif line and not line.isspace():
whitespace = ' '
fiji_ready_macro = join([fiji_ready_macro, line, whitespace])
return fiji_ready_macro
# ============================================================= #
# Fiji Pre-Alignment Protocols #
# ============================================================= #
[docs]def pre_ls_alignment(reference: str, check_sift: bool, path1: str, path2: str,
fls_files: List[str], tfs_value: str, fls_value: str) -> Pre_LS_Align:
""" Pre-alignment file manipulations for linear stack alignment with SIFT.
Check setup, get reference image to align to, and get all image filenames.
Args:
reference: The type of reference to dictate alignment.
Options: 'tfs', 'unflip', 'flip'
check_sift (bool): Option for checking SIFT alignment.
path1: The first unflip/flip/single path/directory.
path2: The first unflip/flip/single path/directory.
fls_files: A list of the FLS filenames.
tfs_value: The through-focal series option.
Options: Unflip/FLip, Single
fls_value: The FLS option.
Options: One, Two
Returns:
vals: A tuple of the reference file as well as all other filenames.
- vals[0]: The reference filename.
- vals[1][0]: 2D list of all files
- vals[1][1]: 2D list of ordered image files for path1
- vals[1][2]: 2D list of ordered image files for path2
"""
# Check setup of datafolder
files1, files2 = read_fls(path1, path2, fls_files, tfs_value, fls_value, check_sift)
# Get reference image to align to.
ref = ''
if reference == 'tfs' or reference == 'unflip':
ref_list = files1[1].pop()
ref = join([path1, ref_list], '/')
elif reference == 'flip':
ref_list = files2[1].pop()
ref = join([path2, ref_list], '/')
all_files = [files1, files2]
vals = ref, (all_files, files1, files2)
return vals
[docs]def get_shift_rot_macro(transformation: Tuple[float, float, float, bool],
stack: bool = False) -> str:
""" Creates macro for applying shifts and rotations for images .
Args:
transformation: Tuple of transformation values.
stack: Option of whether image is a stack or single image
Returns:
apply_transform: The macro for shift and rotation transformations
"""
rot, x_shift, y_shift, horizontal = transformation
apply_rotation, apply_translation, apply_hflip = '', '', ''
run_flip_stack = ''
run_stack = ''
if stack:
run_flip_stack = ', "stack"'
run_stack = ' stack'
if horizontal:
apply_hflip = f'run("Flip Horizontally"{run_flip_stack});'
if rot != 0:
apply_rotation = f'''
run("Rotate... ", "angle={-rot} grid=1 interpolation=Bilinear{run_stack}");
'''
if x_shift != 0 or y_shift != 0:
y_shift = -y_shift
apply_translation = f'''
run("Translate...", "x={x_shift} y={y_shift} interpolation=None{run_stack}");
'''
apply_transform = f'''{apply_hflip}
{apply_rotation}
{apply_translation}'''
return apply_transform
[docs]def set_shortnames(files: List[List[str]],
place: Tuple[int, int]) -> Tuple[List[str], str]:
"""Return the filenames along with shortened names for
FIJI window.
Args:
files: List of list of image filenames.
place: The indices to be checking in the 'files' list.
Returns:
List of image filenames and shortnames
- fnames: List of image filenames.
- short: The shortened name for the files to be aligning
- first character refers to flip/unlip,
- last character to under/in/over-focus.
"""
# Set list of filenames based off orientation and focus.
# Create shortnames for ImageJ windows.
if place == (0, 0):
fnames, short = files[0][0], 'u_-'
elif place == (0, 1):
fnames, short = files[0][1], 'u_0'
elif place == (0, 2):
fnames, short = files[0][2], 'u_+'
elif place == (1, 0):
fnames, short = files[1][0], 'f_-'
elif place == (1, 1):
fnames, short = files[1][1], 'f_0'
elif place == (1, 2):
fnames, short = files[1][2], 'f_+'
return fnames, short
[docs]def set_default_sift() -> Dict[str, Any]:
"""Set the default sift parameters.
Default SIFT parameters
{'igb': 1.6,
'spso': 3,
'min_im': 48,
'max_im': 1200,
'fds': 4,
'fdob': 8,
'cnc': 0.95,
'max_align_err': 5,
'inlier_rat': 0.05,
'exp_transf': 'Affine',
'interpolate': True}
Returns:
sift_params:All necessary Linear Stack Align with SIFT parameters.
"""
sift_params = {'igb': 1.6,
'spso': 3,
'min_im': 48,
'max_im': 1200,
'fds': 4,
'fdob': 8,
'cnc': 0.95,
'max_align_err': 5,
'inlier_rat': 0.05,
'min_num_inls': 7,
'exp_transf': 'Affine',
'interpolate': True,
'filter_param': True}
return sift_params
[docs]def set_default_bUnwarp() -> Dict[str, Any]:
"""Create the default bUnwarpJ parameters.
Default bUnwarpJ parameters
{'reg_mode': 'Fast',
'img_sub_factor': 0,
'init_def': 'Very Coarse',
'final_def': 'Very Fine',
'div_weight': 0.1,
'curl_weight': 0.1,
'landmark_weight': 1.0,
'img_weight': 1.0,
'cons_weight': 10,
'stop_thresh': 0.01}
Returns:
bUnwarp_params:All necessary bUnwarpJ parameters.
"""
bUnwarp_params = {'reg_mode': 'Fast',
'img_sub_factor': 0,
'init_def': 'Very Coarse',
'final_def': 'Very Fine',
'div_weight': 0.1,
'curl_weight': 0.1,
'landmark_weight': 1.0,
'img_weight': 1.0,
'cons_weight': 10,
'stop_thresh': 0.01}
return bUnwarp_params
[docs]def check_image_flip(window: int, path1: str,
path2: str, ref: str) -> Tuple[bool, str]:
"""Check if image requires a horizontal flip.
Args:
window: The number of the window being looked at
path1: The 1st pathname for the orientation directory,
depending on unflip/flip reference.
path2: The 2nd pathname for the orientation directory,
depending on unflip/flip reference.
ref: The selected reference image of 'unflip' or 'flip'.
Returns:
Boolean value for if image needs to be flipped and the pathname
of that image.
"""
# If 'unflip' is reference, flip the 'flipped' images.
# If 'flip' is reference, flip the 'unflipped' images.
# From setup -> windows 1-3: unflipped, windows 4-6: flipped
flip, path = None, None
if window <= 3:
path = path1
if 'unflip' in ref:
flip = False
elif 'flip' in ref:
flip = True
elif window > 3:
path = path2
if 'unflip' in ref:
flip = True
elif 'flip' in ref:
flip = False
return flip, path
[docs]def determine_window_focus(window: int) -> Tuple[Tuple[int, int], bool]:
"""Determine if image window is underfocused, infocus, or overfocused.
Args:
window: The number of the window.
- images [2, 3, 5, 6] in/overfocused (+)
- images [1, 4] underfocus (-)
Returns:
Tuple of place in file list to choose focus during
macro creation later and truth value for whether
image is in/overfocus (True) or underfocus (False).
"""
pos = None
if (window - 1) % 3 > 0:
pos = True
elif (window - 1) % 3 == 0:
pos = False
orientation_num = (window - 1) // 3
focus_num = (window - 1) % 3
place = (orientation_num, focus_num)
return place, pos
# ============================================================= #
# Fiji Alignment Protocols #
# ============================================================= #
[docs]def ls_macro(sift_params: Dict[str, Any]) -> str:
"""Create macro for running SIFT alignment. Read 'feature
extraction' on ImageJ for more information.
Args:
sift_params: Dictionary of sift parameter values.
Returns:
marco: The macro for running the linear stack alignment
with SIFT.
"""
s_p = sift_params
interpolate = ''
if s_p['interpolate']:
interpolate = 'interpolate'
macro = f'''
run("Linear Stack Alignment with SIFT",
"initial_gaussian_blur={s_p['igb']}
steps_per_scale_octave={s_p['spso']}
minimum_image_size={s_p['min_im']}
maximum_image_size={s_p['max_im']}
feature_descriptor_size={s_p['fds']}
feature_descriptor_orientation_bins={s_p['fdob']}
closest/next_closest_ratio={s_p['cnc']}
maximal_alignment_error={s_p['max_align_err']}
inlier_ratio={s_p['inlier_rat']}
expected_transformation={s_p['exp_transf']}
{interpolate}");
'''
return macro
[docs]def ls_alignment(path1: str, path2: str, sift_params: Dict[str, Any],
transform_params: Tuple[float, float, float, bool],
ref: str, all_files: List[List[str]]) -> Tuple[List[str], List[str]]:
"""Create ImageJ macros for each defocus and orientation other than infocus.
Args:
path1: The first unflip/flip path/directory.
path2: The first unflip/flip path/directory.
sift_params: The Linear SIFT params.
transform_params: The shifting and rotation transformation params.
ref: The reference image for flip/unflip.
all_files: All ordered filenames in under
[[underfocus], [infocus] [overfocus]]
Returns:
Tuple of list of both macros and shortnames.
"""
# Set sift param and image transformation defaults
if not sift_params:
sift_params = set_default_sift()
if not transform_params:
transform_params = (0, 0, 0, False)
# Initialize parameters
shortnames, macros = [], []
window = 1
while window <= 6:
# Determine if the image window requires horizontal flip.
flip, path = check_image_flip(window, path1, path2, ref)
# Determine image window's focus
place, pos = determine_window_focus(window)
# Write alignment macro and append to list. Write shortnames.
macro, short = write_ls_macro(path, ref, all_files, window, place, flip=flip,
pos=pos, sift_params=sift_params,
transform_params=transform_params)
macros.append(macro)
shortnames.append(short)
window += 1
return macros, shortnames
[docs]def single_ls_alignment(sift_params: Dict[str, Any], files: List[List[str]],
path: str, param_test: bool = False,
ref: Optional[str] = None) -> Tuple[List[str], List[str]]:
"""Create ImageJ macros for each defocus and orientation
other than the reference infocus image for a single stack.
Args:
sift_params: The Linear SIFT params.
files: All ordered filenames in under [[underfocus], [infocus] [overfocus]]
path: The single path/directory.
param_test: Boolean for whether LS alignment is being checked
ref: The reference filename.
Returns:
Tuple of list of both macros and shortnames.
"""
# Set sift param and image transformation defaults
if not sift_params:
sift_params = set_default_sift()
# Initialize parameters
if ref is None:
ref = files[1][0]
if param_test:
under, over = files[0], files[2]
else:
under, over = files[0][:], files[2][:]
shortnames, macros = [], []
window = 1
for filenames in [under, over]:
# Write single alignment macro and append to list. Write shortnames.
macro, short = write_single_ls_macro(ref, path, filenames, window, sift_params)
macros.append(macro)
shortnames.append(short)
window += 1
return macros, shortnames
[docs]def write_ls_macro(orient_path: str, ref: str, files: List[List[str]],
window: int, place: Tuple[int, int], flip: bool,
pos: bool, sift_params: Dict[str, Any],
transform_params: Tuple[float, float, float, bool]) -> Tuple[str, str]:
"""Create the linear stack alignment with SIFT (LS) macro for
specific orientation, sign, and focus.
Takes the orientation of flip/unflip/tfs and focus level of
overfocus/underfocus/infocus parameters to write the specific
LS macro.
Args:
orient_path: The orientation path (flip, unflip).
ref: The reference infocus image filename.
files: List of list of image filenames.
window: Placeholder for name of new stack being made for bookkeeping
when combining all stacks later.
place: The indices to be checking in the 'files' list.
flip: Option for whether the files are flipped or not.
pos: Option for whether the image is under or overfocus to reverse
the image stack.
sift_params: Dictionary of sift parameter values.
transform_params: Tuple of transformation parameters.
Returns:
Macro for specific orientation and focus and its shortname.
"""
# Grab filenames and create shortened name for window.
filenames, shortname = set_shortnames(files, place)
if not filenames:
return '', shortname
# Create LS macro and apply transformations to images.
sift = ls_macro(sift_params)
num_files, open_files = apply_transform(flip, orient_path, filenames, transform_params)
# Trim excess images and reverse stack if necessary. Exceptions are if only 1 other
delete, reverse = trim_and_reverse_stacks(filenames, shortname, ref, pos)
# Write macro
macro = f'''open("{ref}");
{open_files}
run("Images to Stack", "name={window} title=[] use");
{sift}
selectWindow("{window}");
close();
selectWindow("Aligned {num_files} of {num_files}");
rename("{shortname}");
{delete}
{reverse}
'''
return macro, shortname
[docs]def write_single_ls_macro(ref: str, path: str, files: List[List[str]],
window: int, sift_params: Dict[str, Any]) -> Tuple[str, str]:
"""Create the linear stack alignment with SIFT (LS) macro for
specific orientation, sign, and focus.
Similar to write_ls_macro but for a single through focal series (tfs).
Args:
ref: The reference infocus image filename.
path: The orientation path (tfs).
files: List of list of image filenames.
window: Placeholder for name of new stack being made for bookkeeping
when combining all stacks later.
sift_params: Dictionary of sift parameter values.
Returns:
Macro for specific orientation and focus and its shortname.
"""
# Grab filenames and create shortened name for window.
if window == 1:
shortname = "-under-"
elif window == 2:
shortname = "+over+"
# Create LS macro
sift = ls_macro(sift_params)
# Trim excess images and reverse stack if necessary. Exceptions are if only 1 other
delete, reverse = trim_and_reverse_single_stack(files, shortname)
num_files = len(files) + 1
open_files = "" #apply_transform
for file in files:
file = join([path, file], '/')
apply_transform = f'''open("{file}");
'''
open_files = join([open_files, apply_transform], '\n')
# Write macro
ref = join([path, ref], '/')
macro = f'''open("{ref}");
{open_files}
run("Images to Stack", "name={window} title=[] use");
{sift}
selectWindow("{window}");
close();
selectWindow("Aligned {num_files} of {num_files}");
rename("{shortname}");
{delete}
{reverse}
'''
return macro, shortname
[docs]def extract_SIFT_landmarks_macro(src_img: str, target_img: str,
SIFT_params: Optional[Dict[str, Any]]) -> str:
""" Extracts SIFT features for bUnwarpJ alignment.
Args:
src_img: The infocus image to transform.
target_img: The infocus image to align to.
SIFT_params: Dictionary of bunwarpJ parameters.
Returns:
The bUnwarpJ macro for SIFT feature extraction for bUnwarpJ alignment.
"""
# Grab all SIFT parameters
if not SIFT_params:
SIFT_params = set_default_sift()
if SIFT_params['filter_param']:
filter_p = 'filter'
SIFT_macro = f"""
run("Extract SIFT Correspondences",
"source_image={src_img}
target_image={target_img}
initial_gaussian_blur={SIFT_params['igb']}
steps_per_scale_octave={SIFT_params['spso']}
minimum_image_size={SIFT_params['min_im']}
maximum_image_size={SIFT_params['max_im']}
feature_descriptor_size={SIFT_params['fds']}
feature_descriptor_orientation_bins={SIFT_params['fdob']}
closest/next_closest_ratio={SIFT_params['cnc']}
{filter_p}
maximal_alignment_error={SIFT_params['max_align_err']}
minimal_inlier_ratio={SIFT_params['inlier_rat']}
minimal_number_of_inliers={SIFT_params['min_num_inls']}
expected_transformation={SIFT_params['exp_transf']}"
);
"""
return SIFT_macro
[docs]def pre_bUnwarp_align(unflip_ref: str, flip_ref: str,
mask_files: List[Optional[str]], reference: str,
transformation: Tuple[float, float, float, bool]) -> Tuple[str, str, str, List[Optional[str]]]:
""" Precursor to bUnwarpJ alignment to collect files, references, and masks..
Determine the source and target images for bUnwarpJ. Apply any
pre-transformations necessary to help with bUnwarpJ alignment. Open and
return the masks for source and target.
Args:
unflip_ref: The unflip infocus image path.
flip_ref: The flip infocus image path.
mask_files: The filenames for the mask files, can be [None, None].
reference: The reference value for 'unflip'/'flip'.
transformation: The pre-shift/rotation to align the infocus images.
Returns:
The bUnwarpJ macro for carrying out the bUnwarpJ alignment. Additionally
returns the source and target path names, as well as
the associated masks for each image.
"""
# Grab reference images
if reference == 'unflip':
target_path, src_path = unflip_ref, flip_ref
target_img = f'target_unflip_image'
src_img = f'source_flip_image'
src_mask, target_mask = mask_files[1], mask_files[0]
elif reference == 'flip':
target_path, src_path = flip_ref, unflip_ref
target_img = f'target_flip_image'
src_img = f'source_unflip_image'
src_mask, target_mask = mask_files[0], mask_files[1]
# Open files
macro = f'''
open("{target_path}");
rename("{target_img}");
open("{src_path}");
rename("{src_img}");
'''
# Apply transformations to source image
apply_transform = get_shift_rot_macro(transformation)
macro = join([macro, apply_transform], '\n')
# Open masks
masks = [src_mask, target_mask]
return macro, src_img, target_img, masks
[docs]def bUnwarp_align(src_img: str, target_img: str, mask_fn: List[Optional[str]],
im_size: int, buj_params: Optional[Dict[str, Any]],
transf_savename: str) -> str:
""" Carries out the bUnwarpJ alignment.
Aligns the flip and unflip infocus images, defined as the source or target.
Mask files can be utilized to select specific regions for aligning.
Args:
src_img: The infocus image to transform.
target_img: The infocus image to align to.
mask_fn: The filenames for the mask files, can be [None, None].
im_size: The pixel size of the sides of the square image.
buj_params: Dictionary of bunwarpJ parameters.
transf_savename: The path to the transformation file created by bUnwarpJ.
Returns:
The bUnwarpJ macro for carrying out the bUnwarpJ alignment.
"""
# Grab all bUnwarpJ parameters
if not buj_params:
buj_params = set_default_bUnwarp()
src_mask, tgt_mask = "", ""
mask_macro = ""
open_blank_mask = False
for i in range(len(mask_fn)):
if mask_fn[i]:
if i == 0:
open_mask = f'''open("{mask_fn[i]}");
rename("source_mask");
'''
src_mask = "srcmask=source_mask"
elif i == 1:
open_mask = f'''open("{mask_fn[i]}");
rename("target_mask");
'''
tgt_mask = "tgtmask=target_mask"
else:
x, y = im_size
if not open_blank_mask:
open_mask = f'newImage("No_Mask", "8-bit white", {x}, {y}, 1);'
open_blank_mask = True
if i == 0:
src_mask = f"srcmask=No_Mask"
elif i == 1:
tgt_mask = f"tgtmask=No_Mask"
mask_macro = join([mask_macro, open_mask], '\n')
bUnwarp_macro = f"""
{mask_macro}
run("bUnwarpJ 2 images",
"src={src_img}
tgt={target_img}
{src_mask}
{tgt_mask}
modechoice={buj_params['reg_mode']}
subsamplefactor={buj_params['img_sub_factor']}
minscalechoice=[{buj_params['init_def']}]
maxscalechoice=[{buj_params['final_def']}]
divweight={buj_params['div_weight']}
curlweight={buj_params['curl_weight']}
landmarkweight={buj_params['landmark_weight']}
imageweight={buj_params['img_weight']}
consistencyweight={buj_params['cons_weight']}
stopthreshold={buj_params['stop_thresh']}
savetransf=true
filename={transf_savename}"
);
"""
return bUnwarp_macro
# ============================================================= #
# Fiji Post-Alignment Protocols #
# ============================================================= #
[docs]def trim_and_reverse_stacks(filenames: List[List[str]], shortname: str,
ref: str, pos: bool) -> Tuple[str, str]:
"""Removes excess images and reverses images after initial SIFT alignment.
The images for linear stack alignment with SIFT produce the best results by
aligning images to an infocus image. However the stacks that are made in FIJI
require the first image be the infocus, so when concatenating multiple stacks
together, there are extra infocus images that may need to be removed. This function
takes care of that. Images with only 2 images wait to be trimmed as you can't
concatenate a single image with a stack in FIJI.
Args:
filenames: List of image filenames.
shortname: The shortened name for the files to be aligning
- first character refers to flip/unlip,
- last character to under/in/overfocus.
ref: The reference infocus image filename.
pos: Option for whether the image is under or overfocus to reverse
the image stack.
Returns:
Macros for deleting slices and reversing a stack.
- delete: Macro for deleting slices from stack.
- reverse: Macro for reversing an image stack.
"""
# Trim excess images and reverse stack if necessary. Exceptions are if only 1 other
# file or if overfocus images are of the reference orientation.
# Reverse order of stack if images are underfocused.
delete, reverse = '', ''
if (len(filenames) > 1 and ((shortname != 'u_+' and 'unflip' in ref) or
(shortname != 'f_+' and 'flip' in ref and 'unflip' not in ref))):
delete = 'run("Delete Slice");'
if not pos:
reverse = 'run("Reverse");'
return delete, reverse
[docs]def trim_and_reverse_single_stack(filenames: List[List[str]], shortname: str) -> Tuple[str, str]:
"""Removes excess images for a single stack.
Similar to trim_and_reverse_stacks but for a single tfs stack.
Args:
filenames: List of image filenames.
shortname: The shortened name for the files to be aligning
- first character refers to flip/unlip,
- last character to under/in/over-focus.
Returns:
Macros for deleting slices and reversing a stack.
- delete: Macro for deleting slices from stack.
- reverse: Macro for reversing an image stack.
"""
# Trim excess images and reverse stack if necessary. Exceptions are if only 1 other
# file or if overfocus images are of the reference orientation.
# Reverse order of stack if images are underfocused.
delete, reverse = '', ''
if len(filenames) > 1 and shortname == '-under-':
delete = 'run("Delete Slice");'
reverse = 'run("Reverse");'
return delete, reverse
[docs]def save_stack_macro(savename: str) -> str:
"""Create macro to save and close all remaining windows."""
macro = f'''saveAs("Tiff", "{savename}");'''
return macro
[docs]def close_all_windows_macro() -> str:
"""Create macro to close all remaining windows."""
macro = '''while (nImages>0) {{selectImage(nImages);
close();}};'''
return macro
[docs]def delete_excess_images_macro(files: List[List[str]], short_names: List[List[str]],
ref: str) -> str:
"""Create macro to trim excess images appearing after stack is aligned.
Args:
files: 2D list of all of the files.
short_names: The list of shortnames for the aligned stacks.
ref: The reference infocus image.
Returns:
The macro from trimming excess images from stacks.
"""
# Initialize strings, set save title, and remove
# ref image if necessary.
full_img, window_select, trimmed_macro = '', '', ''
index = 0
for i in range(len(files)):
for j in range(len(files[i])):
delete = ""
# Delete excess reference images except from overfocus
# stack of the same orientation of reference image.
if (len(files[i][j]) == 1 and short_names[index] != 'u_+' and
'unflip' in ref):
delete = f'''selectWindow("{short_names[index]}");
run("Delete Slice");
'''
elif (len(files[i][j]) == 1 and short_names[index] != 'f_+' and
'flip' in ref and 'unflip' not in ref):
delete = f'''selectWindow("{short_names[index]}");
run("Delete Slice");
'''
trimmed_macro = join([trimmed_macro, delete], "\n")
index += 1
return trimmed_macro
[docs]def order_windows_for_selection_macro(shortnames: List[List[str]],
ref: str) -> Tuple[str, str]:
"""Select windows in the order they will be combined.
Args:
shortnames: The list of shortnames for the aligned stacks.
ref: The reference infocus image.
Returns:
Window order macro and the macro for concatenating stacks.
"""
img_num = 1
num_windows = len(shortnames)
window_order, concat_list = '', ''
for window in range(num_windows):
# Build list for concatenation later
img = f' image{img_num}={shortnames[window]}'
if (('unflip' in ref and shortnames[window] != 'u_0') or
('flip' in ref and 'unflip' not in ref and shortnames[window] != 'f_0')):
new_select = f'selectWindow("{shortnames[window]}");'
concat_list = join([concat_list, img])
window_order = join([window_order, new_select], '\n')
img_num += 1
elif '-under-' in shortnames[window] or '+over+' in shortnames[window]:
new_select = f'selectWindow("{shortnames[window]}");'
concat_list = join([concat_list, img])
window_order = join([window_order, new_select], '\n')
img_num += 1
return window_order, concat_list
[docs]def concatenate_stacks_macro(stack_names: str, concat_list: str) -> str:
"""Create macro to concatenate all files into one large stack and return.
Args:
stack_names: The shortnames of the stacks to concatenate.
concat_list: The order for concatenating the stacks.
Returns:
The macro for concatenating stacks.
"""
concatanation = f' run("Concatenate...", " title={stack_names}{concat_list}");'
return concatanation
[docs]def post_ls_alignment(macros: List[str], short_names: List[List[str]],
files: List[List[str]], stack_name:str,
ref: str) -> List[str]:
"""Runs all saving, concatenation, deleting of excess files.
Args:
macros: The current list of macros to feed into FIJI.
short_names: The list of stack shortnames
files: The 2D list of all image files.
stack_name: The name to give the final fully aligned stack.
ref: The reference infocus filename.
Returns:
List of macros for post LS alignment procedure.
"""
# Remove excess images
trimmed_stacks = delete_excess_images_macro(files, short_names, ref)
# Order windows and concatenate them together.
window_order, concat_list = order_windows_for_selection_macro(short_names, ref)
concatenated_stacks = concatenate_stacks_macro(short_names, concat_list)
# Save all stacks and close windows, return names for display in GUI.
save = save_stack_macro(stack_name)
close_windows = close_all_windows_macro()
# Return list of all macros and saved stack name
macros = macros + [trimmed_stacks, window_order, concatenated_stacks, save, close_windows]
return macros
[docs]def post_single_ls_alignment(macros: List[str], shortnames: List[List[str]],
files: List[List[str]], stack_name:str,
ref: str) -> List[str]:
"""Runs all saving, concatenation, deleting of excess files for single tfs.
Args:
macros: The current list of macros to feed into FIJI.
shortnames: The list of stack shortnames
files: The 2D list of all image files.
stack_name: The name to give the final fully aligned stack.
ref: The reference infocus filename.
Returns:
List of macros for post LS alignment procedure.
"""
# Remove excess images if necessary
trimmed_macro = ''
if len(files[0]) == 1:
trimmed_macro = f'''selectWindow("{shortnames[0]}");
run("Delete Slice");
'''
# Order windows and concatenate them together.
window_order, concat_list = order_windows_for_selection_macro(shortnames, ref)
concatenated_stacks = concatenate_stacks_macro(shortnames, concat_list)
# Save all stacks and close windows, return names for display in GUI.
save = save_stack_macro(stack_name)
close_windows = close_all_windows_macro()
# Return list of all macros and saved stack name
macros = macros + [trimmed_macro, window_order, concatenated_stacks, save, close_windows]
return macros
# ============================================================= #
# Fiji Run Calls #
# ============================================================= #
[docs]def run_ls_align(datafolder: str, reference: str = 'unflip', check_sift: bool = False,
sift_params: Optional[Dict[str, Any]] = None,
transform_params: Optional[Tuple[float, float, float, bool]] = None,
stack_name: str = 'uf_aligned_ls_stack.tif',
tfs_value: str = 'Unflip/Flip',
fls_value: str = 'Two',
fls_files: Optional[List[str]] = None) -> str:
""" Aligns all 'dm3' files in the 'datafolder' and saves an aligned Tiff
stack in the datafolder.
Args:
datafolder: The datafolder that contains the paths to the dm3 files.
reference: The reference tag that will be used for aligning.
check_sift: Option for full align (False) or check SIFT params align (True).
sift_params: Dictionary of SIFT params.
transform_params: Tuple of image shift and rotation params.
stack_name: The filename for saving the aligned stack.
tfs_value: Value for the type of tfs.
fls_value: Value for the number of fls files.
fls_files: The list of fls files.
Returns:
The full run LS align macro.
"""
if tfs_value == 'Unflip/Flip':
path1 = join([datafolder, 'unflip'], '/')
path2 = join([datafolder, 'flip'], '/')
elif tfs_value == 'Single':
path1 = join([datafolder, reference], '/')
path2 = None
# Open files, rotate, and apply transformations before alignment (pre-alignment)
ref, image_files = pre_ls_alignment(reference, check_sift, path1, path2, fls_files, tfs_value, fls_value)
all_files, files1, files2 = image_files
# Generate the Fiji macro for each alignment procedure.
if tfs_value != 'Single':
all_macros, shortnames = ls_alignment(path1, path2, sift_params, transform_params, ref, all_files)
all_macros = post_ls_alignment(all_macros, shortnames, all_files, stack_name, ref)
else:
ref_name = ref[ref.rfind('/')+1:]
all_macros, shortnames = single_ls_alignment(sift_params, files1, path1, param_test=check_sift, ref=ref_name)
all_macros = post_single_ls_alignment(all_macros, shortnames, files1, stack_name, ref)
# Format macro to run in FIJI
full_ls_macro = format_macro(all_macros)
# Return list of ordered filenames (to display in GUI)
return full_ls_macro
[docs]def run_single_ls_align(datafolder: str, reference: str = '',
sift_params: Optional[Dict[str, Any]] = None,
stack_name: str = 'test_ls_align.tif',
fls_files: Optional[List[str]] = None) -> str:
""" Aligns all 'dm3' files in the 'datafolder' and saves an aligned tiff
stack in the datafolder for a single tfs.
Args:
datafolder: The datafolder that contains the paths to the dm3 files.
reference: The reference tag that will be used for aligning.
sift_params: Dictionary of SIFT params.
stack_name: The filename for saving the aligned stack.
fls_files: The list of fls files
Returns:
The full run LS align macro for single tfs..
"""
# Grab image data
path1 = join([datafolder, 'unflip'], '/')
path2 = join([datafolder, 'flip'], '/')
unflip_files = pull_image_files(fls_files[0])
flip_files = pull_image_files(fls_files[1])
# Generate the Fiji macro for each alignment procedure.
if reference == "unflip":
ref = unflip_files[1]
files = unflip_files
path = path1
elif reference == 'flip':
ref = flip_files[1]
files = flip_files
path = path2
# Single alignement
all_macros, shortnames = single_ls_alignment(sift_params, files, path)
# Post-alignment processing for saving the stack
all_macros = post_single_ls_alignment(all_macros, shortnames, files, stack_name, ref)
# Format macro to run in FIJI
full_ls_macro = format_macro(all_macros)
return full_ls_macro
[docs]def run_bUnwarp_align(datafolder: str, mask_files: List[Optional[str]],
reference: str, transformation: Tuple[float, float, float, bool],
im_size: int, stack_paths: List[Optional[str]],
sift_FE_params: Optional[Dict[str, Any]] = None,
buj_params: Optional[Dict[str, Any]] = None,
savenames: Tuple[str, str] = ("default.txt", "default.tif"),
fls_files: Optional[List[str]] = None) -> str:
""" Uses bUnwarpJ to align all 'dm3' files in the 'datafolder' and saves an aligned Tiff
stack in the datafolder.
Args:
datafolder: The datafolder that contains the paths to the dm3 files.
mask_files: A list of the names of the mask_files.
- This can be a list of [None, None] if no masks are used.
reference: The reference tag that will be used for aligning.
transformation: Tuple of image shift and rotation params.
im_size: Integer pixel size of the image.
stack_paths: [flip_path, unflip_path]
sift_FE_params: Dictionary of SIFT feature extract params.
buj_params: Dictionary of bUnwarpJ params.
savenames: The filenames for saving the aligned stacks.
fls_files: The list of fls files
Returns:
The full run bUnwarpJ align macro.
"""
# Initiate pre-alignment processing.
if savenames == ("default.txt", "default.tif"):
transf_savename = join([datafolder, savenames[0]], '/')
stack_savename = join([datafolder, savenames[1]], '/')
else:
transf_savename, stack_savename = savenames
# Grab image data and get path names
unflip_files = pull_image_files(fls_files[0])
flip_files = pull_image_files(fls_files[1])
for i in range(len(unflip_files)):
for j in range(len(unflip_files[i])):
unflip_files[i][j] = join([datafolder, 'unflip', unflip_files[i][j]], '/')
flip_files[i][j] = join([datafolder, 'flip', flip_files[i][j]], '/')
unflip_ref, flip_ref = unflip_files[1][0], flip_files[1][0]
# Pre-alignment (open image files, apply transformations, open masks)
open_macro, src_img, target_img, masks = pre_bUnwarp_align(unflip_ref, flip_ref, mask_files,
reference, transformation)
# Pre-alignment (open image files, apply transformations, open masks)
SIFT_macro = extract_SIFT_landmarks_macro(src_img, target_img, sift_FE_params)
# Alignment (grab bunwarp parameters, run alignment, save transformation)
bUnwarp_macro = bUnwarp_align(src_img, target_img, masks, im_size,
buj_params, transf_savename)
close_windows = close_all_windows_macro()
create_trasf_macro = fijify_macro(join([open_macro, SIFT_macro, bUnwarp_macro, close_windows], '\n'))
apply_transf = apply_buj_trasf2stack(flip_ref, unflip_ref, stack_paths,
flip_files, unflip_files, reference,
transf_savename, transformation, stack_savename)
apply_buj_macro = fijify_macro(join([apply_transf, close_windows], '\n'))
full_macro = fijify_macro(join([create_trasf_macro, apply_buj_macro], '\n'))
return full_macro
[docs]def apply_buj_trasf2stack(flip_ref: str, unflip_ref: str,
stack_paths: List[str],
flip_files: List[List[str]], unflip_files: List[List[str]],
reference: str, transform_path: str,
transformation: Tuple[float, float, float, bool],
savename: str) -> str:
""" Applies the transformation produced by bUnwarpJ for the unflip<->flip infocus
images to all images of the opposite stack.
Once the flip/unflip stack series are generated using the Linear Stack with SIFT alignment,
the infocus images of each series are aligned. One of these infocus images is the target
to align to, the other image is the source that is aligning to the target. Once these have
been aligned with bUnwarpJ, the transformation is applied to all images that are in
the stack series of the source image (source stack).
Args:
flip_ref: The datafolder that contains the paths to the dm3 files.
unflip_ref: A list of the names of the mask_files.
stack_paths: [flip_path, unflip_path]
flip_files: List of all the flip files.
unflip_files: List of all the unflip files.
reference: The reference value of 'flip' or 'unflip'.
transform_path: The path to the transformation file created by bUnwarpJ.
transformation: Tuple of image shift and rotation params.
savename: The filename for saving the aligned stack.
Returns:
The bUnwarpJ macro for applying the bUnwarpJ transformation.
"""
flip_stack_path, unflip_stack_path = stack_paths
concat_macro = ''
if reference == 'unflip':
target_img = unflip_ref
target_stack = unflip_stack_path
source_stack = flip_stack_path
stack_len = len(flatten_order_list(flip_files))
concat_macro = f'run("Concatenate...", "title=merged_stack image1=target_stack image2=source_stack");'
elif reference == 'flip':
target_img = flip_ref
target_stack = flip_stack_path
source_stack = unflip_stack_path
stack_len = len(flatten_order_list(unflip_files))
concat_macro = f'run("Concatenate...", "title=merged_stack image1=source_stack image2=target_stack");'
open_macro = f'''open("{target_img}");
rename("target_img");
open("{source_stack}");
rename("source_stack");'''
transf_macro = get_shift_rot_macro(transformation, stack=True)
buj_macro = ""
for i in range(stack_len):
apply_buj_macro = f'''call("bunwarpj.bUnwarpJ_.loadElasticTransform", "{transform_path}",
"target_img",
"source_stack");
print("Applying transform to slice {i+1}");'''
if i != stack_len - 1:
next_slice = 'run("Next Slice [>]");'
apply_buj_macro = join([apply_buj_macro, next_slice], '\n')
buj_macro = join([buj_macro, apply_buj_macro], '\n')
target_stack_macro = f'''open("{target_stack}");
rename("target_stack");
'''
save_macro = f'saveAs("TIFF","{savename}");'
macro = join([open_macro, transf_macro, buj_macro,
target_stack_macro, concat_macro, save_macro], '\n')
return macro