ArraySplitter 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # @created: 20.02.2024
5
+ # @author: Aleksey Komissarov
6
+ # @contact: ad3002@gmail.com
7
+
8
+ __version__ = "1.3.0"
9
+
10
+ # __all__ = []
@@ -0,0 +1,519 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ ArraySplitter Classify - Group arrays into families based on cut sequences and decomposition patterns.
5
+
6
+ The hypothesis: Arrays with the same cut sequence and similar decomposition patterns
7
+ likely belong to the same repeat family.
8
+ """
9
+
10
+ import argparse
11
+ import os
12
+ from collections import Counter, defaultdict
13
+ from statistics import mean, median, stdev
14
+ import json
15
+
16
+ from tqdm import tqdm
17
+ import editdistance as ed
18
+
19
+
20
+ def extract_pattern_features_from_lengths_file(header, lengths_line):
21
+ """
22
+ Extract features from lengths file format.
23
+
24
+ Args:
25
+ header: Header line with metadata (e.g., ">seq_id cut=ATG orientation=fwd n_monomers=50 range=165-175 avg=171.2")
26
+ lengths_line: Space-separated fragment lengths
27
+
28
+ Returns:
29
+ dict: Features including length distribution, variability metrics
30
+ """
31
+ # Parse header
32
+ parts = header.split()
33
+ seq_id = parts[0][1:] # Remove '>'
34
+
35
+ # Extract metadata from header
36
+ metadata = {}
37
+ for part in parts[1:]:
38
+ if '=' in part:
39
+ key, value = part.split('=', 1)
40
+ metadata[key] = value
41
+
42
+ cut_seq = metadata.get('cut', '')
43
+ n_monomers = int(metadata.get('n_monomers', 0))
44
+
45
+ if not cut_seq or n_monomers == 0:
46
+ return None
47
+
48
+ # Parse lengths
49
+ all_lengths = [int(x) for x in lengths_line.strip().split()]
50
+
51
+ # Extract monomer lengths based on n_monomers count
52
+ # The header tells us how many internal monomers there are
53
+ # Typical structure: [left_flank] [monomer1] [monomer2] ... [monomerN] [right_flank]
54
+
55
+ if n_monomers == len(all_lengths):
56
+ # All are monomers, no flanks
57
+ monomer_lengths = all_lengths
58
+ elif n_monomers < len(all_lengths):
59
+ # Determine which are monomers vs flanks
60
+ # Use the average length from header to identify likely flanks
61
+ avg_length = float(metadata.get('avg', 0))
62
+
63
+ # Simple heuristic: monomers are closer to average length
64
+ # First fragment is likely left flank if much different from average
65
+ # Last fragment is likely right flank if much shorter than average
66
+
67
+ if len(all_lengths) == n_monomers + 1:
68
+ # One flank (either left or right)
69
+ if abs(all_lengths[0] - avg_length) > abs(all_lengths[-1] - avg_length):
70
+ # First is flank
71
+ monomer_lengths = all_lengths[1:]
72
+ else:
73
+ # Last is flank
74
+ monomer_lengths = all_lengths[:-1]
75
+ elif len(all_lengths) == n_monomers + 2:
76
+ # Both flanks present
77
+ monomer_lengths = all_lengths[1:-1]
78
+ else:
79
+ # Fallback: take middle n_monomers elements
80
+ start = (len(all_lengths) - n_monomers) // 2
81
+ monomer_lengths = all_lengths[start:start + n_monomers]
82
+ else:
83
+ # Shouldn't happen, but handle gracefully
84
+ monomer_lengths = all_lengths
85
+
86
+ # Calculate statistics
87
+ features = {
88
+ 'cut_sequence': cut_seq if cut_seq else 'UNKNOWN',
89
+ 'num_monomers': len(monomer_lengths),
90
+ 'mean_length': mean(monomer_lengths),
91
+ 'median_length': median(monomer_lengths),
92
+ 'min_length': min(monomer_lengths),
93
+ 'max_length': max(monomer_lengths),
94
+ 'length_variability': stdev(monomer_lengths) if len(monomer_lengths) > 1 else 0,
95
+ 'length_range': max(monomer_lengths) - min(monomer_lengths),
96
+ }
97
+
98
+ # Add length distribution
99
+ length_counts = Counter(monomer_lengths)
100
+ features['length_distribution'] = dict(length_counts)
101
+ features['unique_lengths'] = len(length_counts)
102
+
103
+ # Calculate step differences between consecutive monomers
104
+ if len(monomer_lengths) > 1:
105
+ step_diffs = []
106
+ for i in range(1, len(monomer_lengths)):
107
+ step_diff = abs(monomer_lengths[i] - monomer_lengths[i-1])
108
+ step_diffs.append(step_diff)
109
+
110
+ features['step_mean'] = mean(step_diffs) if step_diffs else 0
111
+ features['step_std'] = stdev(step_diffs) if len(step_diffs) > 1 else 0
112
+ features['step_max'] = max(step_diffs) if step_diffs else 0
113
+ features['step_stability'] = 1.0 - (features['step_std'] / features['mean_length']) if features['mean_length'] > 0 else 0
114
+ else:
115
+ features['step_mean'] = 0
116
+ features['step_std'] = 0
117
+ features['step_max'] = 0
118
+ features['step_stability'] = 1.0
119
+
120
+ # Calculate local variability using sliding window (10kb ~ 50-60 monomers for typical 171bp)
121
+ window_size = max(10, int(10000 / features['mean_length'])) # Adaptive window based on monomer size
122
+ if len(monomer_lengths) >= window_size:
123
+ window_variabilities = []
124
+ for i in range(len(monomer_lengths) - window_size + 1):
125
+ window = monomer_lengths[i:i + window_size]
126
+ window_std = stdev(window) if len(window) > 1 else 0
127
+ window_variabilities.append(window_std)
128
+
129
+ features['local_var_mean'] = mean(window_variabilities)
130
+ features['local_var_min'] = min(window_variabilities)
131
+ features['local_var_max'] = max(window_variabilities)
132
+ features['most_stable_region_var'] = features['local_var_min']
133
+ else:
134
+ # Too few monomers for windowed analysis
135
+ features['local_var_mean'] = features['length_variability']
136
+ features['local_var_min'] = features['length_variability']
137
+ features['local_var_max'] = features['length_variability']
138
+ features['most_stable_region_var'] = features['length_variability']
139
+
140
+ return features
141
+
142
+
143
+ def calculate_pattern_similarity(features1, features2):
144
+ """
145
+ Calculate similarity score between two decomposition patterns.
146
+
147
+ Returns:
148
+ float: Similarity score (0-1, where 1 is identical)
149
+ """
150
+ if not features1 or not features2:
151
+ return 0.0
152
+
153
+ # Different cut sequences = different families
154
+ if features1['cut_sequence'] != features2['cut_sequence']:
155
+ return 0.0
156
+
157
+ scores = []
158
+
159
+ # Compare mean lengths (most important)
160
+ length_diff = abs(features1['mean_length'] - features2['mean_length'])
161
+ mean_avg = (features1['mean_length'] + features2['mean_length']) / 2
162
+ length_score = 1 - min(length_diff / mean_avg, 1.0)
163
+ scores.append(length_score * 2) # Weight this more
164
+
165
+ # Compare variability
166
+ var_diff = abs(features1['length_variability'] - features2['length_variability'])
167
+ var_avg = (features1['length_variability'] + features2['length_variability']) / 2
168
+ if var_avg > 0:
169
+ var_score = 1 - min(var_diff / var_avg, 1.0)
170
+ scores.append(var_score)
171
+
172
+ # Compare length range
173
+ range_diff = abs(features1['length_range'] - features2['length_range'])
174
+ range_avg = (features1['length_range'] + features2['length_range']) / 2
175
+ if range_avg > 0:
176
+ range_score = 1 - min(range_diff / range_avg, 1.0)
177
+ scores.append(range_score)
178
+
179
+ # Compare number of unique lengths
180
+ unique_diff = abs(features1['unique_lengths'] - features2['unique_lengths'])
181
+ unique_avg = (features1['unique_lengths'] + features2['unique_lengths']) / 2
182
+ if unique_avg > 0:
183
+ unique_score = 1 - min(unique_diff / unique_avg, 1.0)
184
+ scores.append(unique_score * 0.5) # Weight this less
185
+
186
+ return sum(scores) / len(scores) if scores else 0.0
187
+
188
+
189
+ def cluster_arrays(array_features, similarity_threshold=0.8):
190
+ """
191
+ Cluster arrays into families based on pattern similarity.
192
+
193
+ Returns:
194
+ dict: Family assignments {array_id: family_id}
195
+ """
196
+ # Group by cut sequence first
197
+ cut_groups = defaultdict(list)
198
+ for array_id, features in array_features.items():
199
+ if features:
200
+ cut_groups[features['cut_sequence']].append(array_id)
201
+
202
+ # Within each cut group, cluster by pattern similarity
203
+ family_assignments = {}
204
+ family_id = 0
205
+
206
+ for cut_seq, array_ids in cut_groups.items():
207
+ if len(array_ids) == 1:
208
+ # Single array with this cut
209
+ family_assignments[array_ids[0]] = f"family_{family_id:04d}"
210
+ family_id += 1
211
+ continue
212
+
213
+ # Cluster arrays with same cut by pattern similarity
214
+ clusters = []
215
+
216
+ for array_id in array_ids:
217
+ assigned = False
218
+ features = array_features[array_id]
219
+
220
+ # Try to assign to existing cluster
221
+ for cluster in clusters:
222
+ # Compare with representative (first member)
223
+ rep_id = cluster[0]
224
+ rep_features = array_features[rep_id]
225
+
226
+ similarity = calculate_pattern_similarity(features, rep_features)
227
+
228
+ if similarity >= similarity_threshold:
229
+ cluster.append(array_id)
230
+ assigned = True
231
+ break
232
+
233
+ if not assigned:
234
+ # Create new cluster
235
+ clusters.append([array_id])
236
+
237
+ # Assign family IDs to clusters
238
+ for cluster in clusters:
239
+ current_family = f"family_{family_id:04d}"
240
+ for array_id in cluster:
241
+ family_assignments[array_id] = current_family
242
+ family_id += 1
243
+
244
+ return family_assignments
245
+
246
+
247
+ def classify_arrays(input_file, output_prefix, similarity_threshold=0.8, verbose=False):
248
+ """
249
+ Main classification function using lengths file.
250
+
251
+ Args:
252
+ input_file: Path to .lengths file from decomposition
253
+ output_prefix: Prefix for output files
254
+ similarity_threshold: Threshold for clustering (0-1)
255
+ verbose: Verbose output
256
+ """
257
+ print("ArraySplitter Classify")
258
+ print("="*60)
259
+ print(f"Input file: {input_file}")
260
+ print(f"Similarity threshold: {similarity_threshold}")
261
+
262
+ # Check if input is a lengths file
263
+ if not input_file.endswith('.lengths'):
264
+ print("Warning: Input file should be a .lengths file from decomposition step")
265
+
266
+ # Read lengths file
267
+ array_features = {}
268
+ array_info = {}
269
+
270
+ print("\nReading lengths file...")
271
+ with open(input_file, 'r') as f:
272
+ lines = f.readlines()
273
+
274
+ # Process pairs of lines (header + lengths)
275
+ i = 0
276
+ while i < len(lines):
277
+ if lines[i].startswith('>'):
278
+ header = lines[i].strip()
279
+ if i + 1 < len(lines):
280
+ lengths_line = lines[i + 1].strip()
281
+
282
+ # Extract sequence ID
283
+ seq_id = header.split()[0][1:] # Remove '>'
284
+
285
+ # Extract features
286
+ features = extract_pattern_features_from_lengths_file(header, lengths_line)
287
+
288
+ if features:
289
+ array_features[seq_id] = features
290
+
291
+ # Parse additional info from header
292
+ parts = header.split()
293
+ metadata = {}
294
+ for part in parts[1:]:
295
+ if '=' in part:
296
+ key, value = part.split('=', 1)
297
+ metadata[key] = value
298
+
299
+ # Calculate total length from all fragments
300
+ all_lengths = [int(x) for x in lengths_line.strip().split()]
301
+ total_length = sum(all_lengths)
302
+
303
+ array_info[seq_id] = {
304
+ 'length': total_length,
305
+ 'cut_sequence': metadata.get('cut', ''),
306
+ 'orientation': metadata.get('orientation', ''),
307
+ 'n_monomers': int(metadata.get('n_monomers', 0)),
308
+ 'avg_length': float(metadata.get('avg', 0))
309
+ }
310
+ else:
311
+ if verbose:
312
+ print(f"Warning: Could not extract features for {seq_id}")
313
+
314
+ i += 2
315
+ else:
316
+ i += 1
317
+ else:
318
+ i += 1
319
+
320
+ print(f"Found {len(array_features)} arrays")
321
+ print(f"\nSuccessfully analyzed {len(array_features)} arrays")
322
+
323
+ # Cluster arrays
324
+ print("\nClustering arrays into families...")
325
+ family_assignments = cluster_arrays(array_features, similarity_threshold)
326
+
327
+ # Count families
328
+ family_counts = Counter(family_assignments.values())
329
+ print(f"\nFound {len(family_counts)} families")
330
+
331
+ # Write results
332
+ output_file = f"{output_prefix}.families.tsv"
333
+ stats_file = f"{output_prefix}.family_stats.tsv"
334
+ summary_file = f"{output_prefix}.family_summary.tsv"
335
+ json_file = f"{output_prefix}.features.json"
336
+
337
+ print(f"\nWriting results to:")
338
+ print(f" {output_file}")
339
+ print(f" {stats_file}")
340
+ print(f" {summary_file}")
341
+ print(f" {json_file}")
342
+
343
+ # Write family assignments with step variability
344
+ with open(output_file, 'w') as f:
345
+ f.write("array_id\tfamily\tcut_sequence\tlength\tmean_monomer_length\tnum_monomers\t")
346
+ f.write("step_stability\tstep_std\tlocal_var_min\tmost_stable_region_var\n")
347
+
348
+ for array_id in sorted(family_assignments.keys()):
349
+ family = family_assignments[array_id]
350
+ info = array_info.get(array_id, {})
351
+ features = array_features.get(array_id, {})
352
+
353
+ f.write(f"{array_id}\t{family}\t{info.get('cut_sequence', 'NA')}\t")
354
+ f.write(f"{info.get('length', 0)}\t")
355
+ f.write(f"{features.get('mean_length', 0):.1f}\t")
356
+ f.write(f"{features.get('num_monomers', 0)}\t")
357
+ f.write(f"{features.get('step_stability', 0):.3f}\t")
358
+ f.write(f"{features.get('step_std', 0):.1f}\t")
359
+ f.write(f"{features.get('local_var_min', 0):.1f}\t")
360
+ f.write(f"{features.get('most_stable_region_var', 0):.1f}\n")
361
+
362
+ # Write family statistics
363
+ with open(stats_file, 'w') as f:
364
+ f.write("family\tnum_arrays\tcut_sequence\tmean_length\tstd_length\tmean_monomers\n")
365
+
366
+ for family in sorted(family_counts.keys()):
367
+ # Get all arrays in this family
368
+ family_arrays = [aid for aid, fam in family_assignments.items() if fam == family]
369
+
370
+ # Calculate family statistics
371
+ cut_seq = array_features[family_arrays[0]]['cut_sequence']
372
+ lengths = [array_info[aid]['length'] for aid in family_arrays]
373
+ monomer_counts = [array_features[aid]['num_monomers'] for aid in family_arrays]
374
+
375
+ f.write(f"{family}\t{len(family_arrays)}\t{cut_seq}\t")
376
+ f.write(f"{mean(lengths):.1f}\t")
377
+ f.write(f"{stdev(lengths) if len(lengths) > 1 else 0:.1f}\t")
378
+ f.write(f"{mean(monomer_counts):.1f}\n")
379
+
380
+ # Write family summary with detailed variability analysis
381
+ with open(summary_file, 'w') as f:
382
+ f.write("family\tcut_sequence\tnum_arrays\ttotal_monomers\tmean_monomer_length\tstd_monomer_length\t")
383
+ f.write("mean_step_stability\tmin_step_stability\tmax_step_stability\t")
384
+ f.write("mean_local_var_min\tmost_stable_array\tmost_stable_array_var\t")
385
+ f.write("structural_importance_score\n")
386
+
387
+ for family in sorted(family_counts.keys()):
388
+ # Get all arrays in this family
389
+ family_arrays = [aid for aid, fam in family_assignments.items() if fam == family]
390
+
391
+ # Aggregate statistics
392
+ cut_seq = array_features[family_arrays[0]]['cut_sequence']
393
+ all_monomer_lengths = []
394
+ step_stabilities = []
395
+ local_var_mins = []
396
+
397
+ for aid in family_arrays:
398
+ features = array_features[aid]
399
+ all_monomer_lengths.append(features['mean_length'])
400
+ step_stabilities.append(features['step_stability'])
401
+ local_var_mins.append(features['local_var_min'])
402
+
403
+ # Find most stable array in family
404
+ most_stable_idx = local_var_mins.index(min(local_var_mins))
405
+ most_stable_array = family_arrays[most_stable_idx]
406
+ most_stable_var = local_var_mins[most_stable_idx]
407
+
408
+ # Calculate structural importance score (0-1, higher = more structurally important)
409
+ # Based on: high step stability, low local variability, consistency across arrays
410
+ avg_step_stability = mean(step_stabilities)
411
+ avg_local_var_min = mean(local_var_mins)
412
+ consistency = 1.0 - (stdev(step_stabilities) if len(step_stabilities) > 1 else 0)
413
+
414
+ # Normalize variability (inverse, so low var = high score)
415
+ var_score = 1.0 / (1.0 + avg_local_var_min / 10.0) # Scale by 10 for typical values
416
+
417
+ structural_score = (avg_step_stability * 0.4 + var_score * 0.4 + consistency * 0.2)
418
+
419
+ # Total monomers in family
420
+ total_monomers = sum(array_features[aid]['num_monomers'] for aid in family_arrays)
421
+
422
+ f.write(f"{family}\t{cut_seq}\t{len(family_arrays)}\t{total_monomers}\t")
423
+ f.write(f"{mean(all_monomer_lengths):.1f}\t")
424
+ f.write(f"{stdev(all_monomer_lengths) if len(all_monomer_lengths) > 1 else 0:.1f}\t")
425
+ f.write(f"{avg_step_stability:.3f}\t")
426
+ f.write(f"{min(step_stabilities):.3f}\t")
427
+ f.write(f"{max(step_stabilities):.3f}\t")
428
+ f.write(f"{avg_local_var_min:.1f}\t")
429
+ f.write(f"{most_stable_array}\t")
430
+ f.write(f"{most_stable_var:.1f}\t")
431
+ f.write(f"{structural_score:.3f}\n")
432
+
433
+ # Write detailed features as JSON
434
+ with open(json_file, 'w') as f:
435
+ output_data = {
436
+ 'array_features': array_features,
437
+ 'family_assignments': family_assignments,
438
+ 'array_info': array_info
439
+ }
440
+ json.dump(output_data, f, indent=2)
441
+
442
+ # Print summary
443
+ print("\nClassification summary:")
444
+ print(f" Total arrays: {len(array_features)}")
445
+ print(f" Successfully classified: {len(family_assignments)}")
446
+ print(f" Number of families: {len(family_counts)}")
447
+
448
+ # Show top families
449
+ print("\nTop 10 families by size:")
450
+ for family, count in family_counts.most_common(10):
451
+ family_arrays = [aid for aid, fam in family_assignments.items() if fam == family]
452
+ cut_seq = array_features[family_arrays[0]]['cut_sequence']
453
+ print(f" {family}: {count} arrays (cut: {cut_seq})")
454
+
455
+ # Show structurally important families
456
+ print("\nStructurally important families (high stability, low variability):")
457
+
458
+ # Calculate structural scores for display
459
+ family_scores = []
460
+ for family in family_counts.keys():
461
+ family_arrays = [aid for aid, fam in family_assignments.items() if fam == family]
462
+
463
+ step_stabilities = [array_features[aid]['step_stability'] for aid in family_arrays]
464
+ local_var_mins = [array_features[aid]['local_var_min'] for aid in family_arrays]
465
+
466
+ avg_step_stability = mean(step_stabilities)
467
+ avg_local_var_min = mean(local_var_mins)
468
+ consistency = 1.0 - (stdev(step_stabilities) if len(step_stabilities) > 1 else 0)
469
+ var_score = 1.0 / (1.0 + avg_local_var_min / 10.0)
470
+ structural_score = (avg_step_stability * 0.4 + var_score * 0.4 + consistency * 0.2)
471
+
472
+ family_scores.append((family, structural_score, array_features[family_arrays[0]]['cut_sequence'], len(family_arrays)))
473
+
474
+ # Sort by structural score
475
+ family_scores.sort(key=lambda x: x[1], reverse=True)
476
+
477
+ # Show top structurally important families
478
+ for i, (family, score, cut_seq, count) in enumerate(family_scores[:5]):
479
+ print(f" {i+1}. {family}: score={score:.3f}, {count} arrays (cut: {cut_seq})")
480
+
481
+
482
+ def run_it():
483
+ """Command line interface."""
484
+ parser = argparse.ArgumentParser(
485
+ description="Classify satellite DNA arrays into families based on decomposition patterns"
486
+ )
487
+ parser.add_argument("-i", "--input", help="Input .monomers.tsv file from decomposition", required=True)
488
+ parser.add_argument("-o", "--output", help="Output prefix", required=True)
489
+ parser.add_argument(
490
+ "-s", "--similarity",
491
+ help="Similarity threshold for clustering (0-1, default: 0.8)",
492
+ type=float,
493
+ default=0.8
494
+ )
495
+ parser.add_argument(
496
+ "-v", "--verbose",
497
+ help="Verbose output",
498
+ action="store_true"
499
+ )
500
+
501
+ args = parser.parse_args()
502
+
503
+ if not os.path.isfile(args.input):
504
+ print(f"Error: Input file {args.input} not found")
505
+ exit(1)
506
+
507
+ if not args.input.endswith('.monomers.tsv'):
508
+ print(f"Warning: Input file should be a .monomers.tsv file from ArraySplitter decomposition")
509
+
510
+ classify_arrays(
511
+ args.input,
512
+ args.output,
513
+ similarity_threshold=args.similarity,
514
+ verbose=args.verbose
515
+ )
516
+
517
+
518
+ if __name__ == "__main__":
519
+ run_it()
File without changes
File without changes
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # @created: 05.06.2011
5
+ # @author: Aleksey Komissarov
6
+ # @contact: ad3002@gmail.com
7
+
8
+ """
9
+ Classes:
10
+
11
+ - AbstractBlockFileIO(AbstractFileIO)
12
+
13
+ """
14
+
15
+ from PyExp import AbstractFileIO
16
+
17
+
18
+ class AbstractBlockFileIO(AbstractFileIO):
19
+ """Working with file with data organized in block, where each block starts with same token.
20
+
21
+ Public methods:
22
+
23
+ - get_block_sequence(self, head_start, next_head, fh)
24
+ - get_blocks(self, token, fh)
25
+ - gen_block_sequences(self, token, fh)
26
+
27
+ Inherited public properties:
28
+
29
+ - data - iterable data, each item is tuple (head, body)
30
+ - N - a number of items in data
31
+
32
+ Overrided public methods:
33
+
34
+ - __init__(self, token)
35
+ - read_from_file(self, input_file)
36
+ - read_online(self, input_file) ~> item
37
+
38
+ Inherited public methods:
39
+
40
+ - read_from_db(self, db_cursor)
41
+ - write_to_file(self, output_file)
42
+ - write_to_db(self, db_cursor)
43
+ - read_as_iter(self, source)
44
+ - iterate(self) ~> item of data
45
+ - do(self, cf, args) -> result
46
+ - process(self, cf, args)
47
+ - clear(self)
48
+ - do_with_iter(self, cf, args) -> [result,]
49
+ - process_with_iter(self, cf, args)
50
+
51
+ """
52
+
53
+ def __init__(self, token, **args):
54
+ """Overrided. Set token velue."""
55
+ super(AbstractBlockFileIO, self).__init__(**args)
56
+ self.token = token
57
+ self.wise_opener = self.get_opener()
58
+
59
+ def read_from_file(self, input_file):
60
+ """Overrided. Read data from given input_file."""
61
+ with self.wise_opener(input_file, "r") as fh:
62
+ for head, body, start, next in self.gen_block_sequences(self.token, fh):
63
+ self.data.append((head, body, start, next))
64
+
65
+ def read_online(self, input_file):
66
+ """Overrided. Yield items from data online from input_file."""
67
+ with self.wise_opener(input_file, "r") as fh:
68
+ for head, body, start, next in self.gen_block_sequences(self.token, fh):
69
+ yield (head, body, start, next)
70
+
71
+ def get_block_sequence(self, head_start, next_head, fh):
72
+ """Get a data block (head, seq, head_start, head_end).
73
+
74
+ Arguments:
75
+
76
+ - head_start -- a head starting position in a file
77
+ - next_head -- a next to head starting position in a file
78
+ - fh -- an open file handler
79
+
80
+ Return format:
81
+
82
+ - head -- a block head
83
+ - seq -- a block body
84
+ - head_start -- a file pointer to block start
85
+ - head_end -- a file pointer to next block start or 0
86
+ """
87
+ head_start = int(head_start)
88
+ next_head = int(next_head)
89
+ sequence = ""
90
+ fh.seek(head_start)
91
+ pos = head_start
92
+ if next_head:
93
+ head = fh.readline()
94
+ pos = fh.tell()
95
+ while pos != next_head:
96
+ temp_seq = fh.readline()
97
+ pos = fh.tell()
98
+ if pos != next_head:
99
+ sequence += temp_seq
100
+ sequence += temp_seq
101
+ else:
102
+ head = fh.readline()
103
+ fasta_list = fh.readlines()
104
+ sequence = "".join(fasta_list)
105
+ return (head, sequence, head_start, next_head)
106
+
107
+ def get_blocks(self, token, fh):
108
+ """Get a list of the token positions in given file (first, next).
109
+ For the last string the function returns (last string, 0).
110
+
111
+ Arguments:
112
+
113
+ - token -- the token indicating a block start
114
+ - fh -- an open file handler
115
+
116
+ Return format: a list of (start, next start) tuples
117
+ """
118
+ fh.seek(0)
119
+ header_start_list = []
120
+ here = 0
121
+ wrong = 0
122
+ start = -1
123
+ while fh:
124
+ here = fh.tell()
125
+ line = fh.readline()
126
+ if len(line) == 0:
127
+ header_start_list.append((start, 0))
128
+ break
129
+ if line.startswith(token):
130
+ if start == -1:
131
+ start = here
132
+ else:
133
+ header_start_list.append((start, here))
134
+ start = here
135
+ return header_start_list
136
+
137
+ def gen_block_sequences(self, token, fh):
138
+ """Yield (head, seq, head_start, head_end) tuplefor given fh for open file.
139
+
140
+ Arguments:
141
+
142
+ - token -- the token indicating a block start
143
+ - fh -- an open file handler
144
+
145
+ Return format:
146
+
147
+ - head -- a block head
148
+ - seq -- a block body
149
+ - head_start -- a file pointer to block start
150
+ - head_end -- a file pointer to next block start or 0
151
+
152
+ """
153
+ header_start_list = self.get_blocks(token, fh)
154
+ for x, y in header_start_list:
155
+ # yeild sequence by coordinates
156
+ yield self.get_block_sequence(x, y, fh)