Deep visual proteomics uncovers nociceptor diversity and pain targets.
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
- # %% [markdown]
- # 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)
- # %% [markdown]
- # # Installation
- # If you are missing dependencies, these are the ones needed to run this notebook. Uncomment the line as needed
- # %%
- #!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
- # %% [markdown]
- # # All imports and functions
- # %%
- import csv
- import cv2
- import matplotlib as mpl
- import numpy as np
- import os
- import pandas as pd
- import random
- import shutil
- import subprocess
- import sys
- import time
- import urllib
- import zipfile
- from astropy.visualization import simple_norm
- from collections import namedtuple
- from datetime import datetime
- from glob import glob
- from matplotlib import pyplot as plt
- from numba import jit
- from pathlib import Path
- from pip._internal.operations.freeze import freeze
- from scipy import signal, ndimage
- from scipy.optimize import linear_sum_assignment
- from skimage import io
- from skimage.metrics import structural_similarity
- from skimage.metrics import peak_signal_noise_ratio as psnr
- from skimage.util import img_as_ubyte, img_as_uint, img_as_float32
- from sklearn.linear_model import LinearRegression
- from tabulate import tabulate
- from tifffile import imread, imsave
- from tqdm import tqdm
- from zipfile import ZIP_DEFLATED
- from IPython.display import HTML
- from IPython.display import display
- matching_criteria = dict()
- def label_are_sequential(y):
- """ returns true if y has only sequential labels from 1... """
- labels = np.unique(y)
- return (set(labels)-{0}) == set(range(1,1+labels.max()))
- def is_array_of_integers(y):
- return isinstance(y,np.ndarray) and np.issubdtype(y.dtype, np.integer)
- def _check_label_array(y, name=None, check_sequential=False):
- err = ValueError("{label} must be an array of {integers}.".format(
- label = 'labels' if name is None else name,
- integers = ('sequential ' if check_sequential else '') + 'non-negative integers',
- ))
- is_array_of_integers(y) or print("An error occured")
- if check_sequential:
- label_are_sequential(y) or print("An error occured")
- else:
- y.min() >= 0 or print("An error occured")
- return True
- def label_overlap(x, y, check=True):
- if check:
- _check_label_array(x,'x',True)
- _check_label_array(y,'y',True)
- x.shape == y.shape or _raise(ValueError("x and y must have the same shape"))
- return _label_overlap(x, y)
- @jit(nopython=True)
- def _label_overlap(x, y):
- x = x.ravel()
- y = y.ravel()
- overlap = np.zeros((1+x.max(),1+y.max()), dtype=np.uint)
- for i in range(len(x)):
- overlap[x[i],y[i]] += 1
- return overlap
- def intersection_over_union(overlap):
- _check_label_array(overlap,'overlap')
- if np.sum(overlap) == 0:
- return overlap
- n_pixels_pred = np.sum(overlap, axis=0, keepdims=True)
- n_pixels_true = np.sum(overlap, axis=1, keepdims=True)
- return overlap / (n_pixels_pred + n_pixels_true - overlap)
- matching_criteria['iou'] = intersection_over_union
- def intersection_over_true(overlap):
- _check_label_array(overlap,'overlap')
- if np.sum(overlap) == 0:
- return overlap
- n_pixels_true = np.sum(overlap, axis=1, keepdims=True)
- return overlap / n_pixels_true
- matching_criteria['iot'] = intersection_over_true
- def intersection_over_pred(overlap):
- _check_label_array(overlap,'overlap')
- if np.sum(overlap) == 0:
- return overlap
- n_pixels_pred = np.sum(overlap, axis=0, keepdims=True)
- return overlap / n_pixels_pred
- matching_criteria['iop'] = intersection_over_pred
- def precision(tp,fp,fn):
- return tp/(tp+fp) if tp > 0 else 0
- def recall(tp,fp,fn):
- return tp/(tp+fn) if tp > 0 else 0
- def accuracy(tp,fp,fn):
- # also known as "average precision" (?)
- # -> https://www.kaggle.com/c/data-science-bowl-2018#evaluation
- return tp/(tp+fp+fn) if tp > 0 else 0
- def f1(tp,fp,fn):
- # also known as "dice coefficient"
- return (2*tp)/(2*tp+fp+fn) if tp > 0 else 0
- def _safe_divide(x,y):
- return x/y if y>0 else 0.0
- def matching(y_true, y_pred, thresh=0.5, criterion='iou', report_matches=False):
- """Calculate detection/instance segmentation metrics between ground truth and predicted label images.
- Currently, the following metrics are implemented:
- 'fp', 'tp', 'fn', 'precision', 'recall', 'accuracy', 'f1', 'criterion', 'thresh', 'n_true', 'n_pred', 'mean_true_score', 'mean_matched_score', 'panoptic_quality'
- Corresponding objects of y_true and y_pred are counted as true positives (tp), false positives (fp), and false negatives (fn)
- whether their intersection over union (IoU) >= thresh (for criterion='iou', which can be changed)
- * mean_matched_score is the mean IoUs of matched true positives
- * mean_true_score is the mean IoUs of matched true positives but normalized by the total number of GT objects
- * panoptic_quality defined as in Eq. 1 of Kirillov et al. "Panoptic Segmentation", CVPR 2019
- Parameters
- ----------
- y_true: ndarray
- ground truth label image (integer valued)
- predicted label image (integer valued)
- thresh: float
- threshold for matching criterion (default 0.5)
- criterion: string
- matching criterion (default IoU)
- report_matches: bool
- if True, additionally calculate matched_pairs and matched_scores (note, that this returns even gt-pred pairs whose scores are below 'thresh')
- Returns
- -------
- Matching object with different metrics as attributes
- Examples
- --------
- >>> y_true = np.zeros((100,100), np.uint16)
- >>> y_true[10:20,10:20] = 1
- >>> y_pred = np.roll(y_true,5,axis = 0)
- >>> stats = matching(y_true, y_pred)
- >>> print(stats)
- 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)
- """
- _check_label_array(y_true,'y_true')
- _check_label_array(y_pred,'y_pred')
- 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)))
- criterion in matching_criteria or _raise(ValueError("Matching criterion '%s' not supported." % criterion))
- if thresh is None: thresh = 0
- thresh = float(thresh) if np.isscalar(thresh) else map(float,thresh)
- y_true, _, map_rev_true = relabel_sequential(y_true)
- y_pred, _, map_rev_pred = relabel_sequential(y_pred)
- overlap = label_overlap(y_true, y_pred, check=False)
- scores = matching_criteria[criterion](overlap)
- assert 0 <= np.min(scores) <= np.max(scores) <= 1
- # ignoring background
- scores = scores[1:,1:]
- n_true, n_pred = scores.shape
- n_matched = min(n_true, n_pred)
- def _single(thr):
- not_trivial = n_matched > 0 and np.any(scores >= thr)
- if not_trivial:
- # compute optimal matching with scores as tie-breaker
- costs = -(scores >= thr).astype(float) - scores / (2*n_matched)
- true_ind, pred_ind = linear_sum_assignment(costs)
- assert n_matched == len(true_ind) == len(pred_ind)
- match_ok = scores[true_ind,pred_ind] >= thr
- tp = np.count_nonzero(match_ok)
- else:
- tp = 0
- fp = n_pred - tp
- fn = n_true - tp
- # assert tp+fp == n_pred
- # assert tp+fn == n_true
- # the score sum over all matched objects (tp)
- sum_matched_score = np.sum(scores[true_ind,pred_ind][match_ok]) if not_trivial else 0.0
- # the score average over all matched objects (tp)
- mean_matched_score = _safe_divide(sum_matched_score, tp)
- # the score average over all gt/true objects
- mean_true_score = _safe_divide(sum_matched_score, n_true)
- panoptic_quality = _safe_divide(sum_matched_score, tp+fp/2+fn/2)
- stats_dict = dict (
- criterion = criterion,
- thresh = thr,
- fp = fp,
- tp = tp,
- fn = fn,
- precision = precision(tp,fp,fn),
- recall = recall(tp,fp,fn),
- accuracy = accuracy(tp,fp,fn),
- f1 = f1(tp,fp,fn),
- n_true = n_true,
- n_pred = n_pred,
- mean_true_score = mean_true_score,
- mean_matched_score = mean_matched_score,
- panoptic_quality = panoptic_quality,
- )
- if bool(report_matches):
- if not_trivial:
- stats_dict.update (
- # int() to be json serializable
- matched_pairs = tuple((int(map_rev_true[i]),int(map_rev_pred[j])) for i,j in zip(1+true_ind,1+pred_ind)),
- matched_scores = tuple(scores[true_ind,pred_ind]),
- matched_tps = tuple(map(int,np.flatnonzero(match_ok))),
- )
- else:
- stats_dict.update (
- matched_pairs = (),
- matched_scores = (),
- matched_tps = (),
- )
- return namedtuple('Matching',stats_dict.keys())(*stats_dict.values())
- return _single(thresh) if np.isscalar(thresh) else tuple(map(_single,thresh))
- def matching_dataset(y_true, y_pred, thresh=0.5, criterion='iou', by_image=False, show_progress=True, parallel=False):
- """matching metrics for list of images, see `stardist.matching.matching`
- """
- len(y_true) == len(y_pred) or _raise(ValueError("y_true and y_pred must have the same length."))
- return matching_dataset_lazy (
- tuple(zip(y_true,y_pred)), thresh=thresh, criterion=criterion, by_image=by_image, show_progress=show_progress, parallel=parallel,
- )
- def matching_dataset_lazy(y_gen, thresh=0.5, criterion='iou', by_image=False, show_progress=True, parallel=False):
- expected_keys = set(('fp', 'tp', 'fn', 'precision', 'recall', 'accuracy', 'f1', 'criterion', 'thresh', 'n_true', 'n_pred', 'mean_true_score', 'mean_matched_score', 'panoptic_quality'))
- single_thresh = False
- if np.isscalar(thresh):
- single_thresh = True
- thresh = (thresh,)
- tqdm_kwargs = {}
- tqdm_kwargs['disable'] = not bool(show_progress)
- if int(show_progress) > 1:
- tqdm_kwargs['total'] = int(show_progress)
- # compute matching stats for every pair of label images
- if parallel:
- from concurrent.futures import ThreadPoolExecutor
- fn = lambda pair: matching(*pair, thresh=thresh, criterion=criterion, report_matches=False)
- with ThreadPoolExecutor() as pool:
- stats_all = tuple(pool.map(fn, tqdm(y_gen,**tqdm_kwargs)))
- else:
- stats_all = tuple (
- matching(y_t, y_p, thresh=thresh, criterion=criterion, report_matches=False)
- for y_t,y_p in tqdm(y_gen,**tqdm_kwargs)
- )
- # accumulate results over all images for each threshold separately
- n_images, n_threshs = len(stats_all), len(thresh)
- accumulate = [{} for _ in range(n_threshs)]
- for stats in stats_all:
- for i,s in enumerate(stats):
- acc = accumulate[i]
- for k,v in s._asdict().items():
- if k == 'mean_true_score' and not bool(by_image):
- # convert mean_true_score to "sum_matched_score"
- acc[k] = acc.setdefault(k,0) + v * s.n_true
- else:
- try:
- acc[k] = acc.setdefault(k,0) + v
- except TypeError:
- pass
- # normalize/compute 'precision', 'recall', 'accuracy', 'f1'
- for thr,acc in zip(thresh,accumulate):
- set(acc.keys()) == expected_keys or _raise(ValueError("unexpected keys"))
- acc['criterion'] = criterion
- acc['thresh'] = thr
- acc['by_image'] = bool(by_image)
- if bool(by_image):
- for k in ('precision', 'recall', 'accuracy', 'f1', 'mean_true_score', 'mean_matched_score', 'panoptic_quality'):
- acc[k] /= n_images
- else:
- tp, fp, fn, n_true = acc['tp'], acc['fp'], acc['fn'], acc['n_true']
- sum_matched_score = acc['mean_true_score']
- mean_matched_score = _safe_divide(sum_matched_score, tp)
- mean_true_score = _safe_divide(sum_matched_score, n_true)
- panoptic_quality = _safe_divide(sum_matched_score, tp+fp/2+fn/2)
- acc.update(
- precision = precision(tp,fp,fn),
- recall = recall(tp,fp,fn),
- accuracy = accuracy(tp,fp,fn),
- f1 = f1(tp,fp,fn),
- mean_true_score = mean_true_score,
- mean_matched_score = mean_matched_score,
- panoptic_quality = panoptic_quality,
- )
- accumulate = tuple(namedtuple('DatasetMatching',acc.keys())(*acc.values()) for acc in accumulate)
- return accumulate[0] if single_thresh else accumulate
- # copied from scikit-image master for now (remove when part of a release)
- def relabel_sequential(label_field, offset=1):
- """Relabel arbitrary labels to {`offset`, ... `offset` + number_of_labels}.
- This function also returns the forward map (mapping the original labels to
- the reduced labels) and the inverse map (mapping the reduced labels back
- to the original ones).
- Parameters
- ----------
- label_field : numpy array of int, arbitrary shape
- An array of labels, which must be non-negative integers.
- offset : int, optional
- The return labels will start at `offset`, which should be
- strictly positive.
- Returns
- -------
- relabeled : numpy array of int, same shape as `label_field`
- The input label field with labels mapped to
- {offset, ..., number_of_labels + offset - 1}.
- The data type will be the same as `label_field`, except when
- offset + number_of_labels causes overflow of the current data type.
- forward_map : numpy array of int, shape ``(label_field.max() + 1,)``
- The map from the original label space to the returned label
- space. Can be used to re-apply the same mapping. See examples
- for usage. The data type will be the same as `relabeled`.
- inverse_map : 1D numpy array of int, of length offset + number of labels
- The map from the new label space to the original space. This
- can be used to reconstruct the original label field from the
- relabeled one. The data type will be the same as `relabeled`.
- Notes
- -----
- The label 0 is assumed to denote the background and is never remapped.
- The forward map can be extremely big for some inputs, since its
- length is given by the maximum of the label field. However, in most
- situations, ``label_field.max()`` is much smaller than
- ``label_field.size``, and in these cases the forward map is
- guaranteed to be smaller than either the input or output images.
- Examples
- --------
- >>> from skimage.segmentation import relabel_sequential
- >>> label_field = np.array([1, 1, 5, 5, 8, 99, 42])
- >>> relab, fw, inv = relabel_sequential(label_field)
- >>> relab
- array([1, 1, 2, 2, 3, 5, 4])
- >>> fw
- array([0, 1, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5])
- >>> inv
- array([ 0, 1, 5, 8, 42, 99])
- >>> (fw[label_field] == relab).all()
- True
- >>> (inv[relab] == label_field).all()
- True
- >>> relab, fw, inv = relabel_sequential(label_field, offset=5)
- >>> relab
- array([5, 5, 6, 6, 7, 9, 8])
- """
- offset = int(offset)
- if offset <= 0:
- raise ValueError("Offset must be strictly positive.")
- if np.min(label_field) < 0:
- raise ValueError("Cannot relabel array that contains negative values.")
- max_label = int(label_field.max()) # Ensure max_label is an integer
- if not np.issubdtype(label_field.dtype, np.integer):
- new_type = np.min_scalar_type(max_label)
- label_field = label_field.astype(new_type)
- labels = np.unique(label_field)
- labels0 = labels[labels != 0]
- new_max_label = offset - 1 + len(labels0)
- new_labels0 = np.arange(offset, new_max_label + 1)
- output_type = label_field.dtype
- required_type = np.min_scalar_type(new_max_label)
- if np.dtype(required_type).itemsize > np.dtype(label_field.dtype).itemsize:
- output_type = required_type
- forward_map = np.zeros(max_label + 1, dtype=output_type)
- forward_map[labels0] = new_labels0
- inverse_map = np.zeros(new_max_label + 1, dtype=output_type)
- inverse_map[offset:] = labels0
- relabeled = forward_map[label_field]
- return relabeled, forward_map, inverse_map
- # ------------- For display ------------
- def showQCResults( model, image_folder ):
- image_folder = Path(image_folder)
- # Grab all tif images
- raw_images = [x for x in Path(image_folder).glob("*.tif") if not ( "masks" in x.name or "flows" in x.name )]
- gt_images = [x for x in Path(image_folder).glob("*.tif") if ( "masks" in x.name and not "cp_masks" in x.name )]
- pred_images = [x for x in Path(image_folder).glob("*.tif") if ( "cp_masks" in x.name )]
- # Define save folder from the parent of the predicted_labels_folder
- results_path = image_folder / "QC-Results"
- # Plot for each image
- for raw, gt, pred in zip(raw_images, gt_images, pred_images):
- print( 'Running QC on: ' + raw.name + " with model : "+model)
- plt.figure(figsize=(25,5))
- source_image = io.imread( raw )
- target_image = io.imread( gt , as_gray = True)
- prediction = io.imread( pred, as_gray = True)
- stats = matching(prediction, target_image, thresh=0.5)
- target_image_mask = np.empty_like(target_image)
- target_image_mask[target_image > 0] = 255
- target_image_mask[target_image == 0] = 0
- prediction_mask = np.empty_like(prediction)
- prediction_mask[prediction > 0] = 255
- prediction_mask[prediction == 0] = 0
- intersection = np.logical_and(target_image_mask, prediction_mask)
- union = np.logical_or(target_image_mask, prediction_mask)
- iou_score = np.sum(intersection) / np.sum(union)
- norm = simple_norm(source_image, percent = 99)
- #Input
- plt.subplot(1,4,1)
- plt.axis('off')
- plt.imshow(source_image)
- plt.title( raw.name )
- #Ground-truth
- plt.subplot(1,4,2)
- plt.axis('off')
- plt.imshow(target_image_mask, aspect='equal', cmap='Greens')
- plt.title('Ground Truth')
- #Prediction
- plt.subplot(1,4,3)
- plt.axis('off')
- plt.imshow(prediction_mask, aspect='equal', cmap='Purples')
- plt.title('Prediction')
- #Overlay
- plt.subplot(1,4,4)
- plt.axis('off')
- plt.imshow(target_image_mask, cmap='Greens')
- plt.imshow(prediction_mask, alpha=0.5, cmap='Purples')
- plt.title('Ground Truth and Prediction, Intersection over Union:'+str(round(iou_score,3 )));
- plt.savefig(results_path / (model+"_"+raw.name.replace(".tif",".png")),bbox_inches='tight',pad_inches=0)
- # Here we start testing the differences between GT and predicted label images
- def compareLabels( model_name, image_folder ):
- image_folder = Path(image_folder)
- # Grab all tif images
- raw_images = [x for x in Path(image_folder).glob("*.tif") if not ( "masks" in x.name or "flows" in x.name )]
- gt_images = [x for x in Path(image_folder).glob("*.tif") if ( "masks" in x.name and not "cp_masks" in x.name )]
- pred_images = [x for x in Path(image_folder).glob("*.tif") if ( "cp_masks" in x.name )]
- # Define save folder from the parent of the predicted_labels_folder
- results_path = image_folder / "QC-Results"
- # Make the directory if it's missing
- results_path.absolute().mkdir( exist_ok=True )
- with open(results_path / ( "Quality_Control for "+model_name+".csv" ), "w", newline='') as file:
- writer = csv.writer(file, delimiter=",")
- 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"])
- # define the images
- for raw, gt, pred in zip(raw_images, gt_images, pred_images):
- print( 'Running QC on: ' + raw.name )
- test_input = io.imread(raw)
- test_prediction = io.imread(pred)
- test_ground_truth_image = io.imread(gt)
- # Calculate the matching (with IoU threshold `thresh`) and all metrics
- stats = matching(test_ground_truth_image, test_prediction, thresh=0.5)
- #Convert pixel values to 0 or 255
- test_prediction_0_to_255 = test_prediction
- test_prediction_0_to_255[test_prediction_0_to_255>0] = 255
- #Convert pixel values to 0 or 255
- test_ground_truth_0_to_255 = test_ground_truth_image
- test_ground_truth_0_to_255[test_ground_truth_0_to_255>0] = 255
- # Intersection over Union metric
- intersection = np.logical_and(test_ground_truth_0_to_255, test_prediction_0_to_255)
- union = np.logical_or(test_ground_truth_0_to_255, test_prediction_0_to_255)
- iou_score = np.sum(intersection) / np.sum(union)
- 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)])
- df = pd.read_csv (results_path / ( "Quality_Control for "+model_name+".csv" ))
- display(tabulate(df, headers='keys', tablefmt="html"))
- # %% [markdown]
- # # Open Images and Compute QC Metrics
- # %%
- data_folder = "../Cellpose Training Folder/test"
- model_name = "cellpose_residual_on_style_on_concatenation_off_train_2021_10_28_11_33_45.741069"
- compareLabels(model_name, data_folder)
- showQCResults(model_name, data_folder)
- # %%
Quality Control-Cellpose.ipynb at commit 5d14816, under Apache-2.0 · at the source
Overview
- Max-Delbrück-Center for Molecular Medicine in the Helmholtz Association (MDC), Molecular Physiology of Somatic Sensation Laboratory, 13125 Berlin, Germany
- Helmholtz Centre for Infection Research (HZI), Pathways in Infection and Nociception (PAIN) Group, 38124 Braunschweig, Germany
- Max-Delbrück-Center for Molecular Medicine in the Helmholtz Association (MDC), Spatial Proteomics Group, 13125 Berlin, Germany
- Charité-Universitätsmedizin Berlin, Charitéplatz 1, 10117 Berlin, Germany
- German Center for Mental Health (DZPG), Partner Site Berlin, Berlin, Germany
- German Cancer Consortium (DKTK), Partner Site Berlin, Heidelberg, Germany
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
5d1481674b467bb4a590cc7d39b29f58fe40c287, 6 July 2026Availability: 1 check, the latest on 29 September 2026: the link answers
- 29 September 2026: the link answers
20 files
- QC/
Quality Control-Cellpose.ipynb , Jupyter, 573 lines - QC/
run-cellpose-qc.py , Python, 361 lines - docs/
member-search-index.js , JavaScript, 1 line - docs/
module-search-index.js , JavaScript, 1 line - docs/
package-search-index.js , JavaScript, 1 line - docs/
script-files/ , JavaScript, 2 linesjquery-3.7.1.min.js - docs/
script-files/ , JavaScript, 6 linesjquery-ui.min.js - docs/
script-files/ , JavaScript, 585 linesscript.js - docs/
script-files/ , JavaScript, 348 linessearch-page.js - docs/
script-files/ , JavaScript, 549 linessearch.js - docs/
tag-search-index.js , JavaScript, 1 line - docs/
type-search-index.js , JavaScript, 1 line - src/
main/ , Java, 1,701 linesjava/ qupath/ ext/ biop/ cellpose/ Cellpose2D.java - src/
main/ , Java, 1,073 linesjava/ qupath/ ext/ biop/ cellpose/ CellposeBuilder.java - src/
main/ , Java, 178 linesjava/ qupath/ ext/ biop/ cellpose/ CellposeExtension.java - src/
main/ , Java, 57 linesjava/ qupath/ ext/ biop/ cellpose/ CellposeSetup.java - src/
main/ , Java, 420 linesjava/ qupath/ ext/ biop/ cellpose/ OpCreators.java - src/
main/ , Java, 265 linesjava/ qupath/ ext/ biop/ cmd/ VirtualEnvironmentRunner .java - LICENSE, License, 201 lines
- README.md, Text, 366 lines
CosciaLab/Qupath_to_LMD
32df199eea7595a465a880ed99fd15e172c4e4a6, 27 August 2026Availability: 1 check, the latest on 29 September 2026: the link answers
- 29 September 2026: the link answers
32 files
- src/
qupath_to_lmd/ , Python, 1 line__init__.py - src/
qupath_to_lmd/ , Python, 161 linesbudget.py - src/
qupath_to_lmd/ , Python, 235 linesexport.py - src/
qupath_to_lmd/ , Python, 40 linesextras.py - src/
qupath_to_lmd/ , Python, 424 linesgeojson.py - src/
qupath_to_lmd/ , Python, 55 linesmock_streamlit.py - src/
qupath_to_lmd/ , Python, 215 linesmodel.py - src/
qupath_to_lmd/ , Python, 209 linesplate.py - src/
qupath_to_lmd/ , Python, 143 linesplot.py - src/
qupath_to_lmd/ , Python, 178 linesqc.py - src/
qupath_to_lmd/ , Python, 349 linesselection.py - src/
qupath_to_lmd/ , Python, 156 linesstats.py - src/
qupath_to_lmd/ , Python, 572 linesui_cells.py - src/
qupath_to_lmd/ , Python, 68 linesui_legacy.py - src/
qupath_to_lmd/ , Python, 868 linesui_shared.py - streamlit_app.py, Python, 96 lines
- tests/
__init__.py , Python, 6 lines - tests/
conftest.py , Python, 186 lines - tests/
test_budget.py , Python, 150 lines - tests/
test_export.py , Python, 202 lines - tests/
test_geojson.py , Python, 335 lines - tests/
test_golden.py , Python, 66 lines - tests/
test_model.py , Python, 142 lines - tests/
test_nomenclature.py , Python, 160 lines - tests/
test_plate.py , Python, 166 lines - tests/
test_qc.py , Python, 112 lines - tests/
test_selection.py , Python, 269 lines - tests/
test_stats.py , Python, 216 lines - tests/
test_ui_behaviour.py , Python, 351 lines - tools/
golden_harness.py , Python, 140 lines - LICENSE, License, 674 lines
- README.md, Text, 161 lines
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://
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://
BibTeX
@article{chakrabarti2026
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/
url = {https://
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/
VL - 17
IS - 1
SP - 3437
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"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":
"volume": "17",
"issue": "1",
"page": "3437",
"DOI": "10.1038/
"PMID": "41965357",
"PMCID": "PMC13076600",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://
"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 biologyIn 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. MedicineIn 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 controlJournal: eLifeIn 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: eLifeIn 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 reportsIn 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 communicationsIn 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: eLifeIn 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 communicationsIn 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: NatureIn 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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 48 scripts, and 0 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:780a9e1c9a20bb21…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
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.
