JFoz commited on
Commit
6756e43
1 Parent(s): 016c3b8

Show screening, permit screening distance to be changed

Browse files
app.py CHANGED
@@ -9,6 +9,7 @@ import numpy as np
9
  # Function to preview the imported image
10
  def preview_image(file1):
11
  if file1:
 
12
  im = imread(file1.name)
13
  print(im.ndim, im.shape)
14
  if im.ndim>2:
@@ -41,6 +42,7 @@ with gr.Blocks() as demo:
41
 
42
  threshold_type = gr.Radio(["per-trace", "per-cell"], label="Threshold-type", value="per-trace", interactive=True)
43
  use_corrected_positions = gr.Checkbox(label="Correct foci position measurements", value=True, interactive=True)
 
44
 
45
 
46
  # The output column showing the result of processing
@@ -52,15 +54,16 @@ with gr.Blocks() as demo:
52
  data_file_output=gr.File(label="Output data file (.csv)")
53
 
54
 
55
- def process(cellid_input, image_input, path_input, sphere_radius, peak_threshold, xy_res, z_res, threshold_type, use_corrected_positions):
56
 
57
  config = { 'sphere_radius': sphere_radius,
58
  'peak_threshold': peak_threshold,
59
  'xy_res': xy_res,
60
  'z_res': z_res,
61
  'threshold_type': threshold_type,
62
- 'use_corrected_positions': use_corrected_positions
63
- }
 
64
 
65
 
66
  paths, traces, fig, extracted_peaks = analyse_paths(cellid_input, image_input.name, path_input.name, config)
@@ -71,7 +74,7 @@ with gr.Blocks() as demo:
71
 
72
  with gr.Row():
73
  greet_btn = gr.Button("Process")
74
- greet_btn.click(fn=process, inputs=[cellid_input, image_input, path_input, sphere_radius, peak_threshold, xy_res, z_res, threshold_type, use_corrected_positions], outputs=[trace_output, image_output, plot_output, data_output, data_file_output], api_name="process")
75
 
76
 
77
  if __name__ == "__main__":
 
9
  # Function to preview the imported image
10
  def preview_image(file1):
11
  if file1:
12
+ print('Uploading image', file1.name)
13
  im = imread(file1.name)
14
  print(im.ndim, im.shape)
15
  if im.ndim>2:
 
42
 
43
  threshold_type = gr.Radio(["per-trace", "per-cell"], label="Threshold-type", value="per-trace", interactive=True)
44
  use_corrected_positions = gr.Checkbox(label="Correct foci position measurements", value=True, interactive=True)
45
+ screening_distance = gr.Number(label='Screening distance (voxels)', value=10, interactive=True)
46
 
47
 
48
  # The output column showing the result of processing
 
54
  data_file_output=gr.File(label="Output data file (.csv)")
55
 
56
 
57
+ def process(cellid_input, image_input, path_input, sphere_radius, peak_threshold, xy_res, z_res, threshold_type, use_corrected_positions, screening_distance):
58
 
59
  config = { 'sphere_radius': sphere_radius,
60
  'peak_threshold': peak_threshold,
61
  'xy_res': xy_res,
62
  'z_res': z_res,
63
  'threshold_type': threshold_type,
64
+ 'use_corrected_positions': use_corrected_positions,
65
+ 'screening_distance': screening_distance,
66
+ }
67
 
68
 
69
  paths, traces, fig, extracted_peaks = analyse_paths(cellid_input, image_input.name, path_input.name, config)
 
74
 
75
  with gr.Row():
76
  greet_btn = gr.Button("Process")
77
+ greet_btn.click(fn=process, inputs=[cellid_input, image_input, path_input, sphere_radius, peak_threshold, xy_res, z_res, threshold_type, use_corrected_positions, screening_distance], outputs=[trace_output, image_output, plot_output, data_output, data_file_output], api_name="process")
78
 
79
 
80
  if __name__ == "__main__":
path_analysis/analyse.py CHANGED
@@ -53,6 +53,7 @@ def calculate_path_length_partials(point_list, voxel_size=(1,1,1)):
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
 
@@ -89,7 +90,7 @@ def visualise_ordering(points_list, dim, wr=5, wc=5):
89
  col_map = [(255,0,0), (0,255,0), (0,0,255), (255,255,0), (255,0,255), (0,255,255),
90
  (255,127,0), (255, 0, 127), (127, 255, 0), (0, 255, 127), (127,0,255), (0,127,255)]
91
 
92
- def draw_paths(all_paths, foci_stack, foci_index=None, r=3):
93
  """
94
  Draws paths on the provided image stack and overlays markers for the foci
95
 
@@ -98,7 +99,7 @@ def draw_paths(all_paths, foci_stack, foci_index=None, r=3):
98
  foci_stack (np.array): 3D numpy array representing the image stack.
99
  foci_index (list, optional): List of list of focus indices (along each path). Defaults to None.
100
  r (int, optional): Radius for the ellipse or line drawing around the focus. Defaults to 3.
101
-
102
  Returns:
103
  PIL.Image.Image: An image with the drawn paths.
104
  """
@@ -110,13 +111,20 @@ def draw_paths(all_paths, foci_stack, foci_index=None, r=3):
110
  for i, (p, col) in enumerate(zip(all_paths, cycle(col_map))):
111
  draw.line([(u[0], u[1]) for u in p], fill=col)
112
  draw.text((p[0][0], p[0][1]), str(i+1), fill=col)
 
 
 
 
 
 
 
 
113
  if foci_index is not None:
114
  for i, (idx, p, col) in enumerate(zip(foci_index, all_paths, cycle(col_map))):
115
  if len(idx):
116
  for j in idx:
117
  draw.line((int(p[j][0]-r), int(p[j][1]), int(p[j][0]+r), int(p[j][1])), fill=col, width=2)
118
  draw.line((int(p[j][0]), int(p[j][1]-r), int(p[j][0]), int(p[j][1]+r)), fill=col, width=2)
119
-
120
  return im
121
 
122
 
@@ -164,8 +172,7 @@ def make_mask_s(p, melem, measure_stack):
164
  #
165
 
166
  R = [u//2 for u in melem.shape]
167
-
168
-
169
  r, c, z = p
170
 
171
  mask = np.zeros(melem.shape)
@@ -210,7 +217,8 @@ def make_sphere(R=5, z_scale_ratio=2.3):
210
  Generate a binary representation of a sphere in 3D space.
211
 
212
  Args:
213
- R (int, optional): Radius of the sphere. Default is 5.
 
214
  z_scale_ratio (float, optional): Scaling factor for the z-axis. Default is 2.3.
215
 
216
  Returns:
@@ -243,25 +251,26 @@ def measure_all_with_sphere(points_list, measure_stack, op='mean', R=5, z_scale_
243
 
244
 
245
  # Measure fluorescence levels along ordered skeleton
246
- def measure_chrom2(path, hei10, config):
247
  """
248
  Measure fluorescence levels along an ordered skeleton.
249
 
250
  Args:
251
  path (list): List of ordered path points (r, c, z).
252
- hei10 (numpy.ndarray): 3D fluorescence data.
253
  config (dict): Configuration dictionary containing 'z_res', 'xy_res', and 'sphere_radius' values.
254
 
255
  Returns:
256
  tuple: A tuple containing the visualization, mean measurements, and max measurements along the path.
257
  """
 
258
  scale_ratio = config['z_res']/config['xy_res']
259
  sphere_xy_radius = int(math.ceil(config['sphere_radius']/config['xy_res']))
260
 
261
- vis = visualise_ordering(path, dim=hei10.shape, wr=sphere_xy_radius, wc=sphere_xy_radius)
262
 
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
@@ -290,20 +299,22 @@ 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):
297
  foci_intensities.extend(list(path_foci_abs_int - tmi))
298
-
 
299
  mean_intensity = np.mean(foci_intensities)
300
  trace_positions = []
301
 
302
  for i in range(n_paths):
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,
@@ -311,17 +322,23 @@ def extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config):
311
  'Measured_trace_length(um)': pl[-1],
312
  'Trace_median_intensity': trace_median_intensities[i],
313
  'Detection_sphere_radius(um)': config['sphere_radius'],
314
- 'Foci_ID_threshold': config['peak_threshold'] }
 
 
315
  for j, (idx, u,v) in enumerate(zip(foci_position_index[i], foci_position[i], foci_absolute_intensity[i])):
316
  if config['use_corrected_positions']:
 
317
  path_data[f'Foci_{j+1}_position(um)'] = pl[idx]
318
  else:
 
319
  path_data[f'Foci_{j+1}_position(um)'] = u
 
320
  path_data[f'Foci_{j+1}_absolute_intensity'] = v
 
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,
@@ -344,24 +361,29 @@ def analyse_paths(cell_id,
344
  """
345
 
346
 
 
 
347
  foci_stack = tifffile.imread(foci_file)
348
 
 
349
  if foci_stack.ndim==2:
350
  foci_stack = foci_stack[None,:,:]
351
 
352
  all_paths, path_lengths = get_paths_from_traces_file(traces_file)
353
 
354
- all_trace_vis = []
355
- all_m = []
356
  for p in all_paths:
 
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))
@@ -371,22 +393,24 @@ def analyse_paths(cell_id,
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
 
389
  plt.tight_layout()
390
- trace_overlay = draw_paths(all_paths, foci_stack, foci_index=foci_pos_index)
391
 
392
  return trace_overlay, all_trace_vis, fig, extracted_peaks
 
53
  section_lengths = [0.0]
54
  s = np.array(voxel_size)
55
  for i in range(len(point_list)-1):
56
+ # Euclidean distance between successive points
57
  section_lengths.append(la.norm(s * (np.array(point_list[i+1]) - np.array(point_list[i]))))
58
  return np.cumsum(section_lengths)
59
 
 
90
  col_map = [(255,0,0), (0,255,0), (0,0,255), (255,255,0), (255,0,255), (0,255,255),
91
  (255,127,0), (255, 0, 127), (127, 255, 0), (0, 255, 127), (127,0,255), (0,127,255)]
92
 
93
+ def draw_paths(all_paths, foci_stack, foci_index=None, r=3, screened_foci_data=None):
94
  """
95
  Draws paths on the provided image stack and overlays markers for the foci
96
 
 
99
  foci_stack (np.array): 3D numpy array representing the image stack.
100
  foci_index (list, optional): List of list of focus indices (along each path). Defaults to None.
101
  r (int, optional): Radius for the ellipse or line drawing around the focus. Defaults to 3.
102
+ screened_foci_data (list, optional): List of RemovedPeakData for screened foci
103
  Returns:
104
  PIL.Image.Image: An image with the drawn paths.
105
  """
 
111
  for i, (p, col) in enumerate(zip(all_paths, cycle(col_map))):
112
  draw.line([(u[0], u[1]) for u in p], fill=col)
113
  draw.text((p[0][0], p[0][1]), str(i+1), fill=col)
114
+
115
+ if screened_foci_data is not None:
116
+ for i, removed_peaks in enumerate(screened_foci_data):
117
+ for p in removed_peaks:
118
+ u = all_paths[i][p.idx]
119
+ v = all_paths[p.screening_peak[0]][p.screening_peak[1]]
120
+ draw.line((int(u[0]), int(u[1]), int(v[0]), int(v[1])), fill=(127,127,127), width=2)
121
+
122
  if foci_index is not None:
123
  for i, (idx, p, col) in enumerate(zip(foci_index, all_paths, cycle(col_map))):
124
  if len(idx):
125
  for j in idx:
126
  draw.line((int(p[j][0]-r), int(p[j][1]), int(p[j][0]+r), int(p[j][1])), fill=col, width=2)
127
  draw.line((int(p[j][0]), int(p[j][1]-r), int(p[j][0]), int(p[j][1]+r)), fill=col, width=2)
 
128
  return im
129
 
130
 
 
172
  #
173
 
174
  R = [u//2 for u in melem.shape]
175
+
 
176
  r, c, z = p
177
 
178
  mask = np.zeros(melem.shape)
 
217
  Generate a binary representation of a sphere in 3D space.
218
 
219
  Args:
220
+ R (int, optional): Radius of the sphere. Default is 5. Centred on the centre of the middle voxel.
221
+ Includes all voxels whose centre is precisely R from the middle voxel.
222
  z_scale_ratio (float, optional): Scaling factor for the z-axis. Default is 2.3.
223
 
224
  Returns:
 
251
 
252
 
253
  # Measure fluorescence levels along ordered skeleton
254
+ def measure_chrom2(path, intensity, config):
255
  """
256
  Measure fluorescence levels along an ordered skeleton.
257
 
258
  Args:
259
  path (list): List of ordered path points (r, c, z).
260
+ intensity (numpy.ndarray): 3D fluorescence data.
261
  config (dict): Configuration dictionary containing 'z_res', 'xy_res', and 'sphere_radius' values.
262
 
263
  Returns:
264
  tuple: A tuple containing the visualization, mean measurements, and max measurements along the path.
265
  """
266
+ # Calculate size of spheroid used for measurement
267
  scale_ratio = config['z_res']/config['xy_res']
268
  sphere_xy_radius = int(math.ceil(config['sphere_radius']/config['xy_res']))
269
 
270
+ vis = visualise_ordering(path, dim=intensity.shape, wr=sphere_xy_radius, wc=sphere_xy_radius)
271
 
272
+ measurements = measure_all_with_sphere(path, intensity, op='mean', R=sphere_xy_radius, z_scale_ratio=scale_ratio)
273
+ measurements_max = measure_all_with_sphere(path, intensity, op='max', R=sphere_xy_radius, z_scale_ratio=scale_ratio)
274
 
275
 
276
  return vis, measurements, measurements_max
 
299
  n_paths = len(all_paths)
300
 
301
  data = []
302
+ foci_absolute_intensity, foci_position, foci_position_index, screened_foci_data, trace_median_intensities, trace_thresholds = analyse_traces(all_paths, path_lengths, measured_traces, config)
303
 
304
+ # Normalize foci intensities (for quantification) using trace medians as estimates of background
305
  foci_intensities = []
306
  for path_foci_abs_int, tmi in zip(foci_absolute_intensity, trace_median_intensities):
307
  foci_intensities.extend(list(path_foci_abs_int - tmi))
308
+
309
+ # Divide all foci intensities by the mean within the cell
310
  mean_intensity = np.mean(foci_intensities)
311
  trace_positions = []
312
 
313
  for i in range(n_paths):
314
 
315
+ # Calculate real (Euclidean) distance of each point along the traced path
316
  pl = calculate_path_length_partials(all_paths[i], (config['xy_res'], config['xy_res'], config['z_res']))
317
 
 
318
 
319
  path_data = { 'Cell_ID':cell_id,
320
  'Trace': i+1,
 
322
  'Measured_trace_length(um)': pl[-1],
323
  'Trace_median_intensity': trace_median_intensities[i],
324
  'Detection_sphere_radius(um)': config['sphere_radius'],
325
+ 'Screening_distance(voxels)': config['screening_distance'],
326
+ 'Foci_ID_threshold': config['peak_threshold'],
327
+ 'Trace_foci_number': len(foci_position_index[i]) }
328
  for j, (idx, u,v) in enumerate(zip(foci_position_index[i], foci_position[i], foci_absolute_intensity[i])):
329
  if config['use_corrected_positions']:
330
+ # Use the calculated position along the traced path
331
  path_data[f'Foci_{j+1}_position(um)'] = pl[idx]
332
  else:
333
+ # Use the measured trace length (from SNT), and assume all steps of path are approximately the same length
334
  path_data[f'Foci_{j+1}_position(um)'] = u
335
+ # The original measured intensity (mean in spheroid around detected peak)
336
  path_data[f'Foci_{j+1}_absolute_intensity'] = v
337
+ # Measure relative intensity by removing per-trace background and dividing by cell total
338
  path_data[f'Foci_{j+1}_relative_intensity'] = (v - trace_median_intensities[i])/mean_intensity
339
  data.append(path_data)
340
  trace_positions.append(pl)
341
+ return pd.DataFrame(data), foci_absolute_intensity, foci_position_index, screened_foci_data, trace_thresholds, trace_positions
342
 
343
 
344
  def analyse_paths(cell_id,
 
361
  """
362
 
363
 
364
+ # Read stack
365
+
366
  foci_stack = tifffile.imread(foci_file)
367
 
368
+ # If 2D add additional (z) dimension
369
  if foci_stack.ndim==2:
370
  foci_stack = foci_stack[None,:,:]
371
 
372
  all_paths, path_lengths = get_paths_from_traces_file(traces_file)
373
 
374
+ all_trace_vis = [] # Per-path visualizations
375
+ all_m = [] # Per-path measured intensities
376
  for p in all_paths:
377
+ # Measure intensity along path - transpose the stack ZYX -> XYZ
378
  vis, m, _ = measure_chrom2(p,foci_stack.transpose(2,1,0), config)
379
  all_trace_vis.append(vis)
380
  all_m.append(m)
381
 
382
 
383
+ # Extract all data from paths and traces
384
+ extracted_peaks, foci_absolute_intensity, foci_pos_index, screened_foci_data, trace_thresholds, trace_positions = extract_peaks(cell_id, all_paths, path_lengths, all_m, config)
385
 
386
+ # Plot per-path measured intensities and indicate foci
387
  n_cols = 2
388
  n_rows = (len(all_paths)+n_cols-1)//n_cols
389
  fig, ax = plt.subplots(n_rows,n_cols, figsize=(5*n_cols, 3*n_rows))
 
393
  ax[i].set_title(f'Trace {i+1}')
394
  ax[i].plot(trace_positions[i], m)
395
  if len(foci_pos_index[i]):
396
+ # Plot detected foci
397
  ax[i].plot(trace_positions[i][foci_pos_index[i]], np.array(m)[foci_pos_index[i]], 'rx')
398
 
399
+ if len(screened_foci_data[i]):
400
+ # Indicate screened foci by gray circles on plots
401
+ screened_foci_pos_index = [u.idx for u in screened_foci_data[i]]
402
+ ax[i].plot(trace_positions[i][screened_foci_pos_index], np.array(m)[screened_foci_pos_index], color=(0.5,0.5,0.5), marker='o', linestyle='None')
403
 
404
+ # Show per-trace intensity thresholds with red dotted lines
405
  if trace_thresholds[i] is not None:
406
  ax[i].axhline(trace_thresholds[i], c='r', ls=':')
407
  ax[i].set_xlabel('Distance from start (um)')
408
  ax[i].set_ylabel('Intensity')
409
+ # Hide excess plots
410
  for i in range(len(all_m), n_cols*n_rows):
411
  ax[i].axis('off')
412
 
413
  plt.tight_layout()
414
+ trace_overlay = draw_paths(all_paths, foci_stack, foci_index=foci_pos_index, screened_foci_data=screened_foci_data)
415
 
416
  return trace_overlay, all_trace_vis, fig, extracted_peaks
path_analysis/data_preprocess.py CHANGED
@@ -74,10 +74,10 @@ class RemovedPeakData(object):
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):
@@ -86,17 +86,17 @@ class PathData(object):
86
  This dataclass encapsulates information about the peaks,
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
@@ -138,7 +138,7 @@ def find_peaks2(v, distance=5, prominence=0.5):
138
  return n_peaks, _
139
 
140
 
141
- def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
142
  """
143
  Process traces of cells to extract peak information and organize the data.
144
 
@@ -152,6 +152,7 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
152
  path_lengths (list of float): List of path lengths corresponding to the provided paths.
153
  measured_trace_fluorescence (list of list of float): A list containing fluorescence
154
  data corresponding to each path point.
 
155
 
156
  Returns:
157
  CellData: An object containing organized peak and path data for a given cell.
@@ -163,17 +164,17 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
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)
170
 
171
  # Find peaks - these will be further refined later
172
- p,_ = find_peaks2(hei10_normalized, distance=5, prominence=0.5*np.std(hei10_normalized))
173
  peaks = np.array(p, dtype=np.int32)
174
 
175
  # Store peak data - using original values, not normalized ones
176
- peak_mean_heights = [ o_hei10[u] for u in peaks ]
177
  peak_points = [ points[u] for u in peaks ]
178
 
179
  cell_peaks.append((peaks, peak_points, peak_mean_heights))
@@ -188,7 +189,7 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
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 = []
@@ -206,7 +207,7 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
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)
@@ -215,15 +216,15 @@ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence):
215
  cell_peaks = new_cell_peaks
216
  pd_list = []
217
 
218
- # Save peak positions, absolute HEI10 intensities, and length for each SC
219
  for k in range(len(all_paths)):
220
 
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)
@@ -235,7 +236,7 @@ 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.
@@ -271,14 +272,14 @@ def analyse_celldata(cell_data, config):
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
 
@@ -296,12 +297,11 @@ def analyse_celldata(cell_data, config):
296
 
297
  # Normalize extracted fluorescent intensities by subtracting mean (and dividing
298
  # by standard deviation - note that the latter should have no effect on the results).
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
@@ -309,30 +309,30 @@ def analyse_celldata(cell_data, config):
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':
338
  """
@@ -343,7 +343,7 @@ def analyse_celldata(cell_data, config):
343
 
344
  # Normalize extracted fluorescent intensities by subtracting mean (and dividing
345
  # by standard deviation - note that the latter should have no effect on the results).
346
- h = np.array(path_data.o_hei10)
347
  h = h - np.mean(h)
348
  max_cell_intensity = max(max_cell_intensity, np.max(h))
349
 
@@ -352,7 +352,7 @@ def analyse_celldata(cell_data, config):
352
 
353
  # Normalize extracted fluorescent intensities by subtracting mean (and dividing
354
  # by standard deviation - note that the latter should have no effect on the results).
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]
@@ -360,33 +360,33 @@ def analyse_celldata(cell_data, config):
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
 
389
- cd = process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence)
390
 
391
  return analyse_celldata(cd, config)
392
 
 
74
 
75
  Attributes:
76
  idx (int): Index of peak along path
77
+ screening_peak (tuple): (path_idx, position along path) for screening peak
78
  """
79
  idx: int
80
+ screening_peak: tuple
81
 
82
  @dataclass
83
  class PathData(object):
 
86
  This dataclass encapsulates information about the peaks,
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_intensity).
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_intensity (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_intensity: list
100
  SC_length: float
101
 
102
  @dataclass
 
138
  return n_peaks, _
139
 
140
 
141
+ def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence, dmin=10):
142
  """
143
  Process traces of cells to extract peak information and organize the data.
144
 
 
152
  path_lengths (list of float): List of path lengths corresponding to the provided paths.
153
  measured_trace_fluorescence (list of list of float): A list containing fluorescence
154
  data corresponding to each path point.
155
+ dmin (float): Distance below which brighter peaks screen less bright ones.
156
 
157
  Returns:
158
  CellData: An object containing organized peak and path data for a given cell.
 
164
 
165
  cell_peaks = []
166
 
167
+ for points, o_intensity in zip(all_paths, measured_trace_fluorescence):
168
 
169
  # For peak determination normalize each trace to have mean zero and s.d. 1
170
+ intensity_normalized = (o_intensity - np.mean(o_intensity))/np.std(o_intensity)
171
 
172
  # Find peaks - these will be further refined later
173
+ p,_ = find_peaks2(intensity_normalized, distance=5, prominence=0.5*np.std(intensity_normalized))
174
  peaks = np.array(p, dtype=np.int32)
175
 
176
  # Store peak data - using original values, not normalized ones
177
+ peak_mean_heights = [ o_intensity[u] for u in peaks ]
178
  peak_points = [ points[u] for u in peaks ]
179
 
180
  cell_peaks.append((peaks, peak_points, peak_mean_heights))
 
189
  to_thin.append(PeakData(pos=cell_peaks[k][1][u], intensity=cell_peaks[k][2][u], key=(k, u)))
190
 
191
  # Exclude any peak with a nearby brighter peak (on any SC)
192
+ removed_peaks, removed_larger_peaks = thin_peaks(to_thin, return_larger_peaks=True, dmin=dmin)
193
 
194
  # Clean up and remove these peaks
195
  new_cell_peaks = []
 
207
  # What's the larger point?
208
  idx = removed_peaks.index((path_idx, peak_idx))
209
  larger_path, larger_idx = removed_larger_peaks[idx]
210
+ path_removed_peaks.append(RemovedPeakData(idx=path_peaks[peak_idx], screening_peak=(larger_path, cell_peaks[larger_path][0][larger_idx])))
211
  ###
212
 
213
  new_cell_peaks.append(path_retained_peaks)
 
216
  cell_peaks = new_cell_peaks
217
  pd_list = []
218
 
219
+ # Save peak positions, absolute intensity intensities, and length for each SC
220
  for k in range(len(all_paths)):
221
 
222
+ points, o_intensity = all_paths[k], measured_trace_fluorescence[k]
223
 
224
  peaks = cell_peaks[k]
225
  removed_peaks = removed_cell_peaks[k]
226
 
227
+ pd = PathData(peaks=peaks, removed_peaks=removed_peaks, points=points, o_intensity=o_intensity, SC_length=path_lengths[k])
228
  pd_list.append(pd)
229
 
230
  cd = CellData(pathdata_list=pd_list)
 
236
 
237
 
238
  # Criterion used for identifying peak as a focus - normalized (with mean and s.d.)
239
+ # intensity levels being above 0.4 time maximum peak level
240
  def focus_criterion(pos, v, alpha=alpha_max):
241
  """
242
  Identify and return positions where values in the array `v` exceed a certain threshold.
 
272
  - foci_rel_intensity (list): List of relative intensities for the detected foci.
273
  - foci_pos (list): List of absolute positions of the detected foci.
274
  - foci_pos_index (list): List of indices of the detected foci.
275
+ - screened_foci_data (list): List of RemovedPeakData indicating positions of removed peaks and the index of the larger peak
276
  - trace_median_intensities (list): Per-trace median intensity
277
  - trace_thresholds (list): Per-trace absolute threshold for calling peaks as foci
278
  """
279
  foci_abs_intensity = []
280
  foci_pos = []
281
  foci_pos_index = []
282
+ screened_foci_data = []
283
  trace_median_intensities = []
284
  trace_thresholds = []
285
 
 
297
 
298
  # Normalize extracted fluorescent intensities by subtracting mean (and dividing
299
  # by standard deviation - note that the latter should have no effect on the results).
300
+ h = np.array(path_data.o_intensity)
301
  h = h - np.mean(h)
302
  h = h/np.std(h)
303
  # Extract foci according to criterion
304
  foci_idx = focus_criterion(peaks, h[peaks], peak_threshold)
 
305
 
306
  #
307
  removed_peaks = path_data.removed_peaks
 
309
 
310
 
311
  if len(peaks):
312
+ trace_thresholds.append((1-peak_threshold)*np.mean(path_data.o_intensity) + peak_threshold*np.max(np.array(path_data.o_intensity)[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_intensity) + peak_threshold*np.max(np.array(path_data.o_intensity)[peaks])
319
  else:
320
  threshold = float('-inf')
321
 
322
 
323
+ removed_peak_heights = np.array(path_data.o_intensity)[removed_peaks_idx]
324
+ screened_foci_idx = np.where(removed_peak_heights>threshold)[0]
325
 
326
+ screened_foci_data.append([removed_peaks[i] for i in screened_foci_idx])
327
  else:
328
+ screened_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_intensity)[foci_idx])
333
 
334
  foci_pos_index.append(foci_idx)
335
+ trace_median_intensities.append(np.median(path_data.o_intensity))
336
 
337
  elif threshold_type == 'per-cell':
338
  """
 
343
 
344
  # Normalize extracted fluorescent intensities by subtracting mean (and dividing
345
  # by standard deviation - note that the latter should have no effect on the results).
346
+ h = np.array(path_data.o_intensity)
347
  h = h - np.mean(h)
348
  max_cell_intensity = max(max_cell_intensity, np.max(h))
349
 
 
352
 
353
  # Normalize extracted fluorescent intensities by subtracting mean (and dividing
354
  # by standard deviation - note that the latter should have no effect on the results).
355
+ h = np.array(path_data.o_intensity)
356
  h = h - np.mean(h)
357
 
358
  foci_idx = peaks[h[peaks]>peak_threshold*max_cell_intensity]
 
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_intensity) + peak_threshold*max_cell_intensity)
364
 
365
  if len(removed_peaks):
366
+ threshold = np.mean(path_data.o_intensity) + peak_threshold*max_cell_intensity
367
 
368
+ removed_peak_heights = np.array(path_data.o_intensity)[removed_peaks_idx]
369
+ screened_foci_idx = np.where(removed_peak_heights>threshold)[0]
370
 
371
+ screened_foci_data.append([removed_peaks[i] for i in screened_foci_idx])
372
  else:
373
+ screened_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_intensity)[foci_idx])
378
 
379
  foci_pos_index.append(foci_idx)
380
+ trace_median_intensities.append(np.median(path_data.o_intensity))
381
 
382
  else:
383
  raise NotImplementedError
384
 
385
+ return foci_abs_intensity, foci_pos, foci_pos_index, screened_foci_data, trace_median_intensities, trace_thresholds
386
 
387
  def analyse_traces(all_paths, path_lengths, measured_trace_fluorescence, config):
388
 
389
+ cd = process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence, dmin=config['screening_distance'])
390
 
391
  return analyse_celldata(cd, config)
392
 
tests/test_analyse.py CHANGED
@@ -1,6 +1,7 @@
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
@@ -99,7 +100,7 @@ def test_get_paths_from_traces_file():
99
  def test_measure_chrom2():
100
  # Mock data
101
  path = [(2, 3, 4), (4, 5, 6), (9, 9, 9)] # Sample ordered path points
102
- hei10 = np.random.rand(10, 10, 10) # Random 3D fluorescence data
103
  config = {
104
  'z_res': 1,
105
  'xy_res': 0.5,
@@ -107,7 +108,7 @@ def test_measure_chrom2():
107
  }
108
 
109
  # Function call
110
- _, measurements, measurements_max = measure_chrom2(path, hei10, config)
111
 
112
  # Assertions
113
  assert len(measurements) == len(path), "Measurements length should match path length"
@@ -118,7 +119,7 @@ def test_measure_chrom2():
118
  def test_measure_chrom2_z():
119
  # Mock data
120
  path = [(2, 3, 4), (4, 5, 6)] # Sample ordered path points
121
- _,_,hei10 = np.meshgrid(np.arange(10), np.arange(10), np.arange(10)) # 3D fluorescence data - z dependent
122
  config = {
123
  'z_res': 1,
124
  'xy_res': 0.5,
@@ -126,7 +127,7 @@ def test_measure_chrom2_z():
126
  }
127
 
128
  # Function call
129
- _, measurements, measurements_max = measure_chrom2(path, hei10, config)
130
 
131
  # Assertions
132
  assert len(measurements) == len(path), "Measurements length should match path length"
@@ -137,7 +138,7 @@ def test_measure_chrom2_z():
137
  def test_measure_chrom2_z2():
138
  # Mock data
139
  path = [(0,0,0), (2, 3, 4), (4, 5, 6)] # Sample ordered path points
140
- _,_,hei10 = np.meshgrid(np.arange(10), np.arange(10), np.arange(10)) # 3D fluorescence data - z dependent
141
  config = {
142
  'z_res': 0.25,
143
  'xy_res': 0.5,
@@ -145,7 +146,7 @@ def test_measure_chrom2_z2():
145
  }
146
 
147
  # Function call
148
- _, measurements, measurements_max = measure_chrom2(path, hei10, config)
149
 
150
  # Assertions
151
  assert len(measurements) == len(path), "Measurements length should match path length"
@@ -283,31 +284,87 @@ def test_make_sphere_equal():
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
  import pytest
3
  from path_analysis.analyse import *
4
+ from path_analysis.data_preprocess import RemovedPeakData
5
  import numpy as np
6
  from math import pi
7
  import xml.etree.ElementTree as ET
 
100
  def test_measure_chrom2():
101
  # Mock data
102
  path = [(2, 3, 4), (4, 5, 6), (9, 9, 9)] # Sample ordered path points
103
+ intensity = np.random.rand(10, 10, 10) # Random 3D fluorescence data
104
  config = {
105
  'z_res': 1,
106
  'xy_res': 0.5,
 
108
  }
109
 
110
  # Function call
111
+ _, measurements, measurements_max = measure_chrom2(path, intensity, config)
112
 
113
  # Assertions
114
  assert len(measurements) == len(path), "Measurements length should match path length"
 
119
  def test_measure_chrom2_z():
120
  # Mock data
121
  path = [(2, 3, 4), (4, 5, 6)] # Sample ordered path points
122
+ _,_,intensity = np.meshgrid(np.arange(10), np.arange(10), np.arange(10)) # 3D fluorescence data - z dependent
123
  config = {
124
  'z_res': 1,
125
  'xy_res': 0.5,
 
127
  }
128
 
129
  # Function call
130
+ _, measurements, measurements_max = measure_chrom2(path, intensity, config)
131
 
132
  # Assertions
133
  assert len(measurements) == len(path), "Measurements length should match path length"
 
138
  def test_measure_chrom2_z2():
139
  # Mock data
140
  path = [(0,0,0), (2, 3, 4), (4, 5, 6)] # Sample ordered path points
141
+ _,_,intensity = np.meshgrid(np.arange(10), np.arange(10), np.arange(10)) # 3D fluorescence data - z dependent
142
  config = {
143
  'z_res': 0.25,
144
  'xy_res': 0.5,
 
146
  }
147
 
148
  # Function call
149
+ _, measurements, measurements_max = measure_chrom2(path, intensity, config)
150
 
151
  # Assertions
152
  assert len(measurements) == len(path), "Measurements length should match path length"
 
284
 
285
  import pandas as pd
286
 
 
 
287
  def test_extract_peaks_basic():
288
+ cell_id = 1 # Simple per-cell tag
289
+ all_paths = [[[0, 0, 0], [1, 1, 0]]] # Single, simple path
290
  path_lengths = [1.41] # length of the above path
291
  measured_traces = [[100, 200]] # fluorescence along the path
292
+ config = {'peak_threshold': 0.4, 'sphere_radius': 2, 'xy_res': 1, 'z_res': 1, 'threshold_type':'per-cell', 'use_corrected_positions': True, 'screening_distance':10 }
293
 
294
+ df, foci_absolute_intensity, foci_pos_index, screened_foci_data, trace_thresholds, trace_positions = extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config)
295
+
 
296
  assert len(df) == 1, "Expected one row in DataFrame"
297
  assert df['Cell_ID'].iloc[0] == cell_id, "Unexpected cell_id"
298
+ assert list(df['Trace_foci_number']) == [1], "Wrong foci number"
299
+ assert df['Foci_1_position(um)'].iloc[0] == np.sqrt(2)
300
+ assert foci_pos_index == [[1]]
301
+ assert foci_absolute_intensity == [[200]]
302
+ assert screened_foci_data == [[]]
303
+ assert trace_thresholds == [ [ 150+0.4*50] ]
304
+ assert np.all(trace_positions[0] == np.array([0, np.sqrt(2)]))
305
 
 
306
  def test_extract_peaks_multiple_paths():
307
  cell_id = 1
308
+ all_paths = [[[0, 0, 0], [1, 1, 0]], [[1, 1, 200], [2, 2, 200]]]
309
+ path_lengths = [1.41, 1.41]
310
+ measured_traces = [[100, 200], [100, 140]]
311
+ config = {'peak_threshold': 0.4, 'sphere_radius': 2, 'xy_res': 1, 'z_res': 1, 'threshold_type':'per-trace', 'use_corrected_positions': True, 'screening_distance':10 }
312
+
313
+ df, foci_absolute_intensity, foci_pos_index, screened_foci_data, trace_thresholds, trace_positions = extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config)
314
+
315
+
316
+
317
+ assert len(df) == 2, "Expected two rows in DataFrame"
318
+ assert df['Cell_ID'].iloc[0] == cell_id, "Unexpected cell_id"
319
+ assert list(df['Trace_foci_number']) == [1,1], "Wrong foci number"
320
+ assert df['Foci_1_position(um)'].iloc[0] == np.sqrt(2)
321
+ print(foci_pos_index)
322
+ assert list(map(list, foci_pos_index)) == [[1],[1]]
323
+ assert list(map(list, foci_absolute_intensity)) == [[200],[140]]
324
+ assert trace_thresholds == [ 150+0.4*50, 120+0.4*20 ]
325
+ assert np.all(trace_positions[0] == np.array([0, np.sqrt(2)]))
326
+ assert screened_foci_data == [[],[]]
327
+
328
+ def test_extract_peaks_multiple_paths_screened():
329
+ cell_id = 1
330
+ all_paths = [[[0, 0, 0], [1, 1, 0]], [[1, 1, 2], [2, 2, 2]]]
331
  path_lengths = [1.41, 1.41]
332
  measured_traces = [[100, 200], [100, 150]]
333
+ config = {'peak_threshold': 0.4, 'sphere_radius': 2, 'xy_res': 1, 'z_res': 1, 'threshold_type':'per-trace', 'use_corrected_positions': True, 'screening_distance':10 }
334
 
335
+ df, foci_absolute_intensity, foci_pos_index, screened_foci_data, trace_thresholds, trace_positions = extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config)
336
 
337
+
338
+
339
  assert len(df) == 2, "Expected two rows in DataFrame"
340
+ assert df['Cell_ID'].iloc[0] == cell_id, "Unexpected cell_id"
341
+ assert list(df['Trace_foci_number']) == [1,0], "Wrong foci number"
342
+ assert df['Foci_1_position(um)'].iloc[0] == np.sqrt(2)
343
+ print(foci_pos_index)
344
+ assert list(map(list, foci_pos_index)) == [[1],[]]
345
+ assert list(map(list, foci_absolute_intensity)) == [[200],[]]
346
+ assert trace_thresholds == [ 150+0.4*50, None ]
347
+ assert np.all(trace_positions[0] == np.array([0, np.sqrt(2)]))
348
+ assert screened_foci_data == [[],[RemovedPeakData(idx=1, screening_peak=(0,1))]]
349
+
350
+
351
+ def test_extract_peaks_multiple_paths_per_cell():
352
+ cell_id = 1
353
+ all_paths = [[[0, 0, 0], [1, 1, 0]], [[1, 1, 200], [2, 2, 200]]]
354
+ path_lengths = [1.41, 1.41]
355
+ measured_traces = [[100, 200], [100, 140]]
356
+ config = {'peak_threshold': 0.4, 'sphere_radius': 2, 'xy_res': 1, 'z_res': 1, 'threshold_type':'per-cell', 'use_corrected_positions': True, 'screening_distance':10 }
357
+
358
+ df, foci_absolute_intensity, foci_pos_index, screened_foci_data, trace_thresholds, trace_positions = extract_peaks(cell_id, all_paths, path_lengths, measured_traces, config)
359
+
360
+
361
+
362
+ assert len(df) == 2, "Expected two rows in DataFrame"
363
+ assert df['Cell_ID'].iloc[0] == cell_id, "Unexpected cell_id"
364
+ assert list(df['Trace_foci_number']) == [1,0], "Wrong foci number"
365
+ assert df['Foci_1_position(um)'].iloc[0] == np.sqrt(2)
366
+ assert list(map(list, foci_pos_index)) == [[1],[]]
367
+ assert list(map(list, foci_absolute_intensity)) == [[200],[]]
368
+ assert trace_thresholds == [ 150+0.4*50, 120+0.4*50 ]
369
+ assert np.all(trace_positions[0] == np.array([0, np.sqrt(2)]))
370
+ assert screened_foci_data == [[],[]]
tests/test_preprocess.py CHANGED
@@ -128,8 +128,8 @@ def test_process_cell_traces_peaks(mock_data):
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):
 
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_intensity=[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_intensity=[38, 2, 20], SC_length=2.3)
133
  return CellData(pathdata_list=[pathdata1, pathdata2])
134
 
135
  def test_analyse_celldata(mock_celldata):