OSCR

Deep visual proteomics uncovers nociceptor diversity and pain targets.

Code ↔ Paper

The paper beside its authors' code: matches between them have not been computed for this paper yet.

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 573 lines · 22 KB · Apache-2.0

  1. # %% [markdown]
  2. # This notebook is an adaptation of the ["Cellpose (2D and 3D)" notebook from ZeroCostDL4Mic](https://colab.research.google.com/github/HenriquesLab/ZeroCostDL4Mic/blob/master/Colab_notebooks/Beta%20notebooks/Cellpose_2D_ZeroCostDL4Mic.ipynb)
  3. # %% [markdown]
  4. # # Installation
  5. # If you are missing dependencies, these are the ones needed to run this notebook. Uncomment the line as needed
  6. # %%
  7. #!pip install numpy==1.19.3 numba==0.53.1 scikit-image==0.19.3 scikit-learn==1.1.1 opencv-python==4.6.0.66 matplotlib==3.5.2 pandas==1.4.3 tqdm==4.64.0 astropy==5.1 tabulate==0.8.10
  8. # %% [markdown]
  9. # # All imports and functions
  10. # %%
  11. import csv
  12. import cv2
  13. import matplotlib as mpl
  14. import numpy as np
  15. import os
  16. import pandas as pd
  17. import random
  18. import shutil
  19. import subprocess
  20. import sys
  21. import time
  22. import urllib
  23. import zipfile
  24. from astropy.visualization import simple_norm
  25. from collections import namedtuple
  26. from datetime import datetime
  27. from glob import glob
  28. from matplotlib import pyplot as plt
  29. from numba import jit
  30. from pathlib import Path
  31. from pip._internal.operations.freeze import freeze
  32. from scipy import signal, ndimage
  33. from scipy.optimize import linear_sum_assignment
  34. from skimage import io
  35. from skimage.metrics import structural_similarity
  36. from skimage.metrics import peak_signal_noise_ratio as psnr
  37. from skimage.util import img_as_ubyte, img_as_uint, img_as_float32
  38. from sklearn.linear_model import LinearRegression
  39. from tabulate import tabulate
  40. from tifffile import imread, imsave
  41. from tqdm import tqdm
  42. from zipfile import ZIP_DEFLATED
  43. from IPython.display import HTML
  44. from IPython.display import display
  45. matching_criteria = dict()
  46. def label_are_sequential(y):
  47. """ returns true if y has only sequential labels from 1... """
  48. labels = np.unique(y)
  49. return (set(labels)-{0}) == set(range(1,1+labels.max()))
  50. def is_array_of_integers(y):
  51. return isinstance(y,np.ndarray) and np.issubdtype(y.dtype, np.integer)
  52. def _check_label_array(y, name=None, check_sequential=False):
  53. err = ValueError("{label} must be an array of {integers}.".format(
  54. label = 'labels' if name is None else name,
  55. integers = ('sequential ' if check_sequential else '') + 'non-negative integers',
  56. ))
  57. is_array_of_integers(y) or print("An error occured")
  58. if check_sequential:
  59. label_are_sequential(y) or print("An error occured")
  60. else:
  61. y.min() >= 0 or print("An error occured")
  62. return True
  63. def label_overlap(x, y, check=True):
  64. if check:
  65. _check_label_array(x,'x',True)
  66. _check_label_array(y,'y',True)
  67. x.shape == y.shape or _raise(ValueError("x and y must have the same shape"))
  68. return _label_overlap(x, y)
  69. @jit(nopython=True)
  70. def _label_overlap(x, y):
  71. x = x.ravel()
  72. y = y.ravel()
  73. overlap = np.zeros((1+x.max(),1+y.max()), dtype=np.uint)
  74. for i in range(len(x)):
  75. overlap[x[i],y[i]] += 1
  76. return overlap
  77. def intersection_over_union(overlap):
  78. _check_label_array(overlap,'overlap')
  79. if np.sum(overlap) == 0:
  80. return overlap
  81. n_pixels_pred = np.sum(overlap, axis=0, keepdims=True)
  82. n_pixels_true = np.sum(overlap, axis=1, keepdims=True)
  83. return overlap / (n_pixels_pred + n_pixels_true - overlap)
  84. matching_criteria['iou'] = intersection_over_union
  85. def intersection_over_true(overlap):
  86. _check_label_array(overlap,'overlap')
  87. if np.sum(overlap) == 0:
  88. return overlap
  89. n_pixels_true = np.sum(overlap, axis=1, keepdims=True)
  90. return overlap / n_pixels_true
  91. matching_criteria['iot'] = intersection_over_true
  92. def intersection_over_pred(overlap):
  93. _check_label_array(overlap,'overlap')
  94. if np.sum(overlap) == 0:
  95. return overlap
  96. n_pixels_pred = np.sum(overlap, axis=0, keepdims=True)
  97. return overlap / n_pixels_pred
  98. matching_criteria['iop'] = intersection_over_pred
  99. def precision(tp,fp,fn):
  100. return tp/(tp+fp) if tp > 0 else 0
  101. def recall(tp,fp,fn):
  102. return tp/(tp+fn) if tp > 0 else 0
  103. def accuracy(tp,fp,fn):
  104. # also known as "average precision" (?)
  105. # -> https://www.kaggle.com/c/data-science-bowl-2018#evaluation
  106. return tp/(tp+fp+fn) if tp > 0 else 0
  107. def f1(tp,fp,fn):
  108. # also known as "dice coefficient"
  109. return (2*tp)/(2*tp+fp+fn) if tp > 0 else 0
  110. def _safe_divide(x,y):
  111. return x/y if y>0 else 0.0
  112. def matching(y_true, y_pred, thresh=0.5, criterion='iou', report_matches=False):
  113. """Calculate detection/instance segmentation metrics between ground truth and predicted label images.
  114. Currently, the following metrics are implemented:
  115. 'fp', 'tp', 'fn', 'precision', 'recall', 'accuracy', 'f1', 'criterion', 'thresh', 'n_true', 'n_pred', 'mean_true_score', 'mean_matched_score', 'panoptic_quality'
  116. Corresponding objects of y_true and y_pred are counted as true positives (tp), false positives (fp), and false negatives (fn)
  117. whether their intersection over union (IoU) >= thresh (for criterion='iou', which can be changed)
  118. * mean_matched_score is the mean IoUs of matched true positives
  119. * mean_true_score is the mean IoUs of matched true positives but normalized by the total number of GT objects
  120. * panoptic_quality defined as in Eq. 1 of Kirillov et al. "Panoptic Segmentation", CVPR 2019
  121. Parameters
  122. ----------
  123. y_true: ndarray
  124. ground truth label image (integer valued)
  125. predicted label image (integer valued)
  126. thresh: float
  127. threshold for matching criterion (default 0.5)
  128. criterion: string
  129. matching criterion (default IoU)
  130. report_matches: bool
  131. if True, additionally calculate matched_pairs and matched_scores (note, that this returns even gt-pred pairs whose scores are below 'thresh')
  132. Returns
  133. -------
  134. Matching object with different metrics as attributes
  135. Examples
  136. --------
  137. >>> y_true = np.zeros((100,100), np.uint16)
  138. >>> y_true[10:20,10:20] = 1
  139. >>> y_pred = np.roll(y_true,5,axis = 0)
  140. >>> stats = matching(y_true, y_pred)
  141. >>> print(stats)
  142. Matching(criterion='iou', thresh=0.5, fp=1, tp=0, fn=1, precision=0, recall=0, accuracy=0, f1=0, n_true=1, n_pred=1, mean_true_score=0.0, mean_matched_score=0.0, panoptic_quality=0.0)
  143. """
  144. _check_label_array(y_true,'y_true')
  145. _check_label_array(y_pred,'y_pred')
  146. y_true.shape == y_pred.shape or _raise(ValueError("y_true ({y_true.shape}) and y_pred ({y_pred.shape}) have different shapes".format(y_true=y_true, y_pred=y_pred)))
  147. criterion in matching_criteria or _raise(ValueError("Matching criterion '%s' not supported." % criterion))
  148. if thresh is None: thresh = 0
  149. thresh = float(thresh) if np.isscalar(thresh) else map(float,thresh)
  150. y_true, _, map_rev_true = relabel_sequential(y_true)
  151. y_pred, _, map_rev_pred = relabel_sequential(y_pred)
  152. overlap = label_overlap(y_true, y_pred, check=False)
  153. scores = matching_criteria[criterion](overlap)
  154. assert 0 <= np.min(scores) <= np.max(scores) <= 1
  155. # ignoring background
  156. scores = scores[1:,1:]
  157. n_true, n_pred = scores.shape
  158. n_matched = min(n_true, n_pred)
  159. def _single(thr):
  160. not_trivial = n_matched > 0 and np.any(scores >= thr)
  161. if not_trivial:
  162. # compute optimal matching with scores as tie-breaker
  163. costs = -(scores >= thr).astype(float) - scores / (2*n_matched)
  164. true_ind, pred_ind = linear_sum_assignment(costs)
  165. assert n_matched == len(true_ind) == len(pred_ind)
  166. match_ok = scores[true_ind,pred_ind] >= thr
  167. tp = np.count_nonzero(match_ok)
  168. else:
  169. tp = 0
  170. fp = n_pred - tp
  171. fn = n_true - tp
  172. # assert tp+fp == n_pred
  173. # assert tp+fn == n_true
  174. # the score sum over all matched objects (tp)
  175. sum_matched_score = np.sum(scores[true_ind,pred_ind][match_ok]) if not_trivial else 0.0
  176. # the score average over all matched objects (tp)
  177. mean_matched_score = _safe_divide(sum_matched_score, tp)
  178. # the score average over all gt/true objects
  179. mean_true_score = _safe_divide(sum_matched_score, n_true)
  180. panoptic_quality = _safe_divide(sum_matched_score, tp+fp/2+fn/2)
  181. stats_dict = dict (
  182. criterion = criterion,
  183. thresh = thr,
  184. fp = fp,
  185. tp = tp,
  186. fn = fn,
  187. precision = precision(tp,fp,fn),
  188. recall = recall(tp,fp,fn),
  189. accuracy = accuracy(tp,fp,fn),
  190. f1 = f1(tp,fp,fn),
  191. n_true = n_true,
  192. n_pred = n_pred,
  193. mean_true_score = mean_true_score,
  194. mean_matched_score = mean_matched_score,
  195. panoptic_quality = panoptic_quality,
  196. )
  197. if bool(report_matches):
  198. if not_trivial:
  199. stats_dict.update (
  200. # int() to be json serializable
  201. matched_pairs = tuple((int(map_rev_true[i]),int(map_rev_pred[j])) for i,j in zip(1+true_ind,1+pred_ind)),
  202. matched_scores = tuple(scores[true_ind,pred_ind]),
  203. matched_tps = tuple(map(int,np.flatnonzero(match_ok))),
  204. )
  205. else:
  206. stats_dict.update (
  207. matched_pairs = (),
  208. matched_scores = (),
  209. matched_tps = (),
  210. )
  211. return namedtuple('Matching',stats_dict.keys())(*stats_dict.values())
  212. return _single(thresh) if np.isscalar(thresh) else tuple(map(_single,thresh))
  213. def matching_dataset(y_true, y_pred, thresh=0.5, criterion='iou', by_image=False, show_progress=True, parallel=False):
  214. """matching metrics for list of images, see `stardist.matching.matching`
  215. """
  216. len(y_true) == len(y_pred) or _raise(ValueError("y_true and y_pred must have the same length."))
  217. return matching_dataset_lazy (
  218. tuple(zip(y_true,y_pred)), thresh=thresh, criterion=criterion, by_image=by_image, show_progress=show_progress, parallel=parallel,
  219. )
  220. def matching_dataset_lazy(y_gen, thresh=0.5, criterion='iou', by_image=False, show_progress=True, parallel=False):
  221. expected_keys = set(('fp', 'tp', 'fn', 'precision', 'recall', 'accuracy', 'f1', 'criterion', 'thresh', 'n_true', 'n_pred', 'mean_true_score', 'mean_matched_score', 'panoptic_quality'))
  222. single_thresh = False
  223. if np.isscalar(thresh):
  224. single_thresh = True
  225. thresh = (thresh,)
  226. tqdm_kwargs = {}
  227. tqdm_kwargs['disable'] = not bool(show_progress)
  228. if int(show_progress) > 1:
  229. tqdm_kwargs['total'] = int(show_progress)
  230. # compute matching stats for every pair of label images
  231. if parallel:
  232. from concurrent.futures import ThreadPoolExecutor
  233. fn = lambda pair: matching(*pair, thresh=thresh, criterion=criterion, report_matches=False)
  234. with ThreadPoolExecutor() as pool:
  235. stats_all = tuple(pool.map(fn, tqdm(y_gen,**tqdm_kwargs)))
  236. else:
  237. stats_all = tuple (
  238. matching(y_t, y_p, thresh=thresh, criterion=criterion, report_matches=False)
  239. for y_t,y_p in tqdm(y_gen,**tqdm_kwargs)
  240. )
  241. # accumulate results over all images for each threshold separately
  242. n_images, n_threshs = len(stats_all), len(thresh)
  243. accumulate = [{} for _ in range(n_threshs)]
  244. for stats in stats_all:
  245. for i,s in enumerate(stats):
  246. acc = accumulate[i]
  247. for k,v in s._asdict().items():
  248. if k == 'mean_true_score' and not bool(by_image):
  249. # convert mean_true_score to "sum_matched_score"
  250. acc[k] = acc.setdefault(k,0) + v * s.n_true
  251. else:
  252. try:
  253. acc[k] = acc.setdefault(k,0) + v
  254. except TypeError:
  255. pass
  256. # normalize/compute 'precision', 'recall', 'accuracy', 'f1'
  257. for thr,acc in zip(thresh,accumulate):
  258. set(acc.keys()) == expected_keys or _raise(ValueError("unexpected keys"))
  259. acc['criterion'] = criterion
  260. acc['thresh'] = thr
  261. acc['by_image'] = bool(by_image)
  262. if bool(by_image):
  263. for k in ('precision', 'recall', 'accuracy', 'f1', 'mean_true_score', 'mean_matched_score', 'panoptic_quality'):
  264. acc[k] /= n_images
  265. else:
  266. tp, fp, fn, n_true = acc['tp'], acc['fp'], acc['fn'], acc['n_true']
  267. sum_matched_score = acc['mean_true_score']
  268. mean_matched_score = _safe_divide(sum_matched_score, tp)
  269. mean_true_score = _safe_divide(sum_matched_score, n_true)
  270. panoptic_quality = _safe_divide(sum_matched_score, tp+fp/2+fn/2)
  271. acc.update(
  272. precision = precision(tp,fp,fn),
  273. recall = recall(tp,fp,fn),
  274. accuracy = accuracy(tp,fp,fn),
  275. f1 = f1(tp,fp,fn),
  276. mean_true_score = mean_true_score,
  277. mean_matched_score = mean_matched_score,
  278. panoptic_quality = panoptic_quality,
  279. )
  280. accumulate = tuple(namedtuple('DatasetMatching',acc.keys())(*acc.values()) for acc in accumulate)
  281. return accumulate[0] if single_thresh else accumulate
  282. # copied from scikit-image master for now (remove when part of a release)
  283. def relabel_sequential(label_field, offset=1):
  284. """Relabel arbitrary labels to {`offset`, ... `offset` + number_of_labels}.
  285. This function also returns the forward map (mapping the original labels to
  286. the reduced labels) and the inverse map (mapping the reduced labels back
  287. to the original ones).
  288. Parameters
  289. ----------
  290. label_field : numpy array of int, arbitrary shape
  291. An array of labels, which must be non-negative integers.
  292. offset : int, optional
  293. The return labels will start at `offset`, which should be
  294. strictly positive.
  295. Returns
  296. -------
  297. relabeled : numpy array of int, same shape as `label_field`
  298. The input label field with labels mapped to
  299. {offset, ..., number_of_labels + offset - 1}.
  300. The data type will be the same as `label_field`, except when
  301. offset + number_of_labels causes overflow of the current data type.
  302. forward_map : numpy array of int, shape ``(label_field.max() + 1,)``
  303. The map from the original label space to the returned label
  304. space. Can be used to re-apply the same mapping. See examples
  305. for usage. The data type will be the same as `relabeled`.
  306. inverse_map : 1D numpy array of int, of length offset + number of labels
  307. The map from the new label space to the original space. This
  308. can be used to reconstruct the original label field from the
  309. relabeled one. The data type will be the same as `relabeled`.
  310. Notes
  311. -----
  312. The label 0 is assumed to denote the background and is never remapped.
  313. The forward map can be extremely big for some inputs, since its
  314. length is given by the maximum of the label field. However, in most
  315. situations, ``label_field.max()`` is much smaller than
  316. ``label_field.size``, and in these cases the forward map is
  317. guaranteed to be smaller than either the input or output images.
  318. Examples
  319. --------
  320. >>> from skimage.segmentation import relabel_sequential
  321. >>> label_field = np.array([1, 1, 5, 5, 8, 99, 42])
  322. >>> relab, fw, inv = relabel_sequential(label_field)
  323. >>> relab
  324. array([1, 1, 2, 2, 3, 5, 4])
  325. >>> fw
  326. array([0, 1, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  327. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0,
  328. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  329. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  330. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5])
  331. >>> inv
  332. array([ 0, 1, 5, 8, 42, 99])
  333. >>> (fw[label_field] == relab).all()
  334. True
  335. >>> (inv[relab] == label_field).all()
  336. True
  337. >>> relab, fw, inv = relabel_sequential(label_field, offset=5)
  338. >>> relab
  339. array([5, 5, 6, 6, 7, 9, 8])
  340. """
  341. offset = int(offset)
  342. if offset <= 0:
  343. raise ValueError("Offset must be strictly positive.")
  344. if np.min(label_field) < 0:
  345. raise ValueError("Cannot relabel array that contains negative values.")
  346. max_label = int(label_field.max()) # Ensure max_label is an integer
  347. if not np.issubdtype(label_field.dtype, np.integer):
  348. new_type = np.min_scalar_type(max_label)
  349. label_field = label_field.astype(new_type)
  350. labels = np.unique(label_field)
  351. labels0 = labels[labels != 0]
  352. new_max_label = offset - 1 + len(labels0)
  353. new_labels0 = np.arange(offset, new_max_label + 1)
  354. output_type = label_field.dtype
  355. required_type = np.min_scalar_type(new_max_label)
  356. if np.dtype(required_type).itemsize > np.dtype(label_field.dtype).itemsize:
  357. output_type = required_type
  358. forward_map = np.zeros(max_label + 1, dtype=output_type)
  359. forward_map[labels0] = new_labels0
  360. inverse_map = np.zeros(new_max_label + 1, dtype=output_type)
  361. inverse_map[offset:] = labels0
  362. relabeled = forward_map[label_field]
  363. return relabeled, forward_map, inverse_map
  364. # ------------- For display ------------
  365. def showQCResults( model, image_folder ):
  366. image_folder = Path(image_folder)
  367. # Grab all tif images
  368. raw_images = [x for x in Path(image_folder).glob("*.tif") if not ( "masks" in x.name or "flows" in x.name )]
  369. gt_images = [x for x in Path(image_folder).glob("*.tif") if ( "masks" in x.name and not "cp_masks" in x.name )]
  370. pred_images = [x for x in Path(image_folder).glob("*.tif") if ( "cp_masks" in x.name )]
  371. # Define save folder from the parent of the predicted_labels_folder
  372. results_path = image_folder / "QC-Results"
  373. # Plot for each image
  374. for raw, gt, pred in zip(raw_images, gt_images, pred_images):
  375. print( 'Running QC on: ' + raw.name + " with model : "+model)
  376. plt.figure(figsize=(25,5))
  377. source_image = io.imread( raw )
  378. target_image = io.imread( gt , as_gray = True)
  379. prediction = io.imread( pred, as_gray = True)
  380. stats = matching(prediction, target_image, thresh=0.5)
  381. target_image_mask = np.empty_like(target_image)
  382. target_image_mask[target_image > 0] = 255
  383. target_image_mask[target_image == 0] = 0
  384. prediction_mask = np.empty_like(prediction)
  385. prediction_mask[prediction > 0] = 255
  386. prediction_mask[prediction == 0] = 0
  387. intersection = np.logical_and(target_image_mask, prediction_mask)
  388. union = np.logical_or(target_image_mask, prediction_mask)
  389. iou_score = np.sum(intersection) / np.sum(union)
  390. norm = simple_norm(source_image, percent = 99)
  391. #Input
  392. plt.subplot(1,4,1)
  393. plt.axis('off')
  394. plt.imshow(source_image)
  395. plt.title( raw.name )
  396. #Ground-truth
  397. plt.subplot(1,4,2)
  398. plt.axis('off')
  399. plt.imshow(target_image_mask, aspect='equal', cmap='Greens')
  400. plt.title('Ground Truth')
  401. #Prediction
  402. plt.subplot(1,4,3)
  403. plt.axis('off')
  404. plt.imshow(prediction_mask, aspect='equal', cmap='Purples')
  405. plt.title('Prediction')
  406. #Overlay
  407. plt.subplot(1,4,4)
  408. plt.axis('off')
  409. plt.imshow(target_image_mask, cmap='Greens')
  410. plt.imshow(prediction_mask, alpha=0.5, cmap='Purples')
  411. plt.title('Ground Truth and Prediction, Intersection over Union:'+str(round(iou_score,3 )));
  412. plt.savefig(results_path / (model+"_"+raw.name.replace(".tif",".png")),bbox_inches='tight',pad_inches=0)
  413. # Here we start testing the differences between GT and predicted label images
  414. def compareLabels( model_name, image_folder ):
  415. image_folder = Path(image_folder)
  416. # Grab all tif images
  417. raw_images = [x for x in Path(image_folder).glob("*.tif") if not ( "masks" in x.name or "flows" in x.name )]
  418. gt_images = [x for x in Path(image_folder).glob("*.tif") if ( "masks" in x.name and not "cp_masks" in x.name )]
  419. pred_images = [x for x in Path(image_folder).glob("*.tif") if ( "cp_masks" in x.name )]
  420. # Define save folder from the parent of the predicted_labels_folder
  421. results_path = image_folder / "QC-Results"
  422. # Make the directory if it's missing
  423. results_path.absolute().mkdir( exist_ok=True )
  424. with open(results_path / ( "Quality_Control for "+model_name+".csv" ), "w", newline='') as file:
  425. writer = csv.writer(file, delimiter=",")
  426. writer.writerow(["model","image","Prediction v. GT Intersection over Union", "false positive", "true positive", "false negative", "precision", "recall", "accuracy", "f1 score", "n_true", "n_pred", "mean_true_score", "mean_matched_score", "panoptic_quality"])
  427. # define the images
  428. for raw, gt, pred in zip(raw_images, gt_images, pred_images):
  429. print( 'Running QC on: ' + raw.name )
  430. test_input = io.imread(raw)
  431. test_prediction = io.imread(pred)
  432. test_ground_truth_image = io.imread(gt)
  433. # Calculate the matching (with IoU threshold `thresh`) and all metrics
  434. stats = matching(test_ground_truth_image, test_prediction, thresh=0.5)
  435. #Convert pixel values to 0 or 255
  436. test_prediction_0_to_255 = test_prediction
  437. test_prediction_0_to_255[test_prediction_0_to_255>0] = 255
  438. #Convert pixel values to 0 or 255
  439. test_ground_truth_0_to_255 = test_ground_truth_image
  440. test_ground_truth_0_to_255[test_ground_truth_0_to_255>0] = 255
  441. # Intersection over Union metric
  442. intersection = np.logical_and(test_ground_truth_0_to_255, test_prediction_0_to_255)
  443. union = np.logical_or(test_ground_truth_0_to_255, test_prediction_0_to_255)
  444. iou_score = np.sum(intersection) / np.sum(union)
  445. writer.writerow([model_name, raw, str(iou_score), str(stats.fp), str(stats.tp), str(stats.fn), str(stats.precision), str(stats.recall), str(stats.accuracy), str(stats.f1), str(stats.n_true), str(stats.n_pred), str(stats.mean_true_score), str(stats.mean_matched_score), str(stats.panoptic_quality)])
  446. df = pd.read_csv (results_path / ( "Quality_Control for "+model_name+".csv" ))
  447. display(tabulate(df, headers='keys', tablefmt="html"))
  448. # %% [markdown]
  449. # # Open Images and Compute QC Metrics
  450. # %%
  451. data_folder = "../Cellpose Training Folder/test"
  452. model_name = "cellpose_residual_on_style_on_concatenation_off_train_2021_10_28_11_33_45.741069"
  453. compareLabels(model_name, data_folder)
  454. showQCResults(model_name, data_folder)
  455. # %%

Quality Control-Cellpose.ipynb at commit 5d14816, under Apache-2.0 · at the source

Overview

Authors: Sampurna Chakrabarti1,2, Anuar Makhmut3,4, Atena Mohammadi1, Wenhan Luo1, Lin Wang1, Gary R Lewin1,4,5, Fabian Coscia3,6
  1. Max-Delbrück-Center for Molecular Medicine in the Helmholtz Association (MDC), Molecular Physiology of Somatic Sensation Laboratory, 13125 Berlin, Germany
  2. Helmholtz Centre for Infection Research (HZI), Pathways in Infection and Nociception (PAIN) Group, 38124 Braunschweig, Germany
  3. Max-Delbrück-Center for Molecular Medicine in the Helmholtz Association (MDC), Spatial Proteomics Group, 13125 Berlin, Germany
  4. Charité-Universitätsmedizin Berlin, Charitéplatz 1, 10117 Berlin, Germany
  5. German Center for Mental Health (DZPG), Partner Site Berlin, Berlin, Germany
  6. German Cancer Consortium (DKTK), Partner Site Berlin, Heidelberg, Germany
Journal: Nature communications, volume 17, issue 1, article 3437
Dates: received 12 February 2026; accepted 19 March 2026; published online 11 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-71418-8 · PMID 41965357 · PMCID PMC13076600 · OpenAlex W7153557317
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), mouse (organism), pain (population), cellular / molecular (subfield)
Methods: Spectral & time-frequency, Statistics, Machine learning, Preprocessing, Connectivity
Keywords: Somatosensory system, Chronic pain
MeSH: Nociceptors*, Pain*, Proteome*, Proteomics*, Animals, Ganglia, Spinal, Male, Mice, Mice, Inbred C57BL, Nerve Growth Factor, Protein Kinase C, Sensory Receptor Cells, Transcriptome (* major topic)
Topic: Pain Mechanisms and Treatments (Physiology, Medicine), according to OpenAlex
Funding: European Research Council (101142488, 101115681); Bundesministerium für Bildung und Forschung (Federal Ministry of Education and Research) (161L0222)
Citations: cited by 2 papers (Europe PMC); 66 references in the paper

Abstract

The richness of our somatosensory experience is reflected in the functional diversity of somatic sensory neurons. Single-cell RNA sequencing of sensory neurons has revealed a molecular basis for such diversity1–3. However, sensory neuron diversity has yet to be captured at the level of the proteome. Here, we combined electrophysiology with deep visual proteomics 4 to quantify over 6000 proteins from phenotypically-defined sensory neurons in mice and identified proteomic markers of sensory neuron subtypes. Comparative analysis revealed both concordance and meaningful divergence between transcriptomes and proteomes. We further show that up to 3000 proteins can be quantified from one-fourth of a single neuron, demonstrating subset-specific protein signatures. In culture, nociceptive neurons can be acutely sensitized to mechanical stimuli by nerve growth factor (NGF) which normally drives inflammatory pain in vivo5. Indeed, overnight exposure of peptidergic nociceptors to NGF and a protein kinase C (PKC) activator produced functional sensitization associated with proteome changes. Functional knockdown experiments identified the up-regulated B3GNT2 enzyme as a potential effector of nociceptor sensitization. In summary, we present a high-resolution proteomic resource linking molecular identity to function, enabling the discovery of mechanisms underlying somatic sensation and pain sensitization.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above.

BIOP/qupath-extension-cellpose

License: Apache-2.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 5d1481674b467bb4a590cc7d39b29f58fe40c287, 6 July 2026
Languages: JavaScript (10), Java (6), Jupyter (1), Python (1)
Size: 109 files, 18 scripts
Software Heritage: archived
Found in: the text, “Image segmentation”
Holds: README, license file, continuous integration, documentation, 1 notebook
Not found: CITATION.cff, environment file, tests
Tools: NumPy (2 files), scikit-image (2 files), SciPy (2 files), Matplotlib (1 file), Numba (1 file), OpenCV (1 file), pandas (1 file), scikit-learn (1 file), tifffile (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
20 files

CosciaLab/Qupath_to_LMD

License: GPL-3.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 32df199eea7595a465a880ed99fd15e172c4e4a6, 27 August 2026
Languages: Python (30)
Size: 75 files, 30 scripts
Software Heritage: not archived
Found in: the text, “Image segmentation”
Holds: README, license file, environment (pyproject.toml, requirements.txt, uv.lock), tests, continuous integration
Not found: CITATION.cff, documentation
Tools: NumPy (10 files), pandas (10 files), Matplotlib (2 files)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
32 files

Code availability

The code used for analysis and plots were adapted from standard R packages as detailed in the Methods section. More detailed information is available upon request.

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 48 scripts, each with its path and the digest of its content;
  • no match between paragraphs and code yet;
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data availability

The mass spectrometry proteomics data have been deposited to the ProteomeXchange Consortium via the PRIDE partner repository66 with the dataset identifier PXD070495 (https://www.ebi.ac.uk/pride/archive). Source data are provided in this paper.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 29 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 2 keywords, 13 MeSH terms, 2 funders, 64 references.

Cite

This paper

Chakrabarti, S., Makhmut, A., Mohammadi, A., Luo, W., Wang, L., Lewin, G. R., & Coscia, F. (2026). Deep visual proteomics uncovers nociceptor diversity and pain targets. Nature communications, 17(1), 3437. https://doi.org/10.1038/s41467-026-71418-8

BibTeX

@article{chakrabarti2026deep,
author = {Chakrabarti, Sampurna and Makhmut, Anuar and Mohammadi, Atena and Luo, Wenhan and Wang, Lin and Lewin, Gary R and Coscia, Fabian},
title = {{Deep visual proteomics uncovers nociceptor diversity and pain targets}},
journal = {Nature communications},
year = {2026},
month = apr,
volume = {17},
number = {1},
pages = {3437},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-71418-8},
url = {https://doi.org/10.1038/s41467-026-71418-8},
pmid = {41965357},
pmcid = {PMC13076600}
}

RIS

TY - JOUR
AU - Chakrabarti, Sampurna
AU - Makhmut, Anuar
AU - Mohammadi, Atena
AU - Luo, Wenhan
AU - Wang, Lin
AU - Lewin, Gary R
AU - Coscia, Fabian
TI - Deep visual proteomics uncovers nociceptor diversity and pain targets
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/04/11
VL - 17
IS - 1
SP - 3437
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-71418-8
UR - https://doi.org/10.1038/s41467-026-71418-8
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-71418-8",
"type": "article-journal",
"title": "Deep visual proteomics uncovers nociceptor diversity and pain targets",
"container-title": "Nature communications",
"author": [
{
"family": "Chakrabarti",
"given": "Sampurna"
},
{
"family": "Makhmut",
"given": "Anuar"
},
{
"family": "Mohammadi",
"given": "Atena"
},
{
"family": "Luo",
"given": "Wenhan"
},
{
"family": "Wang",
"given": "Lin"
},
{
"family": "Lewin",
"given": "Gary R"
},
{
"family": "Coscia",
"given": "Fabian"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "3437",
"DOI": "10.1038/s41467-026-71418-8",
"PMID": "41965357",
"PMCID": "PMC13076600",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-71418-8",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
11
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1371/journal.pcbi.1014571 [code]
SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.
Journal: PLoS computational biology
In common: tifffile, Numba, OpenCV, 6 other tools, 2 references
[2] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: tifffile, Numba, OpenCV, 6 other tools, genetics / omics, cellular / molecular, 1 reference
[3] doi: [code]
Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control
Journal: eLife
In common: tifffile, Numba, OpenCV, 6 other tools, mouse
[4] doi:10.7554/elife.109717 [code]
Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.
Journal: eLife
In common: tifffile, Numba, OpenCV, 6 other tools, mouse
[5] doi:10.1038/s41598-026-57519-w [code]
Automated segmentation of neurons and spinal cord structures in immunofluorescence images using SpineDL.
Journal: Scientific reports
In common: tifffile, Numba, OpenCV, 6 other tools, mouse
[6] doi:10.1038/s41467-026-73373-w [code]
Mapping neuro-vascular unit communications reveals distinct angiogenic programs across developing mouse brain regions.
Journal: Nature communications
In common: tifffile, OpenCV, scikit-image, 5 other tools, genetics / omics, mouse, 1 reference
[7] doi:10.7554/elife.100880 [code]
An applicable and efficient retrograde monosynaptic circuit mapping tool for larval zebrafish.
Journal: eLife
In common: tifffile, Numba, OpenCV, 6 other tools
[8] doi:10.1002/advs.202515887 [code]
An Open-Source Pipeline for Calcium Imaging and All-Optical Physiology in Human Stem Cell-Derived Neurons.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: tifffile, Numba, scikit-image, 5 other tools, 1 reference
[9] doi:10.1038/s41467-026-71458-0 [code]
Early differential impact of MeCP2 mutations on functional networks in Rett syndrome patient-derived human cortical organoids.
Journal: Nature communications
In common: tifffile, Numba, OpenCV, 5 other tools, cellular / molecular
[10] doi:10.1038/s41586-026-10679-1 [code]
Cortical development dynamics across autism spectrum disorder mouse models.
Journal: Nature
In common: tifffile, OpenCV, scikit-image, 5 other tools, genetics / omics, mouse, cellular / molecular

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.