-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathget_closest_variant.py
More file actions
executable file
·235 lines (184 loc) · 8.43 KB
/
Copy pathget_closest_variant.py
File metadata and controls
executable file
·235 lines (184 loc) · 8.43 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
#!/usr/bin/env python
"""
Identify the closest variant of a given type to each cluster.
Useful for filtering things that are too close to fiducial marks for example.
Warnings: The distance matrix computation is catastrophically memory intensive
for a whole chip of data. Limit core use to prevent overloading the server.
Inputs:
CPannot file
variant_ID of interest
Outputs:
pickled 'nearest variant' file
Ben Ober-Reynolds
"""
import os
import sys
import argparse
import pandas as pd
import numpy as np
import gc
from scipy import spatial
from joblib import Parallel, delayed
def main():
################ Parse input parameters ################
#set up command line argument parser
parser = argparse.ArgumentParser(description='script for identifying the nearest cluster of a specific variant, for each other variant.')
group = parser.add_argument_group('required arguments:')
group.add_argument('-ca', '--cpannot_file', required=True,
help='The CPannot file (cluster_IDs and associated variant_IDs).')
group.add_argument('-v', '--variant_IDs_of_interest', required=True,
help='A file containing variant IDs for which you want to calculate min distance to (min distance to closest of any included variant_ID)')
group = parser.add_argument_group('optional arguments')
group.add_argument('-od','--output_directory', default="",
help='output directory for output file (default is current directory)')
group.add_argument('-of','--output_filename', default="",
help='prefex for the calculated distance file (default is CPannot filename with variant_ID appended)')
group.add_argument('-n','--num_cores', type=int, default=1,
help='number of cores to use (uses lots of memory!)')
# Some fixed parameters:
VOI_warning_threshold = 1000
variant_col_header = 'variant_ID'
index_col_header = 'index'
tile_col_header = 'tile'
location_col_header = 'location'
# print help if no arguments provided
if len(sys.argv) <= 1:
parser.print_help()
sys.exit()
#parse command line arguments
args = parser.parse_args()
# Fiducial mark variant_ID:
VOI_IDs = args.variant_IDs_of_interest
# variants to calculate distance to:
VOI_IDs = []
with open(args.variant_IDs_of_interest, 'r') as f:
for line in f:
VOI_IDs.append(line.strip())
# If no output directory given, use current directory
output_dir = args.output_directory
if output_dir == "":
output_dir = './'
if not os.path.isdir(output_dir):
print "Error: invalid output directory selection. Exiting..."
sys.exit()
# Set output filename
output_filename = args.output_filename
if output_filename == "":
output_filename = os.path.basename(args.cpannot_file).split('.')[0] + "_VOI_distances.pkl"
output_filename = output_dir.strip('/') + '/' + output_filename
# Number of cores to use for distance matrix calculation
numCores = args.num_cores
# Read in data:
print "Reading in CPannot file {}...".format(args.cpannot_file)
annot_df = pd.read_pickle(args.cpannot_file)
num_variants = len(annot_df[annot_df[variant_col_header].isin(VOI_IDs)].index)
if num_variants < VOI_warning_threshold:
print "Only found {} clusters with in variant ID list!".format(num_variants)
print "Is this correct? Continue with distance calculation?"
answer = raw_input('[y/n]')
if answer.lower() != 'y':
sys.exit()
print "Data loaded successfully. Preparing for variant proximity filtering..."
# Calculate distances:
min_dist_df = compute_min_distances(annot_df, VOI_IDs, variant_col_header,
index_col_header, tile_col_header, location_col_header, numCores)
# Save output as pickle
print "Saving output..."
min_dist_df.to_pickle(output_filename)
print "Done."
def extract_x(cluster_id):
# Extract x position
split_list = cluster_id.split(':')
return int(split_list[5])
def extract_y(cluster_id):
# Extract y position
split_list = cluster_id.split(':')
return int(split_list[6])
def extractTile(cluster_id):
# Extract the tile number from a cluster ID
return int(cluster_id.split(':')[4][2:])
def preallocate_dict(keys):
# Pre-allocate a dictionary with the keys required.
d = {}
for key in keys:
d[key] = []
return d
def compute_min_distances(annot_df, VOI_IDs, variant_col_header, index_col_header,
tile_col_header, location_col_header, numCores):
"""
A very hairy function for calculating the real distance for each cluster to
its closest fiducial mark. Use of pandas slicing allows for significant speedup of
some sections at the cost of programatic elegance...
"""
# separate the VOI and non-VOI clusters:
no_VOI_df = annot_df.loc[~annot_df[variant_col_header].isin(VOI_IDs)]
# Filter by proximity to VOI clusters:
VOI_indicees = pd.Series(annot_df.loc[annot_df[variant_col_header].isin(VOI_IDs)].index.values)
non_VOI_indicees = pd.Series(no_VOI_df.index.values)
non_VOI_x = non_VOI_indicees.apply(func=extract_x)
non_VOI_y = non_VOI_indicees.apply(func=extract_y)
non_VOI_tiles = non_VOI_indicees.apply(func=extractTile)
VOI_x = VOI_indicees.apply(func=extract_x)
VOI_y = VOI_indicees.apply(func=extract_y)
VOI_tiles = VOI_indicees.apply(func=extractTile)
# Construct some data frames with the previously obtained tile and location information
# these intermediate structures will speed up dictionary creation in the next step.
non_VOI_loc_df = pd.DataFrame({index_col_header: non_VOI_indicees, tile_col_header: non_VOI_tiles,
'x': non_VOI_x, 'y': non_VOI_y})
non_VOI_loc_df = non_VOI_loc_df[[index_col_header, tile_col_header, 'x', 'y']]
non_VOI_loc_df = non_VOI_loc_df.set_index(index_col_header)
VOI_loc_df = pd.DataFrame({index_col_header: VOI_indicees, tile_col_header: VOI_tiles,
'x': VOI_x, 'y': VOI_y})
VOI_loc_df = VOI_loc_df[[index_col_header, tile_col_header, 'x', 'y']]
VOI_loc_df = VOI_loc_df.set_index(index_col_header)
# Construct dictionaries keyed by tile:
# Originally thought using dictionaries would reduce memory usage during parallel processing... they don't though.
# distance matrix computation
all_tiles = sorted(list(set(VOI_tiles)))
VOI_loc_dict = preallocate_dict(all_tiles)
non_VOI_index_dict = preallocate_dict(all_tiles)
non_VOI_loc_dict = preallocate_dict(all_tiles)
# Fill in the dictionaries (the series maintain their original sorting)
for tile in all_tiles:
non_VOI_index_dict[tile] = non_VOI_loc_df[non_VOI_loc_df[tile_col_header] == tile].index.values
non_VOI_loc_dict[tile] = non_VOI_loc_df[non_VOI_loc_df[tile_col_header] == tile][['x', 'y']].values
VOI_loc_dict[tile] = VOI_loc_df[VOI_loc_df[tile_col_header] == tile][['x', 'y']].values
# Free up as much memory as possible...
del VOI_indicees
del non_VOI_indicees
del VOI_x
del VOI_y
del non_VOI_x
del non_VOI_y
del VOI_tiles
del non_VOI_tiles
del VOI_loc_df
del non_VOI_loc_df
del annot_df
print "garbage collected..."
gc.collect()
# Use the dictionaries for parallel computation of distance matrices:
if numCores > 1:
print "Calculating distances in parallel with {} cores...".format(numCores)
frames = (Parallel(n_jobs=numCores, verbose = 10)(delayed(get_VOI_proximities)(\
non_VOI_index_dict[tile],
non_VOI_loc_dict[tile],
VOI_loc_dict[tile], tile) for tile in all_tiles))
else:
print "Calculating distances on a single core..."
frames = [get_VOI_proximities(
non_VOI_index_dict[tile],
non_VOI_loc_dict[tile],
VOI_loc_dict[tile], tile) for tile in all_tiles]
return pd.concat(frames)
def get_VOI_proximities(non_VOI_clusterIDs, non_VOI_loc_list, VOI_loc_list, tile):
# Construct a data frame of distances to closest fiducial mark for
# each cluster
print "calculating distances for tile {}".format(tile)
dm = spatial.distance_matrix(non_VOI_loc_list, VOI_loc_list)
min_distances = [min(x) for x in dm]
dist_df = pd.DataFrame({'cluster_ID': non_VOI_clusterIDs,
'min_dist': min_distances}).set_index('cluster_ID')
return dist_df
if __name__ == '__main__':
main()