-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgetRegistrationOffsets.py
More file actions
executable file
·305 lines (249 loc) · 10.8 KB
/
Copy pathgetRegistrationOffsets.py
File metadata and controls
executable file
·305 lines (249 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#!/usr/bin/env python
"""
Get the registration offsets for a directory of images relative to a
directory of CPseqs.
Note: Python 3
Inputs:
directory of images to register
directory of CPseq files from which to pick clusters
Outputs:
registration offsets
Ben Ober-Reynolds
"""
import os
import sys
import argparse
import re
import uuid
import subprocess
import time
from collections import OrderedDict
from joblib import Parallel, delayed
##### Gloval vars #####
offset_scale_x = -3.7
offset_scale_y = 3.7
def main():
# set up command line argument parser
parser = argparse.ArgumentParser(description='script for isolating specific \
clusters from fastq files, based on a set of CPseq files')
group = parser.add_argument_group('required arguments:')
group.add_argument('-id', '--image_directory', required=True,
help='directory containing images to register')
group.add_argument('-sd', '--CPseq_dir', required=True,
help='directory containing CPseq files')
group.add_argument('-gv','--global_vars_path', required=True,
help='path to the directory in which the "GlobalVars.m" parameter file \
for the run can be found')
group = parser.add_argument_group('optional arguments')
group.add_argument('--nofile', action='store_true',
help='use flag to prevent output of offsets file. Results printed to STDOUT only.')
group.add_argument('-f', '--filters', type=str, nargs='+',
help='Which filter(s) from the CPseq files to register. Default is "FIDUCIAL"')
group.add_argument('-ds', '--data_scaling', type=str, default='MiSeq_to_TIRFStation1',
help='Data scaling for registration. Default is "MiSeq_to_TIRFStation1"')
group.add_argument('-od', '--output_directory',
help='output directory for registration offsets (default is original \
image_directory)')
group.add_argument('-op', '--output_prefix', type=str, default='registration_offsets',
help='output prefix for registration offsets file (default is "registration_offsets")')
group.add_argument('-n', '--num_cores', type=int, default=18,
help='number of cores to use (should be same as number of image \
files)')
# print help if no arguments provided
if len(sys.argv) <= 1:
parser.print_help()
sys.exit()
# parse command line arguments
args = parser.parse_args()
numCores = args.num_cores
# Pre-defined variables, constants, and settings
image_extension = 'tif'
CPseq_extension = 'CPseq'
# Check directories and output files
image_dir = args.image_directory
if not os.path.isdir(image_dir):
print("Error: invalid image directory selection. Exiting...")
sys.exit()
CPseq_dir = args.CPseq_dir
if not os.path.isdir(CPseq_dir):
print("Error: invalid CPseq directory selection. Exiting...")
sys.exit()
output_dir = args.output_directory
if not output_dir:
output_dir = image_dir
# Check global vars:
globalVarsFilename = os.path.join(args.global_vars_path, 'GlobalVars.m')
if not os.path.isfile(globalVarsFilename):
print('ERROR: GlobalVars.m file not found in directory "' + args.global_vars_path + '". Aborting')
sys.exit()
# Gather image files:
print("Finding image files in directory {}".format(image_dir))
image_list = find_files_in_directory(image_dir,
extensionList=[image_extension])
# Gather CPseq files:
print("Finding CPseq files in directory {}".format(CPseq_dir))
CPseq_list = find_files_in_directory(CPseq_dir,
extensionList=[CPseq_extension])
# Make tile dict of each tile list
image_tile_dict = make_tile_dict(image_list)
CPseq_tile_dict = make_tile_dict(CPseq_list)
# Pick filters to use
filter_list = args.filters
if not filter_list:
filter_list = ['FIDUCIAL']
print("Registering images using the following filter(s): {}".format(filter_list))
# Run registration
registration_results = []
if numCores > 1:
print("Getting registration offsets for {} image files on {} cores...".format(
len(image_list), numCores))
registration_results = (Parallel(n_jobs=numCores, verbose=10)\
(delayed(checkRegistrationOffset)(
tile, image_tile_dict[tile], CPseq_tile_dict[tile], args.data_scaling,
filter_list, args.global_vars_path) for tile in image_tile_dict.keys()))
else:
print("Getting registration offsets for {} image files on one core...".format(
len(image_list)))
registration_results = [checkRegistrationOffset(
tile, image_tile_dict[tile], CPseq_tile_dict[tile], args.data_scaling,
filter_list, args.global_vars_path) for tile in image_tile_dict.keys()]
# Format and output results:
# First sort by tile number:
registration_results.sort(key=lambda x: x[0])
# Save a file if indicated
if not args.nofile:
with open(output_dir + '/' + args.output_prefix + '.txt', 'w') as f:
f.write('x\ty\n')
for offset in registration_results:
f.write("{}\t{}\n".format(round(offset_scale_x*offset[2], 3), round(offset_scale_y*offset[1], 3)))
# Add zeros at the end of this file, since the imaging stating expects 19 tiles
f.write("0\t0")
# print to stdout:
print("Found offsets:")
print("\tx\ty")
for offset in registration_results:
print("tile {}:\t{}\t{}".format(offset[0], round(offset_scale_x*offset[2], 3), round(offset_scale_y*offset[1], 3)))
def find_files_in_directory(dirPath, extensionList=None,
excludedExtensionList=None):
"""
Locate files in a given directory path. Optionally, desired files are
identified as matching one of the extension types provided in
'extensionList'
Input:
dirPath (str) - path to directory
extensionList (list) - list of acceptable extensions
excludedExtensionList (list) - list of unacceptable extensions
Output:
fileList (list) - list of found files (with path)
"""
def extension_match(filename, extensionList=None):
# from CPlibs
if extensionList is not None:
for currExt in extensionList:
if filename.lower().endswith(currExt.lower()):
return True
return False
dirList = os.listdir(dirPath)
fileList = []
for currFilename in dirList:
if (extension_match(currFilename, extensionList)
and not extension_match(currFilename, excludedExtensionList)):
fileList.append(dirPath+currFilename)
if len(dirList) == 0:
print('\tNONE FOUND')
else:
for filename in fileList:
print("found:\t\t{}".format(filename))
return fileList
def get_tile_number_from_filename(inFilename):
"""
Extract the tile number from a provided filename based on the presence of
'tile###'
Input: filename (string)
Output: three digit tile number (string)
"""
# from CPlibs
(path,filename) = os.path.split(inFilename) #split the file into parts
(root,ext) = os.path.splitext(filename)
matches = re.findall('tile[0-9]{1,3}',root.lower())
tileNumber = ''
if matches != []:
tileNumber = '{:03}'.format(int(matches[-1][4:]))
return tileNumber
def make_tile_dict(fileList):
"""
Make a dictionary of files keyed by tile number.
Input: list of files containing tile numbers
Output: dictionary of file names keyed by tile number
"""
fileDict = {}
for f in fileList:
tile = get_tile_number_from_filename(f)
if tile == '':
print("Error: no tile number in file: "+ f)
sys.exit()
else:
if tile in fileDict:
print("Error: multiple files per tile")
sys.exit()
fileDict[tile] = f
return fileDict
def checkRegistrationOffset(tile, image_file, CPseq_file, data_scaling, filter_list, global_vars_path):
"""
Run the matlab script 'checkTileRegistration.m'
Return the raw offsets calculated.
"""
filter_string = "{{{}}}".format(",".join("'" + x + "'" for x in filter_list))
matlabFunctionCallString = "checkTileRegistrationV2('{0}','{1}','{2}', {3});".format(
CPseq_file, image_file, data_scaling, filter_string)
logstring = spawnMatlabJob(matlabFunctionCallString, global_vars_path)
# Parse logstring for relevant information:
center_pos_offsets = ""
log_lines = logstring.split('\n')
parens_pat = re.compile(("\(.+?\,.+?\)"))
for line in log_lines:
matches = re.findall(parens_pat, line)
if matches:
center_pos_offsets = matches[1]
if center_pos_offsets == "":
# If registration not found, just return zeros.
return (int(tile), 0, 0)
offset_y, offset_x = [float(x) for x in center_pos_offsets[1:-1].split(',')]
return (int(tile), offset_y, offset_x)
def spawnMatlabJob(matlabFunctionCallString,globalVarsPath):
"""
Adapted from CPlibs.py
"""
try:
#construct the command-line matlab call
functionCallString = "try,"
functionCallString = functionCallString + "addpath('{0}');".format(globalVarsPath) #placeholder TEMP DEBUG CHANGE
functionCallString = functionCallString + matlabFunctionCallString + ';'
functionCallString = functionCallString + "catch e,"
functionCallString = functionCallString + "disp(getReport(e,'extended'));"
functionCallString = functionCallString + "end,"
functionCallString = functionCallString + "quit;"
logFilename = 'matlabProcess_' + str(uuid.uuid4()) + str(time.time()) + '.tempLog' #timestamped logfile filename
cmdString ='matlab -nodesktop -nosplash -singleCompThread -r "{0}"'.format(functionCallString)
cmdString = cmdString + ' 1>> {0}'.format(logFilename)
cmdString = cmdString + ' 2>> {0}'.format(logFilename)
print('issuing subprocess shell command: ' + cmdString)
returnCode = subprocess.call(cmdString,shell=True) #execute the command in the shell
returnCode2 = subprocess.call('stty sane',shell=True) #matlab messes up the terminal in a weird way--this fixes it
#read log file into a string
try:
with open(logFilename) as logFilehandle:
logString = logFilehandle.read()
# delete logfile
try:
os.unlink(logFilename)
except OSError:
pass
except IOError:
logString = 'Log file not generated for command "' + functionCallString + '".'
# return log
return logString
except Exception as e:
return 'Python exception generated in spawnMatlabJob: ' + e
if __name__ == '__main__':
main()