Spaces:
Sleeping
Sleeping
Add checks for removed foci
Browse files- path_analysis/analyse.py +31 -18
- path_analysis/data_preprocess.py +132 -51
- tests/test_analyse.py +90 -0
- tests/test_preprocess.py +35 -24
path_analysis/analyse.py
CHANGED
|
@@ -36,22 +36,25 @@ def get_paths_from_traces_file(traces_file):
|
|
| 36 |
path_lengths.append(float(length))
|
| 37 |
return all_paths, path_lengths
|
| 38 |
|
| 39 |
-
def calculate_path_length(point_list, voxel_size=(1,1,1)):
|
| 40 |
-
# Simple calculation
|
| 41 |
-
l = 0
|
| 42 |
-
s = np.array(voxel_size)
|
| 43 |
-
for i in range(len(point_list)-1):
|
| 44 |
-
l += la.norm(s * (np.array(point_list[i+1]) - np.array(point_list[i])))
|
| 45 |
-
return l
|
| 46 |
|
| 47 |
|
| 48 |
def calculate_path_length_partials(point_list, voxel_size=(1,1,1)):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
# Simple calculation
|
| 50 |
-
|
| 51 |
s = np.array(voxel_size)
|
| 52 |
for i in range(len(point_list)-1):
|
| 53 |
-
|
| 54 |
-
return np.cumsum(
|
| 55 |
|
| 56 |
|
| 57 |
def visualise_ordering(points_list, dim, wr=5, wc=5):
|
|
@@ -260,6 +263,7 @@ def measure_chrom2(path, hei10, config):
|
|
| 260 |
measurements = measure_all_with_sphere(path, hei10, op='mean', R=sphere_xy_radius, z_scale_ratio=scale_ratio)
|
| 261 |
measurements_max = measure_all_with_sphere(path, hei10, op='max', R=sphere_xy_radius, z_scale_ratio=scale_ratio)
|
| 262 |
|
|
|
|
| 263 |
return vis, measurements, measurements_max
|
| 264 |
|
| 265 |
def extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config):
|
|
@@ -286,7 +290,7 @@ def extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config):
|
|
| 286 |
n_paths = len(all_paths)
|
| 287 |
|
| 288 |
data = []
|
| 289 |
-
foci_absolute_intensity, foci_position, foci_position_index, trace_median_intensities, trace_thresholds = analyse_traces(all_paths, path_lengths, measured_traces, config)
|
| 290 |
|
| 291 |
foci_intensities = []
|
| 292 |
for path_foci_abs_int, tmi in zip(foci_absolute_intensity, trace_median_intensities):
|
|
@@ -299,6 +303,8 @@ def extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config):
|
|
| 299 |
|
| 300 |
pl = calculate_path_length_partials(all_paths[i], (config['xy_res'], config['xy_res'], config['z_res']))
|
| 301 |
|
|
|
|
|
|
|
| 302 |
path_data = { 'Cell_ID':cell_id,
|
| 303 |
'Trace': i+1,
|
| 304 |
'SNT_trace_length(um)': path_lengths[i],
|
|
@@ -315,7 +321,7 @@ def extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config):
|
|
| 315 |
path_data[f'Foci_{j+1}_relative_intensity'] = (v - trace_median_intensities[i])/mean_intensity
|
| 316 |
data.append(path_data)
|
| 317 |
trace_positions.append(pl)
|
| 318 |
-
return pd.DataFrame(data), foci_absolute_intensity, foci_position_index, trace_thresholds, trace_positions
|
| 319 |
|
| 320 |
|
| 321 |
def analyse_paths(cell_id,
|
|
@@ -351,25 +357,32 @@ def analyse_paths(cell_id,
|
|
| 351 |
vis, m, _ = measure_chrom2(p,foci_stack.transpose(2,1,0), config)
|
| 352 |
all_trace_vis.append(vis)
|
| 353 |
all_m.append(m)
|
|
|
|
| 354 |
|
| 355 |
-
|
| 356 |
-
extracted_peaks, foci_absolute_intensity, foci_pos_index, trace_thresholds, trace_positions = extract_peaks(cell_id, all_paths, path_lengths, all_m, config)
|
| 357 |
|
| 358 |
|
| 359 |
n_cols = 2
|
| 360 |
n_rows = (len(all_paths)+n_cols-1)//n_cols
|
| 361 |
-
fig, ax = plt.subplots(n_rows,n_cols)
|
| 362 |
ax = ax.flatten()
|
| 363 |
|
| 364 |
for i, m in enumerate(all_m):
|
| 365 |
ax[i].set_title(f'Trace {i+1}')
|
| 366 |
ax[i].plot(trace_positions[i], m)
|
| 367 |
-
print(foci_pos_index)
|
| 368 |
if len(foci_pos_index[i]):
|
| 369 |
ax[i].plot(trace_positions[i][foci_pos_index[i]], np.array(m)[foci_pos_index[i]], 'rx')
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
ax[i].axhline(trace_thresholds[i], c='r', ls=':')
|
|
|
|
|
|
|
| 373 |
for i in range(len(all_m), n_cols*n_rows):
|
| 374 |
ax[i].axis('off')
|
| 375 |
|
|
|
|
| 36 |
path_lengths.append(float(length))
|
| 37 |
return all_paths, path_lengths
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
|
| 41 |
def calculate_path_length_partials(point_list, voxel_size=(1,1,1)):
|
| 42 |
+
"""
|
| 43 |
+
Calculate the partial path length of a series of points.
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
point_list (list of tuple): List of points, each represented as a tuple of coordinates (x, y, z).
|
| 47 |
+
voxel_size (tuple, optional): Size of the voxel in each dimension (x, y, z). Defaults to (1, 1, 1).
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
numpy.ndarray: Array of cumulative partial path lengths at each point.
|
| 51 |
+
"""
|
| 52 |
# Simple calculation
|
| 53 |
+
section_lengths = [0.0]
|
| 54 |
s = np.array(voxel_size)
|
| 55 |
for i in range(len(point_list)-1):
|
| 56 |
+
section_lengths.append(la.norm(s * (np.array(point_list[i+1]) - np.array(point_list[i]))))
|
| 57 |
+
return np.cumsum(section_lengths)
|
| 58 |
|
| 59 |
|
| 60 |
def visualise_ordering(points_list, dim, wr=5, wc=5):
|
|
|
|
| 263 |
measurements = measure_all_with_sphere(path, hei10, op='mean', R=sphere_xy_radius, z_scale_ratio=scale_ratio)
|
| 264 |
measurements_max = measure_all_with_sphere(path, hei10, op='max', R=sphere_xy_radius, z_scale_ratio=scale_ratio)
|
| 265 |
|
| 266 |
+
|
| 267 |
return vis, measurements, measurements_max
|
| 268 |
|
| 269 |
def extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config):
|
|
|
|
| 290 |
n_paths = len(all_paths)
|
| 291 |
|
| 292 |
data = []
|
| 293 |
+
foci_absolute_intensity, foci_position, foci_position_index, dominated_foci_data, trace_median_intensities, trace_thresholds = analyse_traces(all_paths, path_lengths, measured_traces, config)
|
| 294 |
|
| 295 |
foci_intensities = []
|
| 296 |
for path_foci_abs_int, tmi in zip(foci_absolute_intensity, trace_median_intensities):
|
|
|
|
| 303 |
|
| 304 |
pl = calculate_path_length_partials(all_paths[i], (config['xy_res'], config['xy_res'], config['z_res']))
|
| 305 |
|
| 306 |
+
print(i, len(all_paths[i]), len(pl))
|
| 307 |
+
|
| 308 |
path_data = { 'Cell_ID':cell_id,
|
| 309 |
'Trace': i+1,
|
| 310 |
'SNT_trace_length(um)': path_lengths[i],
|
|
|
|
| 321 |
path_data[f'Foci_{j+1}_relative_intensity'] = (v - trace_median_intensities[i])/mean_intensity
|
| 322 |
data.append(path_data)
|
| 323 |
trace_positions.append(pl)
|
| 324 |
+
return pd.DataFrame(data), foci_absolute_intensity, foci_position_index, dominated_foci_data, trace_thresholds, trace_positions
|
| 325 |
|
| 326 |
|
| 327 |
def analyse_paths(cell_id,
|
|
|
|
| 357 |
vis, m, _ = measure_chrom2(p,foci_stack.transpose(2,1,0), config)
|
| 358 |
all_trace_vis.append(vis)
|
| 359 |
all_m.append(m)
|
| 360 |
+
|
| 361 |
|
| 362 |
+
extracted_peaks, foci_absolute_intensity, foci_pos_index, dominated_foci_data, trace_thresholds, trace_positions = extract_peaks(cell_id, all_paths, path_lengths, all_m, config)
|
|
|
|
| 363 |
|
| 364 |
|
| 365 |
n_cols = 2
|
| 366 |
n_rows = (len(all_paths)+n_cols-1)//n_cols
|
| 367 |
+
fig, ax = plt.subplots(n_rows,n_cols, figsize=(5*n_cols, 3*n_rows))
|
| 368 |
ax = ax.flatten()
|
| 369 |
|
| 370 |
for i, m in enumerate(all_m):
|
| 371 |
ax[i].set_title(f'Trace {i+1}')
|
| 372 |
ax[i].plot(trace_positions[i], m)
|
|
|
|
| 373 |
if len(foci_pos_index[i]):
|
| 374 |
ax[i].plot(trace_positions[i][foci_pos_index[i]], np.array(m)[foci_pos_index[i]], 'rx')
|
| 375 |
+
|
| 376 |
+
if len(dominated_foci_data[i]):
|
| 377 |
+
|
| 378 |
+
dominated_foci_pos_index = [u.idx for u in dominated_foci_data[i]]
|
| 379 |
+
ax[i].plot(trace_positions[i][dominated_foci_pos_index], np.array(m)[dominated_foci_pos_index], color=(0.5,0.5,0.5), marker='o', linestyle='None')
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
if trace_thresholds[i] is not None:
|
| 383 |
ax[i].axhline(trace_thresholds[i], c='r', ls=':')
|
| 384 |
+
ax[i].set_xlabel('Distance from start (um)')
|
| 385 |
+
ax[i].set_ylabel('Intensity')
|
| 386 |
for i in range(len(all_m), n_cols*n_rows):
|
| 387 |
ax[i].axis('off')
|
| 388 |
|
path_analysis/data_preprocess.py
CHANGED
|
@@ -9,45 +9,54 @@ from math import ceil
|
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
-
def
|
| 13 |
"""
|
| 14 |
-
Remove
|
| 15 |
|
| 16 |
Args:
|
| 17 |
-
-
|
| 18 |
-
-
|
| 19 |
-
- intensity (float): The intensity value of the
|
| 20 |
-
-
|
| 21 |
-
- dmin (float, optional): Minimum distance between
|
|
|
|
| 22 |
|
| 23 |
Returns:
|
| 24 |
-
- list of
|
|
|
|
|
|
|
| 25 |
|
| 26 |
Notes:
|
| 27 |
-
- The function uses the L2 norm (Euclidean distance) to compute the distance between
|
| 28 |
-
- When two
|
| 29 |
"""
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
| 33 |
continue
|
| 34 |
-
for j in range(len(
|
| 35 |
if i==j:
|
| 36 |
continue
|
| 37 |
-
if
|
| 38 |
continue
|
| 39 |
-
d = (np.array(
|
| 40 |
d = la.norm(d)
|
| 41 |
if d<dmin:
|
| 42 |
-
hi =
|
| 43 |
-
hj =
|
| 44 |
if hi<hj:
|
| 45 |
-
|
|
|
|
| 46 |
break
|
| 47 |
else:
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
|
| 53 |
@dataclass
|
|
@@ -59,6 +68,17 @@ class CellData(object):
|
|
| 59 |
"""
|
| 60 |
pathdata_list: list
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
@dataclass
|
| 63 |
class PathData(object):
|
| 64 |
"""Represents data related to a specific path in the cell.
|
|
@@ -67,16 +87,23 @@ class PathData(object):
|
|
| 67 |
the defining points, the fluorescence values, and the path length of a specific path.
|
| 68 |
|
| 69 |
Attributes: peaks (list): List of peaks in the path (indicies of positions in points, o_hei10).
|
|
|
|
| 70 |
points (list): List of points defining the path.
|
| 71 |
o_hei10 (list): List of (unnormalized) fluorescence intensity values along the path
|
| 72 |
SC_length (float): Length of the path.
|
| 73 |
|
| 74 |
"""
|
| 75 |
peaks: list
|
|
|
|
| 76 |
points: list
|
| 77 |
o_hei10: list
|
| 78 |
SC_length: float
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
|
| 82 |
def find_peaks2(v, distance=5, prominence=0.5):
|
|
@@ -136,7 +163,7 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
|
|
| 136 |
|
| 137 |
cell_peaks = []
|
| 138 |
|
| 139 |
-
for points,
|
| 140 |
|
| 141 |
# For peak determination normalize each trace to have mean zero and s.d. 1
|
| 142 |
hei10_normalized = (o_hei10 - np.mean(o_hei10))/np.std(o_hei10)
|
|
@@ -158,24 +185,34 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
|
|
| 158 |
to_thin = []
|
| 159 |
for k in range(len(cell_peaks)):
|
| 160 |
for u in range(len(cell_peaks[k][0])):
|
| 161 |
-
to_thin.append((cell_peaks[k][1][u], cell_peaks[k][2][u], (k, u)))
|
| 162 |
|
| 163 |
# Exclude any peak with a nearby brighter peak (on any SC)
|
| 164 |
-
|
| 165 |
|
| 166 |
-
|
| 167 |
# Clean up and remove these peaks
|
| 168 |
new_cell_peaks = []
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
cell_peaks = new_cell_peaks
|
| 178 |
-
|
| 179 |
pd_list = []
|
| 180 |
|
| 181 |
# Save peak positions, absolute HEI10 intensities, and length for each SC
|
|
@@ -184,8 +221,9 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
|
|
| 184 |
points, o_hei10 = all_paths[k], measured_trace_fluorescence[k]
|
| 185 |
|
| 186 |
peaks = cell_peaks[k]
|
|
|
|
| 187 |
|
| 188 |
-
pd = PathData(peaks=peaks, points=points, o_hei10=o_hei10, SC_length=path_lengths[k])
|
| 189 |
pd_list.append(pd)
|
| 190 |
|
| 191 |
cd = CellData(pathdata_list=pd_list)
|
|
@@ -196,9 +234,9 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
|
|
| 196 |
alpha_max = 0.4
|
| 197 |
|
| 198 |
|
| 199 |
-
# Criterion used for identifying peak as a
|
| 200 |
# hei10 levels being above 0.4 time maximum peak level
|
| 201 |
-
def
|
| 202 |
"""
|
| 203 |
Identify and return positions where values in the array `v` exceed a certain threshold.
|
| 204 |
|
|
@@ -212,8 +250,11 @@ def pc(pos, v, alpha=alpha_max):
|
|
| 212 |
Returns:
|
| 213 |
- numpy.ndarray: Array of positions where corresponding values in `v` exceed the threshold.
|
| 214 |
"""
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
def analyse_celldata(cell_data, config):
|
| 219 |
"""
|
|
@@ -226,14 +267,18 @@ def analyse_celldata(cell_data, config):
|
|
| 226 |
'threshold_type' (str) = 'per-trace', 'per-foci'
|
| 227 |
|
| 228 |
Returns:
|
| 229 |
-
tuple: A tuple containing
|
| 230 |
- foci_rel_intensity (list): List of relative intensities for the detected foci.
|
| 231 |
- foci_pos (list): List of absolute positions of the detected foci.
|
| 232 |
- foci_pos_index (list): List of indices of the detected foci.
|
|
|
|
|
|
|
|
|
|
| 233 |
"""
|
| 234 |
foci_abs_intensity = []
|
| 235 |
foci_pos = []
|
| 236 |
foci_pos_index = []
|
|
|
|
| 237 |
trace_median_intensities = []
|
| 238 |
trace_thresholds = []
|
| 239 |
|
|
@@ -254,15 +299,39 @@ def analyse_celldata(cell_data, config):
|
|
| 254 |
h = np.array(path_data.o_hei10)
|
| 255 |
h = h - np.mean(h)
|
| 256 |
h = h/np.std(h)
|
| 257 |
-
# Extract
|
| 258 |
-
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
|
| 261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
foci_pos.append(pos_abs)
|
| 263 |
-
foci_abs_intensity.append(np.array(path_data.o_hei10)[
|
| 264 |
|
| 265 |
-
foci_pos_index.append(
|
| 266 |
trace_median_intensities.append(np.median(path_data.o_hei10))
|
| 267 |
|
| 268 |
elif threshold_type == 'per-cell':
|
|
@@ -286,22 +355,34 @@ def analyse_celldata(cell_data, config):
|
|
| 286 |
h = np.array(path_data.o_hei10)
|
| 287 |
h = h - np.mean(h)
|
| 288 |
|
| 289 |
-
|
|
|
|
|
|
|
|
|
|
| 290 |
|
| 291 |
trace_thresholds.append(np.mean(path_data.o_hei10) + peak_threshold*max_cell_intensity)
|
| 292 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
|
| 294 |
-
pos_abs = (
|
| 295 |
foci_pos.append(pos_abs)
|
| 296 |
-
foci_abs_intensity.append(np.array(path_data.o_hei10)[
|
| 297 |
|
| 298 |
-
foci_pos_index.append(
|
| 299 |
trace_median_intensities.append(np.median(path_data.o_hei10))
|
| 300 |
|
| 301 |
else:
|
| 302 |
raise NotImplementedError
|
| 303 |
|
| 304 |
-
return foci_abs_intensity, foci_pos, foci_pos_index, trace_median_intensities, trace_thresholds
|
| 305 |
|
| 306 |
def analyse_traces(all_paths, path_lengths, measured_trace_fluorescence, config):
|
| 307 |
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
+
def thin_peaks(peak_list, dmin=10, voxel_size=(1,1,1), return_larger_peaks=False):
|
| 13 |
"""
|
| 14 |
+
Remove peaks within a specified distance of each other, retaining the peak with the highest intensity.
|
| 15 |
|
| 16 |
Args:
|
| 17 |
+
- peak_list (list of PeakData): Each element contains:
|
| 18 |
+
- pos (list of float): 3D coordinates of the peak.
|
| 19 |
+
- intensity (float): The intensity value of the peak.
|
| 20 |
+
- key (tuple): A unique identifier or index for the peak (#trace, #peak)
|
| 21 |
+
- dmin (float, optional): Minimum distance between peaks. peaks closer than this threshold will be thinned. Defaults to 10.
|
| 22 |
+
- return_larger_peaks (bool, optional): Indicate larger peak for each thinned peak
|
| 23 |
|
| 24 |
Returns:
|
| 25 |
+
- list of tuples: A list containing keys of the removed peaks.
|
| 26 |
+
if return_larger_peaks
|
| 27 |
+
- list of tuples: A list containing the keys of the larger peak causing the peak to be removed
|
| 28 |
|
| 29 |
Notes:
|
| 30 |
+
- The function uses the L2 norm (Euclidean distance) to compute the distance between peaks.
|
| 31 |
+
- When two peaks are within `dmin` distance, the peak with the lower intensity is removed.
|
| 32 |
"""
|
| 33 |
+
removed_peaks = []
|
| 34 |
+
removed_larger_peaks = []
|
| 35 |
+
for i in range(len(peak_list)):
|
| 36 |
+
if peak_list[i].key in removed_peaks:
|
| 37 |
continue
|
| 38 |
+
for j in range(len(peak_list)):
|
| 39 |
if i==j:
|
| 40 |
continue
|
| 41 |
+
if peak_list[j].key in removed_peaks:
|
| 42 |
continue
|
| 43 |
+
d = (np.array(peak_list[i].pos) - np.array(peak_list[j].pos))*np.array(voxel_size)
|
| 44 |
d = la.norm(d)
|
| 45 |
if d<dmin:
|
| 46 |
+
hi = peak_list[i].intensity
|
| 47 |
+
hj = peak_list[j].intensity
|
| 48 |
if hi<hj:
|
| 49 |
+
removed_peaks.append(peak_list[i].key)
|
| 50 |
+
removed_larger_peaks.append(peak_list[j].key)
|
| 51 |
break
|
| 52 |
else:
|
| 53 |
+
removed_peaks.append(peak_list[j].key)
|
| 54 |
+
removed_larger_peaks.append(peak_list[i].key)
|
| 55 |
+
|
| 56 |
+
if return_larger_peaks:
|
| 57 |
+
return removed_peaks, removed_larger_peaks
|
| 58 |
+
else:
|
| 59 |
+
return removed_peaks
|
| 60 |
|
| 61 |
|
| 62 |
@dataclass
|
|
|
|
| 68 |
"""
|
| 69 |
pathdata_list: list
|
| 70 |
|
| 71 |
+
@dataclass
|
| 72 |
+
class RemovedPeakData(object):
|
| 73 |
+
"""Represents data related to a removed peak
|
| 74 |
+
|
| 75 |
+
Attributes:
|
| 76 |
+
idx (int): Index of peak along path
|
| 77 |
+
dominating_peak (tuple): (path_idx, position along path) for dominating peak
|
| 78 |
+
"""
|
| 79 |
+
idx: int
|
| 80 |
+
dominating_peak: tuple
|
| 81 |
+
|
| 82 |
@dataclass
|
| 83 |
class PathData(object):
|
| 84 |
"""Represents data related to a specific path in the cell.
|
|
|
|
| 87 |
the defining points, the fluorescence values, and the path length of a specific path.
|
| 88 |
|
| 89 |
Attributes: peaks (list): List of peaks in the path (indicies of positions in points, o_hei10).
|
| 90 |
+
removed_peaks (list): List of peaks in the path which have been removed because of a nearby larger peak
|
| 91 |
points (list): List of points defining the path.
|
| 92 |
o_hei10 (list): List of (unnormalized) fluorescence intensity values along the path
|
| 93 |
SC_length (float): Length of the path.
|
| 94 |
|
| 95 |
"""
|
| 96 |
peaks: list
|
| 97 |
+
removed_peaks: list
|
| 98 |
points: list
|
| 99 |
o_hei10: list
|
| 100 |
SC_length: float
|
| 101 |
|
| 102 |
+
@dataclass
|
| 103 |
+
class PeakData(object):
|
| 104 |
+
pos: tuple
|
| 105 |
+
intensity: float
|
| 106 |
+
key: tuple
|
| 107 |
|
| 108 |
|
| 109 |
def find_peaks2(v, distance=5, prominence=0.5):
|
|
|
|
| 163 |
|
| 164 |
cell_peaks = []
|
| 165 |
|
| 166 |
+
for points, o_hei10 in zip(all_paths, measured_trace_fluorescence):
|
| 167 |
|
| 168 |
# For peak determination normalize each trace to have mean zero and s.d. 1
|
| 169 |
hei10_normalized = (o_hei10 - np.mean(o_hei10))/np.std(o_hei10)
|
|
|
|
| 185 |
to_thin = []
|
| 186 |
for k in range(len(cell_peaks)):
|
| 187 |
for u in range(len(cell_peaks[k][0])):
|
| 188 |
+
to_thin.append(PeakData(pos=cell_peaks[k][1][u], intensity=cell_peaks[k][2][u], key=(k, u)))
|
| 189 |
|
| 190 |
# Exclude any peak with a nearby brighter peak (on any SC)
|
| 191 |
+
removed_peaks, removed_larger_peaks = thin_peaks(to_thin, return_larger_peaks=True)
|
| 192 |
|
|
|
|
| 193 |
# Clean up and remove these peaks
|
| 194 |
new_cell_peaks = []
|
| 195 |
+
removed_cell_peaks = []
|
| 196 |
+
removed_cell_peaks_larger = []
|
| 197 |
+
for path_idx in range(len(cell_peaks)):
|
| 198 |
+
path_retained_peaks = []
|
| 199 |
+
path_removed_peaks = []
|
| 200 |
+
path_peaks = cell_peaks[path_idx][0]
|
| 201 |
+
|
| 202 |
+
for peak_idx in range(len(path_peaks)):
|
| 203 |
+
if (path_idx, peak_idx) not in removed_peaks:
|
| 204 |
+
path_retained_peaks.append(path_peaks[peak_idx])
|
| 205 |
+
else:
|
| 206 |
+
# What's the larger point?
|
| 207 |
+
idx = removed_peaks.index((path_idx, peak_idx))
|
| 208 |
+
larger_path, larger_idx = removed_larger_peaks[idx]
|
| 209 |
+
path_removed_peaks.append(RemovedPeakData(idx=path_peaks[peak_idx], dominating_peak=(larger_path, cell_peaks[larger_path][0][larger_idx])))
|
| 210 |
+
###
|
| 211 |
+
|
| 212 |
+
new_cell_peaks.append(path_retained_peaks)
|
| 213 |
+
removed_cell_peaks.append(path_removed_peaks)
|
| 214 |
|
| 215 |
cell_peaks = new_cell_peaks
|
|
|
|
| 216 |
pd_list = []
|
| 217 |
|
| 218 |
# Save peak positions, absolute HEI10 intensities, and length for each SC
|
|
|
|
| 221 |
points, o_hei10 = all_paths[k], measured_trace_fluorescence[k]
|
| 222 |
|
| 223 |
peaks = cell_peaks[k]
|
| 224 |
+
removed_peaks = removed_cell_peaks[k]
|
| 225 |
|
| 226 |
+
pd = PathData(peaks=peaks, removed_peaks=removed_peaks, points=points, o_hei10=o_hei10, SC_length=path_lengths[k])
|
| 227 |
pd_list.append(pd)
|
| 228 |
|
| 229 |
cd = CellData(pathdata_list=pd_list)
|
|
|
|
| 234 |
alpha_max = 0.4
|
| 235 |
|
| 236 |
|
| 237 |
+
# Criterion used for identifying peak as a focus - normalized (with mean and s.d.)
|
| 238 |
# hei10 levels being above 0.4 time maximum peak level
|
| 239 |
+
def focus_criterion(pos, v, alpha=alpha_max):
|
| 240 |
"""
|
| 241 |
Identify and return positions where values in the array `v` exceed a certain threshold.
|
| 242 |
|
|
|
|
| 250 |
Returns:
|
| 251 |
- numpy.ndarray: Array of positions where corresponding values in `v` exceed the threshold.
|
| 252 |
"""
|
| 253 |
+
if len(v):
|
| 254 |
+
idx = (v>=alpha*np.max(v))
|
| 255 |
+
return np.array(pos[idx])
|
| 256 |
+
else:
|
| 257 |
+
return np.array([], dtype=np.int32)
|
| 258 |
|
| 259 |
def analyse_celldata(cell_data, config):
|
| 260 |
"""
|
|
|
|
| 267 |
'threshold_type' (str) = 'per-trace', 'per-foci'
|
| 268 |
|
| 269 |
Returns:
|
| 270 |
+
tuple: A tuple containing:
|
| 271 |
- foci_rel_intensity (list): List of relative intensities for the detected foci.
|
| 272 |
- foci_pos (list): List of absolute positions of the detected foci.
|
| 273 |
- foci_pos_index (list): List of indices of the detected foci.
|
| 274 |
+
- dominated_foci_data (list): List of RemovedPeakData indicating positions of removed peaks and the index of the larger peak
|
| 275 |
+
- trace_median_intensities (list): Per-trace median intensity
|
| 276 |
+
- trace_thresholds (list): Per-trace absolute threshold for calling peaks as foci
|
| 277 |
"""
|
| 278 |
foci_abs_intensity = []
|
| 279 |
foci_pos = []
|
| 280 |
foci_pos_index = []
|
| 281 |
+
dominated_foci_data = []
|
| 282 |
trace_median_intensities = []
|
| 283 |
trace_thresholds = []
|
| 284 |
|
|
|
|
| 299 |
h = np.array(path_data.o_hei10)
|
| 300 |
h = h - np.mean(h)
|
| 301 |
h = h/np.std(h)
|
| 302 |
+
# Extract foci according to criterion
|
| 303 |
+
foci_idx = focus_criterion(peaks, h[peaks], peak_threshold)
|
| 304 |
+
print('peaks', peaks, h[peaks], foci_idx, np.mean(path_data.o_hei10))
|
| 305 |
+
|
| 306 |
+
#
|
| 307 |
+
removed_peaks = path_data.removed_peaks
|
| 308 |
+
removed_peaks_idx = np.array([u.idx for u in removed_peaks], dtype=np.int32)
|
| 309 |
+
|
| 310 |
|
| 311 |
+
if len(peaks):
|
| 312 |
+
trace_thresholds.append((1-peak_threshold)*np.mean(path_data.o_hei10) + peak_threshold*np.max(np.array(path_data.o_hei10)[peaks]))
|
| 313 |
+
else:
|
| 314 |
+
trace_thresholds.append(None)
|
| 315 |
+
|
| 316 |
+
if len(removed_peaks):
|
| 317 |
+
if len(peaks):
|
| 318 |
+
threshold = (1-peak_threshold)*np.mean(path_data.o_hei10) + peak_threshold*np.max(np.array(path_data.o_hei10)[peaks])
|
| 319 |
+
else:
|
| 320 |
+
threshold = float('-inf')
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
removed_peak_heights = np.array(path_data.o_hei10)[removed_peaks_idx]
|
| 324 |
+
dominated_foci_idx = np.where(removed_peak_heights>threshold)[0]
|
| 325 |
+
|
| 326 |
+
dominated_foci_data.append([removed_peaks[i] for i in dominated_foci_idx])
|
| 327 |
+
else:
|
| 328 |
+
dominated_foci_data.append([])
|
| 329 |
+
|
| 330 |
+
pos_abs = (foci_idx/len(path_data.points))*path_data.SC_length
|
| 331 |
foci_pos.append(pos_abs)
|
| 332 |
+
foci_abs_intensity.append(np.array(path_data.o_hei10)[foci_idx])
|
| 333 |
|
| 334 |
+
foci_pos_index.append(foci_idx)
|
| 335 |
trace_median_intensities.append(np.median(path_data.o_hei10))
|
| 336 |
|
| 337 |
elif threshold_type == 'per-cell':
|
|
|
|
| 355 |
h = np.array(path_data.o_hei10)
|
| 356 |
h = h - np.mean(h)
|
| 357 |
|
| 358 |
+
foci_idx = peaks[h[peaks]>peak_threshold*max_cell_intensity]
|
| 359 |
+
|
| 360 |
+
removed_peaks = path_data.removed_peaks
|
| 361 |
+
removed_peaks_idx = np.array([u.idx for u in removed_peaks], dtype=np.int32)
|
| 362 |
|
| 363 |
trace_thresholds.append(np.mean(path_data.o_hei10) + peak_threshold*max_cell_intensity)
|
| 364 |
|
| 365 |
+
if len(removed_peaks):
|
| 366 |
+
threshold = np.mean(path_data.o_hei10) + peak_threshold*max_cell_intensity
|
| 367 |
+
|
| 368 |
+
removed_peak_heights = np.array(path_data.o_hei10)[removed_peaks_idx]
|
| 369 |
+
dominated_foci_idx = np.where(removed_peak_heights>threshold)[0]
|
| 370 |
+
|
| 371 |
+
dominated_foci_data.append([removed_peaks[i] for i in dominated_foci_idx])
|
| 372 |
+
else:
|
| 373 |
+
dominated_foci_data.append([])
|
| 374 |
|
| 375 |
+
pos_abs = (foci_idx/len(path_data.points))*path_data.SC_length
|
| 376 |
foci_pos.append(pos_abs)
|
| 377 |
+
foci_abs_intensity.append(np.array(path_data.o_hei10)[foci_idx])
|
| 378 |
|
| 379 |
+
foci_pos_index.append(foci_idx)
|
| 380 |
trace_median_intensities.append(np.median(path_data.o_hei10))
|
| 381 |
|
| 382 |
else:
|
| 383 |
raise NotImplementedError
|
| 384 |
|
| 385 |
+
return foci_abs_intensity, foci_pos, foci_pos_index, dominated_foci_data, trace_median_intensities, trace_thresholds
|
| 386 |
|
| 387 |
def analyse_traces(all_paths, path_lengths, measured_trace_fluorescence, config):
|
| 388 |
|
tests/test_analyse.py
CHANGED
|
@@ -1,9 +1,68 @@
|
|
| 1 |
|
|
|
|
| 2 |
from path_analysis.analyse import *
|
| 3 |
import numpy as np
|
| 4 |
from math import pi
|
| 5 |
import xml.etree.ElementTree as ET
|
|
|
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
def test_get_paths_from_traces_file():
|
| 9 |
# Mock the XML traces file content
|
|
@@ -221,3 +280,34 @@ def test_make_sphere_equal():
|
|
| 221 |
assert abs(np.sum(sphere)-4/3*pi*R**3)<10, f"Expected approximate volume to be correct"
|
| 222 |
assert (sphere[R,R,0] == 1), f"Expected centre point on top plane to be within sphere"
|
| 223 |
assert (sphere[R+1,R,0] == 0), f"Expected point next to centre on top plane to be outside sphere"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
|
| 2 |
+
import pytest
|
| 3 |
from path_analysis.analyse import *
|
| 4 |
import numpy as np
|
| 5 |
from math import pi
|
| 6 |
import xml.etree.ElementTree as ET
|
| 7 |
+
from PIL import ImageChops
|
| 8 |
|
| 9 |
+
def test_draw_paths_no_error():
|
| 10 |
+
all_paths = [[[0, 0], [1, 1]], [[2, 2], [3, 3]]]
|
| 11 |
+
foci_stack = np.zeros((5, 5, 5))
|
| 12 |
+
foci_stack[0,0,0] = 1.0
|
| 13 |
+
foci_index = [[0], [1]]
|
| 14 |
+
r = 3
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
im = draw_paths(all_paths, foci_stack, foci_index, r)
|
| 18 |
+
except Exception as e:
|
| 19 |
+
pytest.fail(f"draw_paths raised an exception: {e}")
|
| 20 |
+
|
| 21 |
+
def test_draw_paths_image_size():
|
| 22 |
+
all_paths = [[[0, 0], [1, 1]], [[2, 2], [3, 3]]]
|
| 23 |
+
foci_stack = np.zeros((5, 5, 5))
|
| 24 |
+
foci_stack[0,0,0] = 1.0
|
| 25 |
+
|
| 26 |
+
foci_index = [[0], [1]]
|
| 27 |
+
r = 3
|
| 28 |
+
|
| 29 |
+
im = draw_paths(all_paths, foci_stack, foci_index, r)
|
| 30 |
+
assert im.size == (5, 5), f"Expected image size (5, 5), got {im.size}"
|
| 31 |
+
|
| 32 |
+
def test_draw_paths_image_modified():
|
| 33 |
+
all_paths = [[[0, 0], [1, 1]], [[2, 2], [3, 3]]]
|
| 34 |
+
foci_stack = np.zeros((5, 5, 5))
|
| 35 |
+
foci_stack[0,0,0] = 1.0
|
| 36 |
+
foci_index = [[0], [1]]
|
| 37 |
+
r = 3
|
| 38 |
+
|
| 39 |
+
im = draw_paths(all_paths, foci_stack, foci_index, r)
|
| 40 |
+
blank_image = Image.new("RGB", (5, 5), "black")
|
| 41 |
+
|
| 42 |
+
# Check if the image is not entirely black (i.e., has been modified)
|
| 43 |
+
diff = ImageChops.difference(im, blank_image)
|
| 44 |
+
assert diff.getbbox() is not None, "The image has not been modified"
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def test_calculate_path_length_partials_default_voxel():
|
| 49 |
+
point_list = [(0, 0, 0), (1, 0, 0), (1, 1, 1)]
|
| 50 |
+
expected_result = np.array([0.0, 1.0, 1.0+np.sqrt(2)])
|
| 51 |
+
result = calculate_path_length_partials(point_list)
|
| 52 |
+
np.testing.assert_allclose(result, expected_result, atol=1e-5)
|
| 53 |
+
|
| 54 |
+
def test_calculate_path_length_partials_custom_voxel():
|
| 55 |
+
point_list = [(0, 0, 0), (1, 0, 0), (1, 1, 0)]
|
| 56 |
+
voxel_size = (1, 2, 1)
|
| 57 |
+
expected_result = np.array([0.0, 1.0, 3.0])
|
| 58 |
+
result = calculate_path_length_partials(point_list, voxel_size=voxel_size)
|
| 59 |
+
np.testing.assert_allclose(result, expected_result, atol=1e-5)
|
| 60 |
+
|
| 61 |
+
def test_calculate_path_length_partials_single_point():
|
| 62 |
+
point_list = [(0, 0, 0)]
|
| 63 |
+
expected_result = np.array([0.0])
|
| 64 |
+
result = calculate_path_length_partials(point_list)
|
| 65 |
+
np.testing.assert_allclose(result, expected_result, atol=1e-5)
|
| 66 |
|
| 67 |
def test_get_paths_from_traces_file():
|
| 68 |
# Mock the XML traces file content
|
|
|
|
| 280 |
assert abs(np.sum(sphere)-4/3*pi*R**3)<10, f"Expected approximate volume to be correct"
|
| 281 |
assert (sphere[R,R,0] == 1), f"Expected centre point on top plane to be within sphere"
|
| 282 |
assert (sphere[R+1,R,0] == 0), f"Expected point next to centre on top plane to be outside sphere"
|
| 283 |
+
|
| 284 |
+
import pandas as pd
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
# 1. Test basic functionality
|
| 288 |
+
def test_extract_peaks_basic():
|
| 289 |
+
cell_id = 1
|
| 290 |
+
all_paths = [[[0, 0], [1, 1]]]
|
| 291 |
+
path_lengths = [1.41] # length of the above path
|
| 292 |
+
measured_traces = [[100, 200]] # fluorescence along the path
|
| 293 |
+
config = {'peak_threshold': 0.4, 'sphere_radius': 2, 'xy_res': 1, 'z_res': 1, 'use_corrected_positions': True}
|
| 294 |
+
|
| 295 |
+
df, foci_abs_int, foci_pos_idx, _, _, _ = extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config)
|
| 296 |
+
|
| 297 |
+
# Now add your assertions to validate the result
|
| 298 |
+
assert len(df) == 1, "Expected one row in DataFrame"
|
| 299 |
+
assert df['Cell_ID'].iloc[0] == cell_id, "Unexpected cell_id"
|
| 300 |
+
# Add more assertions here based on expected values
|
| 301 |
+
|
| 302 |
+
# 2. Test multiple paths
|
| 303 |
+
def test_extract_peaks_multiple_paths():
|
| 304 |
+
cell_id = 1
|
| 305 |
+
all_paths = [[[0, 0], [1, 1]], [[1, 1], [2, 2]]]
|
| 306 |
+
path_lengths = [1.41, 1.41]
|
| 307 |
+
measured_traces = [[100, 200], [100, 150]]
|
| 308 |
+
config = {'peak_threshold': 0.4, 'sphere_radius': 2, 'xy_res': 1, 'z_res': 1, 'use_corrected_positions': True}
|
| 309 |
+
|
| 310 |
+
df, _, _, _, _, _ = extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config)
|
| 311 |
+
|
| 312 |
+
assert len(df) == 2, "Expected two rows in DataFrame"
|
| 313 |
+
# Add more assertions here
|
tests/test_preprocess.py
CHANGED
|
@@ -2,18 +2,19 @@ from path_analysis.data_preprocess import *
|
|
| 2 |
import numpy as np
|
| 3 |
import pytest
|
| 4 |
|
|
|
|
| 5 |
def test_thin_points():
|
| 6 |
# Define a sample point list
|
| 7 |
points = [
|
| 8 |
-
([0, 0, 0], 10, 0),
|
| 9 |
-
([1, 1, 1], 8, 1),
|
| 10 |
-
([10, 10, 10], 12, 2),
|
| 11 |
-
([10.5, 10.5, 10.5], 5, 3),
|
| 12 |
-
([20, 20, 20], 15, 4)
|
| 13 |
]
|
| 14 |
|
| 15 |
# Call the thin_points function with dmin=5 (for example)
|
| 16 |
-
removed_indices =
|
| 17 |
|
| 18 |
# Check results
|
| 19 |
# Point at index 1 ([1, 1, 1]) should be removed since it's within 5 units distance of point at index 0 and has lower intensity.
|
|
@@ -22,12 +23,12 @@ def test_thin_points():
|
|
| 22 |
|
| 23 |
# Another simple test to check if function does nothing when points are far apart
|
| 24 |
far_points = [
|
| 25 |
-
([0, 0, 0], 10, 0),
|
| 26 |
-
([100, 100, 100], 12, 1),
|
| 27 |
-
([200, 200, 200], 15, 2)
|
| 28 |
]
|
| 29 |
|
| 30 |
-
removed_indices_far =
|
| 31 |
assert len(removed_indices_far) == 0 # Expect no points to be removed
|
| 32 |
|
| 33 |
|
|
@@ -72,29 +73,32 @@ def test_find_peaks2():
|
|
| 72 |
assert peaks == [1] # Only the peak at position 1 meets the prominence threshold
|
| 73 |
|
| 74 |
|
| 75 |
-
def
|
| 76 |
pos = np.array([0, 1, 2, 3, 4, 6])
|
| 77 |
values = np.array([0.1, 0.5, 0.2, 0.8, 0.3, 0.9])
|
| 78 |
|
| 79 |
# Basic test
|
| 80 |
-
assert np.array_equal(
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
# Test with custom alpha
|
| 83 |
-
assert np.array_equal(
|
| 84 |
|
| 85 |
# Test with a larger alpha
|
| 86 |
-
assert np.array_equal(
|
| 87 |
|
| 88 |
# Test with all values below threshold
|
| 89 |
values = np.array([0.1, 0.2, 0.3, 0.4])
|
| 90 |
|
| 91 |
-
assert np.array_equal(
|
| 92 |
|
| 93 |
@pytest.fixture
|
| 94 |
def mock_data():
|
| 95 |
all_paths = [ [ (0,0,0), (0,2,0), (0,5,0), (0,10,0), (0,15,0), (0,20,0)], [ (1,20,0), (1,20,10), (1,20,20) ] ] # Mock paths
|
| 96 |
path_lengths = [ 2.2, 2.3 ] # Mock path lengths
|
| 97 |
-
measured_trace_fluorescence = [ [100, 8, 3, 2, 3,
|
| 98 |
return all_paths, path_lengths, measured_trace_fluorescence
|
| 99 |
|
| 100 |
def test_process_cell_traces_return_type(mock_data):
|
|
@@ -117,23 +121,30 @@ def test_process_cell_traces_pathdata_path_lengths(mock_data):
|
|
| 117 |
def test_process_cell_traces_peaks(mock_data):
|
| 118 |
all_paths, path_lengths, measured_trace_fluorescence = mock_data
|
| 119 |
result = process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence)
|
|
|
|
| 120 |
peaks = [p.peaks for p in result.pathdata_list]
|
| 121 |
assert peaks == [[0,5],[]]
|
| 122 |
|
| 123 |
# Mock data
|
| 124 |
@pytest.fixture
|
| 125 |
def mock_celldata():
|
| 126 |
-
pathdata1 = PathData(peaks=[0, 5], points=[(0,0,0), (0,2,0), (0,5,0), (0,10,0), (0,15,0), (0,20,0)], o_hei10=[100, 8, 3, 2, 3,
|
| 127 |
-
pathdata2 = PathData(peaks=[
|
| 128 |
return CellData(pathdata_list=[pathdata1, pathdata2])
|
| 129 |
|
| 130 |
-
def
|
| 131 |
-
|
| 132 |
-
assert len(
|
| 133 |
-
assert len(
|
| 134 |
-
assert len(
|
| 135 |
-
|
| 136 |
|
|
|
|
| 137 |
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
|
|
|
| 2 |
import numpy as np
|
| 3 |
import pytest
|
| 4 |
|
| 5 |
+
|
| 6 |
def test_thin_points():
|
| 7 |
# Define a sample point list
|
| 8 |
points = [
|
| 9 |
+
PeakData([0, 0, 0], 10, 0),
|
| 10 |
+
PeakData([1, 1, 1], 8, 1),
|
| 11 |
+
PeakData([10, 10, 10], 12, 2),
|
| 12 |
+
PeakData([10.5, 10.5, 10.5], 5, 3),
|
| 13 |
+
PeakData([20, 20, 20], 15, 4)
|
| 14 |
]
|
| 15 |
|
| 16 |
# Call the thin_points function with dmin=5 (for example)
|
| 17 |
+
removed_indices = thin_peaks(points, dmin=5)
|
| 18 |
|
| 19 |
# Check results
|
| 20 |
# Point at index 1 ([1, 1, 1]) should be removed since it's within 5 units distance of point at index 0 and has lower intensity.
|
|
|
|
| 23 |
|
| 24 |
# Another simple test to check if function does nothing when points are far apart
|
| 25 |
far_points = [
|
| 26 |
+
PeakData([0, 0, 0], 10, 0),
|
| 27 |
+
PeakData([100, 100, 100], 12, 1),
|
| 28 |
+
PeakData([200, 200, 200], 15, 2)
|
| 29 |
]
|
| 30 |
|
| 31 |
+
removed_indices_far = thin_peaks(far_points, dmin=5)
|
| 32 |
assert len(removed_indices_far) == 0 # Expect no points to be removed
|
| 33 |
|
| 34 |
|
|
|
|
| 73 |
assert peaks == [1] # Only the peak at position 1 meets the prominence threshold
|
| 74 |
|
| 75 |
|
| 76 |
+
def test_focus_criterion():
|
| 77 |
pos = np.array([0, 1, 2, 3, 4, 6])
|
| 78 |
values = np.array([0.1, 0.5, 0.2, 0.8, 0.3, 0.9])
|
| 79 |
|
| 80 |
# Basic test
|
| 81 |
+
assert np.array_equal(focus_criterion(pos, values), np.array([1, 3, 6])) # only values 0.8 and 0.9 exceed 0.4 times the max (which is 0.9)
|
| 82 |
+
|
| 83 |
+
# Empty test
|
| 84 |
+
assert np.array_equal(focus_criterion(np.array([]), np.array([])), np.array([]))
|
| 85 |
|
| 86 |
# Test with custom alpha
|
| 87 |
+
assert np.array_equal(focus_criterion(pos, values, alpha=0.5), np.array([1, 3, 6]))
|
| 88 |
|
| 89 |
# Test with a larger alpha
|
| 90 |
+
assert np.array_equal(focus_criterion(pos, values, alpha=1.0), [6]) # No values exceed the maximum value itself
|
| 91 |
|
| 92 |
# Test with all values below threshold
|
| 93 |
values = np.array([0.1, 0.2, 0.3, 0.4])
|
| 94 |
|
| 95 |
+
assert np.array_equal(focus_criterion(pos[:4], values), [1,2,3]) # All values are below 0.4 times the max (which is 0.4)
|
| 96 |
|
| 97 |
@pytest.fixture
|
| 98 |
def mock_data():
|
| 99 |
all_paths = [ [ (0,0,0), (0,2,0), (0,5,0), (0,10,0), (0,15,0), (0,20,0)], [ (1,20,0), (1,20,10), (1,20,20) ] ] # Mock paths
|
| 100 |
path_lengths = [ 2.2, 2.3 ] # Mock path lengths
|
| 101 |
+
measured_trace_fluorescence = [ [100, 8, 3, 2, 3, 49], [38, 2, 20] ] # Mock fluorescence data
|
| 102 |
return all_paths, path_lengths, measured_trace_fluorescence
|
| 103 |
|
| 104 |
def test_process_cell_traces_return_type(mock_data):
|
|
|
|
| 121 |
def test_process_cell_traces_peaks(mock_data):
|
| 122 |
all_paths, path_lengths, measured_trace_fluorescence = mock_data
|
| 123 |
result = process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence)
|
| 124 |
+
print(result)
|
| 125 |
peaks = [p.peaks for p in result.pathdata_list]
|
| 126 |
assert peaks == [[0,5],[]]
|
| 127 |
|
| 128 |
# Mock data
|
| 129 |
@pytest.fixture
|
| 130 |
def mock_celldata():
|
| 131 |
+
pathdata1 = PathData(peaks=[0, 5], points=[(0,0,0), (0,2,0), (0,5,0), (0,10,0), (0,15,0), (0,20,0)], removed_peaks=[], o_hei10=[100, 8, 3, 2, 3, 69], SC_length=2.2)
|
| 132 |
+
pathdata2 = PathData(peaks=[2], points=[(1,20,0), (1,20,10), (1,20,20) ], removed_peaks=[RemovedPeakData(0, (0,5))], o_hei10=[38, 2, 20], SC_length=2.3)
|
| 133 |
return CellData(pathdata_list=[pathdata1, pathdata2])
|
| 134 |
|
| 135 |
+
def test_analyse_celldata(mock_celldata):
|
| 136 |
+
data_frame, foci_absolute_intensity, foci_position_index, dominated_foci_data, trace_median_intensity, trace_thresholds = analyse_celldata(mock_celldata, {'peak_threshold': 0.4, 'threshold_type':'per-trace'})
|
| 137 |
+
assert len(data_frame) == len(mock_celldata.pathdata_list), "Mismatch in dataframe length"
|
| 138 |
+
assert len(foci_absolute_intensity) == len(mock_celldata.pathdata_list), "Mismatch in relative intensities length"
|
| 139 |
+
assert len(foci_position_index) == len(mock_celldata.pathdata_list), "Mismatch in positions length"
|
|
|
|
| 140 |
|
| 141 |
+
assert list(map(list, foci_position_index)) == [[0, 5], [2]]
|
| 142 |
|
| 143 |
|
| 144 |
+
def test_analyse_celldata_per_cell(mock_celldata):
|
| 145 |
+
data_frame, foci_absolute_intensity, foci_position_index, dominated_foci_data, trace_median_intensity, trace_thresholds = analyse_celldata(mock_celldata, {'peak_threshold': 0.4, 'threshold_type':'per-cell'})
|
| 146 |
+
assert len(data_frame) == len(mock_celldata.pathdata_list), "Mismatch in relative intensities length"
|
| 147 |
+
assert len(foci_absolute_intensity) == len(mock_celldata.pathdata_list), "Mismatch in positions length"
|
| 148 |
+
assert len(foci_position_index) == len(mock_celldata.pathdata_list), "Mismatch in position indices length"
|
| 149 |
+
assert list(map(list, foci_position_index)) == [[0, 5], []]
|
| 150 |
|