Coverage for /home/kale/research/software/projects/bio96/bio96/util.py: 100%

# Coverage for /home/kale/research/software/projects/bio96/bio96/util.py : 100%

## 62 statements 62 run 0 missing 0 excluded

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

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 | #!/usr/bin/env python3 import re import string # Indices # ======= # `well` # A string naming a well, e.g. "A1" or "A01". Note that columns count from # 1. Rows beyond "Z" can be specified useing multiple letters, e.g. "AA". # # `well0` # The same as `well`, but with the column numbers padded with leading zeros. # # `row`, `col` # The individual row (letter) or column (number) parts of a well. # # `i`, `j` # The (row, column) numeric coordinates of a well, counting from 0. For # example, "A1" is (0,0), "A2" is (0, 1), "B1" is (1, 0), etc. def well_from_row_col(row, col): return f'{row}{int(col)}' def well_from_ij(i, j): return well_from_row_col( row_from_i(i), col_from_j(j), ) def well0_from_well(well, digits=2): row, col = row_col_from_well(well) return well0_from_row_col(row, col, digits) def well0_from_row_col(row, col, digits=2): return f'{row}{int(col):0{digits}}' def row_from_i(i): row = '' N = len(string.ascii_uppercase) while i >= 0: row = string.ascii_uppercase[i % N] + row i = (i // N) - 1 return row def col_from_j(j): return str(j + 1) def row_col_from_ij(i, j): return row_from_i(i), col_from_j(j) def row_col_from_well(well): m = re.match('([A-Za-z]+)([0-9]+)', well) if not m: raise ConfigError(f"Cannot parse well '{well}', expected 'A1', 'B2', etc.") return m.group(1).upper(), str(int(m.group(2))) def i_from_row(row): if not row.isalpha(): raise ConfigError(f"Cannot parse row '{row}', expected letter(s) e.g. 'A', 'B', etc.") i = 0 D = len(row) - 1 N = len(string.ascii_uppercase) for d, char in enumerate(row): n = ord(char.upper()) - ord('A') + 1 i += n * N**(D - d) return i - 1 def j_from_col(col): if not col.isdigit(): raise ConfigError(f"Cannot parse column '{col}', expected digit(s) e.g. '1', '2', etc.") return int(col) - 1 def ij_from_well(well): return ij_from_row_col(*row_col_from_well(well)) def ij_from_row_col(row, col): return i_from_row(row), j_from_col(col) def interleave(a, b): """ Convert the given coordinates between "real" and "interleaved" space. Only the first coordinate differs between the two spaces. A straight row in "interleaved" space will alternative between two adjacent rows in "real" space, and vice versa. This function can also used to interleave columns (by passing the column as the first argument and the row as the second). Note that this function is its own inverse. """ if a % 2 == 0: return a + b % 2 else: return a - b % 2 def iter_ij_in_block(top_left, width, height): top, left = ij_from_well(top_left) for dx in range(width): for dy in range(height): yield top + dy, left + dx class ConfigError(Exception): def __init__(self, message): self.message = message self.toml_path = None def __str__(self): if self.toml_path: return f"{self.toml_path}: {self.message}" else: return self.message