"""Miscellaneous useful functions.
In particular, methods for converting to and from plate coordinates.
"""
import numpy as np
from collections import OrderedDict
from fuzzywuzzy import process
import re
[docs]def compute_rows_columns(num_wells):
"""Convert 96->(8,12), 384->(16,24), etc."""
a = np.sqrt(num_wells / 6)
n_rows = int(np.round(2 * a))
n_columns = int(np.round(3 * a))
return n_rows, n_columns
[docs]def rowname_to_number(name):
"Convert A->1 Z->26 AA->27 etc."
if len(name) == 2:
return 26 * rowname_to_number(name[0]) + rowname_to_number(name[1])
try:
return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.index(name) + 1
except:
raise ValueError(name + " is not a valid row name.")
[docs]def number_to_rowname(number):
"Convert 1->A 26->Z 27->AA etc."
if number > 26:
return number_to_rowname(int(number / 26)) +\
number_to_rowname(number % 26)
return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[number - 1]
[docs]def wellname_to_coordinates(wellname):
"""Convert A1->(1,1), H11->(8, 11), etc."""
rowname, colname = re.match("([a-zA-Z]+)([0-9]+)", wellname).groups()
return rowname_to_number(rowname), int(colname)
[docs]def coordinates_to_wellname(coords):
"""Convert (1,1)->A1, (4,3)->D3, (12, 12)->H12, etc."""
row, column = coords
return number_to_rowname(row)+str(column)
[docs]def wellname_to_index(wellname, num_wells, direction="row"):
""" Convert e.g. A1..H12 into 1..96
direction is either row for A1 A2 A3... or column for A1 B1 C1 D1 etc.
"""
n_rows, n_columns = compute_rows_columns(num_wells)
row, column = wellname_to_coordinates(wellname)
if direction == "row":
return column + n_columns*(row-1)
elif direction == "column":
return row + n_rows*(column-1)
else:
raise ValueError("`direction` must be in (row, column)")
def index_to_row_column(index, num_wells, direction="row"):
n_rows, n_columns = compute_rows_columns(num_wells)
if direction == "row":
row = 1 + int((index-1) / n_columns)
column = 1 + ((index-1) % n_columns)
elif direction == "column":
row, column = 1 + ((index-1) % n_rows), 1 + int((index-1) / n_rows)
else:
raise ValueError("`direction` must be in (row, column)")
return row, column
[docs]def index_to_wellname(index, num_wells, direction="row"):
""" Convert e.g. 1..96 into A1..H12"""
row, column = index_to_row_column(index, num_wells, direction)
return coordinates_to_wellname((row, column))
def shift_wellname(wellname, row_shift=0, column_shift=0):
letter, number = wellname[0], wellname[1:]
letter_rownum = rowname_to_number(letter)
new_letter_rownum = letter_rownum + row_shift
new_letter = number_to_rowname(new_letter_rownum)
new_number = str(int(number) + column_shift)
return new_letter + new_number
[docs]def infer_plate_size_from_wellnames(wellnames):
"""Return the first of 96, 384, or 1536, to contain all wellnames."""
coordinates = [wellname_to_coordinates(name) for name in wellnames]
all_rows, all_columns = zip(*coordinates)
max_rows, max_columns = max(all_rows), max(all_columns)
if (max_rows > 16) or (max_columns > 24):
return 1536
elif (max_rows > 8) or (max_columns > 12):
return 384
else:
return 96
[docs]def round_at(value, rounding):
"""Round value at the nearest rounding"""
if rounding is None:
return value
else:
return np.round(value / rounding) * rounding
def dicts_to_columns(dicts):
return {
key: [d[key] for d in dicts]
for key in dicts[0]
}
def replace_nans_in_dict(dictionnary, replace_by='null'):
for key, value in dictionnary.items():
if isinstance(value, dict):
replace_nans_in_dict(value, replace_by=replace_by)
elif value == np.nan:
dictionnary[key] = replace_by
[docs]def human_seq_size(n):
'Return the given sequence as a human friendly 35b, 1.4k, 15k, etc.'
if n < 1000:
return '%db' % n
elif n < 10000:
return '%.1fk' % (n / 1000)
else:
return '%dk' % np.round(n / 1000)
unit_factors = {
prefix + unit: factor
for unit in 'glL'
for prefix, factor in [('', 1), ('m', 1e-3), ('u', 1e-6), ('n', 1e-9)]
}
volume_values_and_units = sorted([
(value, unit)
for (unit, value) in unit_factors.items()
if unit.endswith('L')
])
def find_best_volume_unit(vols):
med = np.median(vols)
for value, unit in volume_values_and_units:
if (not unit.endswith('g')) and (med <= 999 * value):
return unit
return unit
def human_volume(vol, unit='auto'):
if unit == 'auto':
unit = find_best_volume_unit([vol])
vol = np.round(vol / unit_factors[unit], 2)
if int(vol) == vol:
return "%d %s" % (vol, unit)
else:
return "%s %s" % (('%.02f' % vol).rstrip('0'), unit)
def did_you_mean(name, other_names, limit=5, min_score=50):
if isinstance(name, (list, tuple)):
return {
n: did_you_mean(n, other_names, limit=limit, min_score=min_score)
for n in name
}
results = process.extract(name, list(other_names), limit=limit)
return [e for (e, score) in results if score >= min_score]