An enteric neuron ionotropic receptor regulates salt stress resistance.
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
Python · 944 lines · 40 KB · MIT
- #!/usr/bin/env python3
- #######################################################################
- # Kymograph Generation for C. elegans Pharyngeal Pumping Analysis
- #
- # This script processes video recordings of C. elegans to generate kymographs
- # that visualize pharyngeal pumping behavior over time. The analysis pipeline:
- #
- # 1. Loads behavior recordings from HDF5 files
- # 2. Segments worms from background using image processing
- # 3. Extracts skeleton centerlines using Tierpsy tracker
- # 4. Creates kymographs showing intensity changes along the body
- # 5. Generates quality control videos with skeleton overlays
- #
- # Key outputs:
- # - Kymograph images showing pumping patterns
- # - Processed videos for quality control
- # - Numerical data for further analysis
- #######################################################################
- # Modules
- import os # File and folder path handling
- import gc # Garbage collection for memory management
- import datetime # Timestamp generation
- import numpy as np # Numerical computing and array operations
- import matplotlib.pyplot as plt # Plotting and image generation
- from scipy.io import savemat # MATLAB file export
- # Tierpsy Tracker - C. elegans computer vision library
- # https://github.com/SinRas/tierpsy-tracker
- import tierpsy
- from tierpsy.analysis.ske_create.getSkeletonsTables import getWormMask, getSkeleton
- # Custom utilities for data processing
- from utils import *
- # Import configuration parameters
- from config import *
- #######################################################################
- # Computer Vision Functions
- #
- # These functions handle the image processing pipeline for worm detection
- # and skeleton extraction. They work together to convert raw video frames
- # into structured data suitable for behavioral analysis.
- #######################################################################
- def create_foreground_mask(img_beh, threshold_foreground=130, threshold_area_min_object=100, threshold_area_max_object=500_000):
- """
- Create a binary mask identifying worm pixels in the image.
- This function segments the worm from the background by:
- 1. Applying intensity thresholding to find dark objects
- 2. Filtering by object size to remove debris and artifacts
- Args:
- img_beh: Input grayscale image (numpy array)
- threshold_foreground: Intensity threshold for worm detection (0-255)
- threshold_area_min_object: Minimum object size in pixels
- threshold_area_max_object: Maximum object size in pixels
- Returns:
- Binary mask where True indicates worm pixels
- """
- # Apply multi-scale thresholding to handle varying image quality
- img_beh_masked = (cv.medianBlur(img_beh, 11) < threshold_foreground) | \
- (cv.medianBlur(img_beh, 3) < threshold_foreground) | \
- (img_beh < threshold_foreground)
- # Remove objects that are too small or too large (debris, artifacts)
- n_labels, labels, stats, centroids = cv.connectedComponentsWithStats(img_beh_masked.astype(np.uint8))
- img_beh_masked &= False # Reset mask
- # Keep only objects within size constraints
- for label, area in enumerate(stats[:, -1]):
- if label == 0 or area < threshold_area_min_object or area > threshold_area_max_object:
- continue
- img_beh_masked |= labels == label
- # Return
- return img_beh_masked
- def extract_worm_mask(img_mask, coords_center=None, size_close=11):
- """
- Extract the worm mask by selecting the largest connected component and filling gaps.
- This function refines the foreground mask by:
- 1. Finding the object closest to the expected worm position
- 2. Filling gaps in the worm outline using morphological operations
- 3. Ensuring the mask represents a single, connected worm
- Args:
- img_mask: Binary foreground mask from create_foreground_mask()
- coords_center: Expected worm position (None = use image center)
- size_close: Size of morphological closing kernel
- Returns:
- Refined binary mask representing the worm
- """
- if coords_center is None:
- coords_center = np.array(img_mask.shape, dtype=np.float32) / 2
- # Find all connected components in the mask
- n_labels, labels, stats, centroids = cv.connectedComponentsWithStats(img_mask.astype(np.uint8))
- # Handle case where no objects are found
- if n_labels <= 1:
- return np.zeros_like(img_mask, dtype=np.bool_)
- # Select the object closest to the expected center position
- dists = np.linalg.norm(centroids - coords_center[np.newaxis, :], axis=1)
- label_closest_center = 1 + np.argmin(dists[1:])
- worm_mask = labels == label_closest_center
- # Fill gaps in the worm outline using morphological closing
- worm_mask = cv.morphologyEx(
- worm_mask.astype(np.uint8),
- cv.MORPH_CLOSE,
- cv.getStructuringElement(cv.MORPH_ELLIPSE, (size_close, size_close))
- ) > 0
- # Fill interior regions by inverting and finding background
- n_labels, labels, stats, centroids = cv.connectedComponentsWithStats((~worm_mask).astype(np.uint8))
- label_all_background = 1 + np.argmax(stats[1:, -1])
- worm_mask = labels != label_all_background
- # Return
- return worm_mask
- def convert_mask_for_tierpsy(worm_mask):
- """
- Convert binary mask to format expected by Tierpsy skeleton extraction.
- Tierpsy expects specific intensity values:
- - Background: 255 (white)
- - Threshold: 110 (splits background from worm)
- - Worm: 55 (dark gray, or any value below 110)
- Args:
- worm_mask: Binary mask where True = worm pixels
- Returns:
- Grayscale image with proper intensity values for Tierpsy
- """
- img_tierpsy = (worm_mask == 0).astype(np.uint8) * 200 # Background = 200
- img_tierpsy += 55 # Worm = 55, Background = 255
- return img_tierpsy
- def extract_skeleton(worm_mask, skeleton_prev=np.zeros(0), n_skeleton_segments=101):
- """
- Extract worm skeleton using Tierpsy computer vision library.
- This function uses Tierpsy's algorithms to find the centerline of the worm,
- which is essential for creating kymographs.
- Args:
- worm_mask: Binary mask representing the worm
- skeleton_prev: Previous skeleton for temporal consistency (optional)
- n_skeleton_segments: Number of points along the skeleton
- Returns:
- Array of skeleton points (x, y coordinates)
- """
- # Convert mask to Tierpsy format
- worm_tierpsy = convert_mask_for_tierpsy(worm_mask)
- # Extract worm contour using Tierpsy
- _, worm_cnt, _ = getWormMask(
- worm_img=worm_tierpsy,
- threshold=110, # Don't change it, this is paired with `img_mask_to_tierpsy`
- strel_size=11, # Hard coded in Tierpsy
- min_blob_area=TIERPSY_MIN_BLOB_AREA, # Change if you need to find skeleton for a very tiny worm, which should not be the case!
- is_light_background=TIERPSY_IS_LIGHT_BACKGROUND
- )
- # Extract skeleton from contour
- output = getSkeleton(
- worm_cnt=worm_cnt,
- prev_skeleton=skeleton_prev,
- resampling_N=n_skeleton_segments
- )
- skeleton, ske_len, cnt_side1, cnt_side2, cnt_widths, cnt_area = output
- # Return
- return skeleton
- #######################################################################
- # Time Series Processing Functions
- #
- # These functions handle temporal smoothing of skeleton data to reduce
- # noise and improve the quality of kymograph generation. They implement
- # different types of moving averages for different analysis needs.
- #######################################################################
- def moving_average_causal(arr, k, dtype=np.float32):
- """
- Apply causal (past-only) moving average to time series data.
- This function smooths data using only past values, making it suitable
- for real-time analysis where future data is not available.
- Args:
- arr: Input time series array
- k: Window size for averaging
- dtype: Output data type
- Returns:
- Smoothed time series array
- """
- if k <= 1:
- return arr.copy()
- if np.isnan(arr[0]) or np.isnan(arr[-1]):
- il = 0
- while il < len(arr) and np.isnan(arr[il]):
- il += 1
- ir = len(arr)-1
- while ir >= 0 and np.isnan(arr[ir]):
- ir -= 1
- ir += 1
- assert il < ir, "Please don't test me on all edge cases :)) give me a better array! :grin: (array is all NaNs)"
- result = np.zeros_like(arr) * np.nan
- result[il:ir] = moving_average_causal( arr[il:ir], k )
- return result
- # Rolling counts
- w = k+1
- arr_cumsum = np.nancumsum( arr, axis=0 )
- ns_cumsum = np.cumsum( ~np.isnan(arr), axis=0 )
- result = np.zeros_like(arr_cumsum, dtype=dtype) * np.nan
- # Beginning
- result[:w] = arr_cumsum[:w] / ns_cumsum[:w]
- # Rest
- result[w:] = (arr_cumsum[w:] - arr_cumsum[:-w]) / np.maximum(ns_cumsum[w:] - ns_cumsum[:-w], 1)
- # NaNs
- _indices = w + np.where( ns_cumsum[w:] == ns_cumsum[:-w] )[0]
- result[_indices] = np.nan
- # Return
- return result
- def moving_average_symmetric(arr, k, dtype=np.float32):
- """
- Apply symmetric (past and future) moving average to time series data.
- This function smooths data using both past and future values, providing
- better smoothing but requiring the entire time series to be available.
- Args:
- arr: Input time series array
- k: Window size for averaging
- dtype: Output data type
- Returns:
- Smoothed time series array
- """
- if k <= 1:
- return arr.astype(dtype)
- return (moving_average_causal(arr, k, dtype=dtype) * (k+1) +
- moving_average_causal(arr[::-1], k-1, dtype=dtype)[::-1] * k) / (2*k+1)
- #######################################################################
- # Skeleton Processing and Kymograph Generation
- #
- # These functions handle the geometric processing of skeleton data and
- # the creation of kymographs that visualize intensity changes along the
- # worm's body over time.
- #######################################################################
- from scipy.interpolate import make_interp_spline
- def smooth_skeleton(arr2d, stride=5, k=3):
- """
- Smooth skeleton coordinates using cubic spline interpolation.
- This function reduces noise in skeleton tracking by fitting smooth
- curves through the skeleton points.
- Args:
- arr2d: 2D array of skeleton coordinates (time, points, x/y)
- stride: Sampling interval for spline fitting
- k: Spline order (3 = cubic)
- Returns:
- Smoothed skeleton coordinates
- """
- ts = np.arange(len(arr2d))
- spl = make_interp_spline(ts[::stride], arr2d[::stride], k=k)
- return spl(ts)
- def interpolate_skeleton(coords, dists_to_interpolate, k=3):
- """
- Interpolate skeleton points at specific distances from the head.
- This function creates a standardized skeleton representation by
- sampling points at regular intervals along the worm's length.
- Args:
- coords: Skeleton coordinates (points, x/y)
- dists_to_interpolate: Target distances from head (in pixels)
- k: Spline order for interpolation
- Returns:
- Interpolated skeleton coordinates at specified distances
- """
- # Calculate cumulative distances along skeleton
- dists = np.zeros(len(coords))
- dists[1:] = np.cumsum(np.linalg.norm(np.diff(coords, axis=0), axis=1))
- # Interpolate at target distances
- spl = make_interp_spline(dists, coords, k=k)
- return spl(dists_to_interpolate)
- def compute_skeleton_normals(skeleton, normal_factors=np.arange(-5.0, 6.0, 1.0)):
- """
- Create perpendicular lines (normals) extending from skeleton points.
- These normals are used to measure intensity profiles
- across the body for kymograph generation.
- Args:
- skeleton: Skeleton coordinates (points, x/y)
- normal_factors: Distances along normals (negative = left, positive = right)
- Returns:
- 3D array of normal line coordinates (midpoints, x/y, normal_points)
- """
- # Calculate midpoints between consecutive skeleton points
- skeleton_mids = (skeleton[:-1] + skeleton[1:]) / 2
- # Calculate normal vectors (perpendicular to skeleton direction)
- diffs = np.diff(skeleton, axis=0)
- dists = np.linalg.norm(diffs, axis=1)
- normals = diffs[:, ::-1] / dists[:, np.newaxis] # Rotate 90 degrees
- normals[:, 0] *= -1 # Flip x-component for correct orientation
- # Create normal lines extending from midpoints
- skeleton_mids_normals = skeleton_mids[:, :, np.newaxis] + \
- (normals[:, :, np.newaxis] * normal_factors[np.newaxis, np.newaxis, :])
- return skeleton_mids_normals
- #######################################################################
- # Main Kymograph Processing Functions
- #
- # These functions process a segment of video data to generate kymographs
- # and quality control videos. They handle the complete pipeline from
- # raw video frames to kymograph images.
- #######################################################################
- def load_and_align_kymograph(fp_npz, scale_roll=1.0, il=130, ir=200):
- """
- Load kymograph data and align it to compensate for worm movement.
- This function addresses the challenge that worms can move during recording,
- causing the pharynx to shift position in the kymograph. Alignment is done
- by rolling the kymograph along the normal lines.
- Args:
- fp_npz: Path to NPZ file containing kymograph data
- scale_roll: Scaling factor for alignment correction
- il: Left boundary of analysis region (pixels from head)
- ir: Right boundary of analysis region (pixels from head)
- Returns:
- Tuple of (original_kymograph, aligned_kymograph)
- """
- # Load
- with np.load(fp_npz) as in_file:
- data = {
- key: in_file[key] for key in in_file.keys()
- }
- # Load or average
- intensities_skeleton_normals_avg = np.nanmean(
- data['intensities_skeleton_normals_full'][:, :, 15:46],
- axis=-1
- ) if 'intensities_skeleton_normals_full' in data else data['kymograph_skeleton_normals'].copy()
- # Find the gut boundary shifts
- weights = intensities_skeleton_normals_avg[:,il:ir]
- center_of_mass = np.nansum(
- weights * np.arange(ir-il)[np.newaxis,:], axis=1
- ) / np.nansum( weights, axis=1 )
- dindex_center_of_mass = np.round(
- (center_of_mass - np.nanmedian(center_of_mass))*scale_roll,
- 0
- ).astype(np.int64)
- # Rolled
- intensities_skeleton_normals_avg_rolled = intensities_skeleton_normals_avg.copy()
- for idx, k in enumerate(dindex_center_of_mass):
- if np.isnan(k) or np.isinf(k) or k == 0:
- continue
- intensities_skeleton_normals_avg_rolled[idx] = np.roll(intensities_skeleton_normals_avg_rolled[idx], k)
- # Return
- return intensities_skeleton_normals_avg, intensities_skeleton_normals_avg_rolled
- def process_kymograph_segment(idx_start, idx_end, indices_skeleton_reverse=set(), worm_id=None, condition=None, strain=None):
- """
- Process a video segment to generate kymographs and quality control videos.
- This function performs the complete analysis pipeline:
- 1. Creates foreground mask videos
- 2. Extracts worm masks and skeleton overlays
- 3. Generates kymographs showing intensity changes over time
- 4. Saves all data in multiple formats (NPZ, MAT, PNG)
- Args:
- idx_start: Starting frame index for processing
- idx_end: Ending frame index for processing
- indices_skeleton_reverse: Set of frame indices where skeleton orientation should be reversed
- """
- # Recording and writing parameters
- INDEX_START = idx_start
- INDICES_TO_CONSIDER = np.arange(idx_start, idx_end, 1)
- NT = len(INDICES_TO_CONSIDER)
- _PREFIX = f"{worm_id}_{condition}_{strain}_pumping_{str(idx_start).zfill(8)}_{str(idx_end).zfill(8)}_"
- FP_WRITE_VIDEO_FOREGROUND = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}movie_foreground.mp4" ) # Path to the video file to write masked foreground
- FP_WRITE_VIDEO_WORMMASK = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}movie_worm_mask.mp4" ) # Path to the video file to write worm mask video
- FP_WRITE_VIDEO_WORMSKELETON = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}movie_worm_skeleton.mp4" ) # Path to the video file to write worm mask video
- FP_WRITE_IMAGE_KYMOGRAPH = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}kymograph_only_center.png" ) # Path to the video file to write worm mask video
- FP_WRITE_IMAGE_KYMOGRAPH_SCALED = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}kymograph_only_center_scaled.png" ) # Path to the video file to write worm mask video
- FP_WRITE_IMAGE_KYMOGRAPH_NORMALS = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}kymograph_normals.png" ) # Path to the video file to write worm mask video
- FP_WRITE_IMAGE_KYMOGRAPH_NORMALS_SCALED = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}kymograph_normals_scaled.png" ) # Path to the video file to write worm mask video
- FP_WRITE_NPZ_KYMOGRAPH_ALL = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}kymograph_all.npz" ) # Path to all the data for the kymographs, e.g. timestamps and distances in `um`
- FP_WRITE_MAT_KYMOGRAPH_ALL = os.path.join( FP_PUMPING_EXTRACTS, f"{_PREFIX}kymograph_all.mat" ) # Path to all the data for the kymographs, e.g. timestamps and distances in `um`
- # 1) Make foreground mask video
- # Add frame index and actual FPS
- # series_beh = SerializeDatas([imgs.copy()])
- ## Mask images (lazy calculation -> nothing is calculated until writing it to file)
- def do_mask(img):
- img_mask = create_foreground_mask(img, threshold_foreground=MASK_THRESHOLD_FOREGROUND, threshold_area_min_object=MASK_AREA_MIN_OBJECT, threshold_area_max_object=MASK_AREA_MAX_OBJECT)
- return img_mask.astype(np.uint8)
- series_to_write = ImgToProcess(series_beh, fn_process=do_mask, rescale=True)
- ## White color
- series_to_write = AddFrameIndicesTexts(
- series_to_write,
- texts=texts,
- textOrigin=TEXT_ORIGIN_WHITE, color=TEXT_COLOR_WHITE
- )
- ## Black color -> for very bright images
- series_to_write = AddFrameIndicesTexts(
- series_to_write,
- texts=texts,
- textOrigin=TEXT_ORIGIN_BLACK, color=TEXT_COLOR_BLACK
- )
- ## Slice specific range
- series_to_write = ReIndexData(
- series_to_write,
- indices_new=INDICES_TO_CONSIDER
- )
- write_video(
- series_to_write,
- fp=FP_WRITE_VIDEO_FOREGROUND,
- fps=FRAMES_PER_SECOND,
- verbose=True
- )
- # 2) Find worm mask
- # Add frame index and actual FPS
- # series_beh = SerializeDatas([imgs.copy()])
- ## Find worm mask
- def do_mask(img):
- img_mask = create_foreground_mask(img, threshold_foreground=MASK_THRESHOLD_FOREGROUND, threshold_area_min_object=MASK_AREA_MIN_OBJECT, threshold_area_max_object=MASK_AREA_MAX_OBJECT)
- worm_mask = extract_worm_mask(img_mask, coords_center=WORM_CENTER_ESTIMATE, size_close=WORM_SIZE_CLOSE)
- return worm_mask.astype(np.uint8)
- series_to_write = ImgToProcess(series_beh, fn_process=do_mask, rescale=True)
- ## White color
- series_to_write = AddFrameIndicesTexts(
- series_to_write,
- texts=texts,
- textOrigin=TEXT_ORIGIN_WHITE, color=TEXT_COLOR_WHITE
- )
- ## Black color -> for very bright images
- series_to_write = AddFrameIndicesTexts(
- series_to_write,
- texts=texts,
- textOrigin=TEXT_ORIGIN_BLACK, color=TEXT_COLOR_BLACK
- )
- ## Slice specific range
- series_to_write = ReIndexData(
- series_to_write,
- indices_new=INDICES_TO_CONSIDER
- )
- write_video(
- series_to_write,
- fp=FP_WRITE_VIDEO_WORMMASK,
- fps=FRAMES_PER_SECOND,
- verbose=True
- )
- # 3) Find skeleton and video from it
- # Add frame index and actual FPS
- # series_beh = SerializeDatas([imgs.copy()])
- ## Find worm mask
- def do_mask(img, idx_frame):
- # These non-local variables are used: idx_start, infos
- # Get skeleton
- idx_skeleton = idx_frame - idx_start if idx_frame >= idx_start else 0 # Technical due to definition of class `ImgToProcessIndexed`
- skeleton = infos['skeleton_smooth'][idx_skeleton]
- # Overlay image
- img_overlayed = cv.polylines( img.astype(np.uint8), [ skeleton.astype(np.int32)[:,np.newaxis,:] ], False, SKELETON_LINE_COLOR, SKELETON_LINE_WIDTH ) # Aesthetics: width and color of the skeleton
- # Head part
- img_overlayed = cv.polylines( img_overlayed, [ skeleton.astype(np.int32)[:SKELETON_HEAD_POINTS,np.newaxis,:] ], False, SKELETON_HEAD_LINE_COLOR, SKELETON_HEAD_LINE_WIDTH ) # Aesthetics: width and color of the skeleton
- return img_overlayed
- # Find skeleton
- infos = {
- 'indices': INDICES_TO_CONSIDER,
- 'skeleton': np.zeros((NT, SKELETON_N_SEGMENTS, 2), dtype=np.float64)*np.nan,
- }
- for idx, idx_frame in enumerate(tqdm(INDICES_TO_CONSIDER)):
- # Load
- img = series_beh[idx_frame]
- # Mask
- img_mask = create_foreground_mask(img, threshold_foreground=MASK_THRESHOLD_FOREGROUND, threshold_area_min_object=MASK_AREA_MIN_OBJECT, threshold_area_max_object=MASK_AREA_MAX_OBJECT)
- worm_mask = extract_worm_mask(img_mask, coords_center=WORM_CENTER_ESTIMATE, size_close=WORM_SIZE_CLOSE)
- # Skeleton
- skeleton_prev = np.zeros(0) if idx == 0 or np.any(np.isnan(infos['skeleton'][idx-1])) else infos['skeleton'][idx-1] # Use last skeleton if it was found, to re-orient and keep orientation consistency
- skeleton = extract_skeleton(
- worm_mask,
- skeleton_prev=skeleton_prev,
- n_skeleton_segments=SKELETON_N_SEGMENTS
- )
- ## Reverse initial skeleton
- if idx_frame in indices_skeleton_reverse:
- print(f"*** skeleton reversed at {idx_frame}")
- skeleton = skeleton[::-1]
- if len(skeleton) == 0:
- continue
- # Intensities
- infos['skeleton'][idx] = skeleton
- # Smooth Skeletons temporaly
- infos['skeleton_smooth'] = infos['skeleton'].copy()
- infos['skeleton_smooth'][1:] += infos['skeleton'][:-1]
- infos['skeleton_smooth'][:-1] += infos['skeleton'][1:]
- infos['skeleton_smooth'][:1] /= 2
- infos['skeleton_smooth'][1:-1] /= 3
- infos['skeleton_smooth'][-1:] /= 2
- # Write video
- series_to_write = ImgToProcessIndexed(series_beh, fn_process=do_mask, rescale=True)
- ## White color
- series_to_write = AddFrameIndicesTexts(
- series_to_write,
- texts=texts,
- textOrigin=TEXT_ORIGIN_WHITE, color=TEXT_COLOR_WHITE
- )
- ## Black color -> for very bright images
- series_to_write = AddFrameIndicesTexts(
- series_to_write,
- texts=texts,
- textOrigin=TEXT_ORIGIN_BLACK, color=TEXT_COLOR_BLACK
- )
- ## Slice specific range
- series_to_write = ReIndexData(
- series_to_write,
- indices_new=INDICES_TO_CONSIDER
- )
- write_video(
- series_to_write,
- fp=FP_WRITE_VIDEO_WORMSKELETON,
- fps=FRAMES_PER_SECOND,
- verbose=True
- )
- # 4) Create the kymograph
- # Interpolated
- dists_to_interpolate_px = SKELETON_INTENSITY_STEPS_UM / UM_PER_PIXEL
- _n = len(dists_to_interpolate_px)
- infos['skeleton_smooth_interp'] = infos['skeleton_smooth'].copy()
- infos['skeleton_smooth_interp_um'] = np.zeros((NT, _n, 2))*np.nan
- infos['skeleton_smooth_interp_um_normals'] = np.zeros((NT, _n, 2))*np.nan
- infos['intensities_skeleton'] = np.zeros((NT, _n))*np.nan
- infos['intensities_skeleton_normals'] = np.zeros((NT, _n-1))*np.nan
- infos['intensities_skeleton_normals_full'] = np.zeros((NT, _n-1, N_SKELETON_NORMALS_HALF_LENGTH_PIXELS))*np.nan
- infos['widths_skeleton_normals'] = np.zeros((NT, _n-1))*np.nan
- # Load intensities along lines
- for idx, idx_frame in enumerate(tqdm(infos['indices'])):
- # Skeleton Smoothed
- skeleton = infos['skeleton_smooth'][idx]
- if np.any(np.isnan(skeleton)):
- continue
- # Smooth spatially
- skeleton_interp = smooth_skeleton( skeleton, stride=5, k=3 )
- skeleton_interp_um = interpolate_skeleton(skeleton_interp, dists_to_interpolate_px, k=3 )
- skeleton_interp_um_normals = compute_skeleton_normals(
- skeleton_interp_um,
- normal_factors=np.arange(-SKELETON_NORMALS_HALF_LENGTH_PIXELS, SKELETON_NORMALS_HALF_LENGTH_PIXELS+1).astype(np.float32)
- )
- skeleton_interp_um_normals = np.minimum(
- np.maximum(
- skeleton_interp_um_normals, 0
- ),
- NX-1
- ).astype(np.int64)
- infos['skeleton_smooth_interp'][idx] = skeleton_interp
- infos['skeleton_smooth_interp_um'][idx] = skeleton_interp_um
- skeleton_int = np.minimum(
- np.maximum(
- skeleton_interp_um, 0
- ),
- NX-1
- ).astype(np.int64)
- # Load
- img = series_beh[idx_frame]
- # Mask
- img_mask = create_foreground_mask(img, threshold_foreground=MASK_THRESHOLD_FOREGROUND, threshold_area_min_object=MASK_AREA_MIN_OBJECT, threshold_area_max_object=MASK_AREA_MAX_OBJECT)
- worm_mask = extract_worm_mask(img_mask, coords_center=WORM_CENTER_ESTIMATE, size_close=WORM_SIZE_CLOSE)
- # NaNed
- img_naned = img.astype(np.float32)
- img_naned[~worm_mask] = np.nan
- # Intensities
- infos['intensities_skeleton'][idx] = img_naned[skeleton_int[:,1], skeleton_int[:,0]]
- infos['intensities_skeleton_normals'][idx] = np.nanmean(
- img_naned[skeleton_interp_um_normals[:,1], skeleton_interp_um_normals[:,0]],
- axis=-1
- )
- infos['widths_skeleton_normals'][idx] = np.sum(
- ~np.isnan(img_naned[skeleton_interp_um_normals[:,1], skeleton_interp_um_normals[:,0]]),
- axis=-1
- )
- infos['intensities_skeleton_normals_full'][idx] = img_naned[skeleton_interp_um_normals[:,1], skeleton_interp_um_normals[:,0]]
- # Store
- ## Numpy
- np.savez_compressed(
- FP_WRITE_NPZ_KYMOGRAPH_ALL,
- timestamps = times_beh[INDICES_TO_CONSIDER],
- kymograph_skeleton = infos['intensities_skeleton'],
- kymograph_skeleton_normals = infos['intensities_skeleton_normals'],
- intensities_skeleton_normals_full = infos['intensities_skeleton_normals_full'],
- distance_from_nose = SKELETON_INTENSITY_STEPS_UM,
- normal_scales_pixel = np.arange(-SKELETON_NORMALS_HALF_LENGTH_PIXELS, SKELETON_NORMALS_HALF_LENGTH_PIXELS+1).astype(np.float32),
- )
- ## Matlab
- savemat(
- FP_WRITE_MAT_KYMOGRAPH_ALL,
- dict(
- timestamps = times_beh[INDICES_TO_CONSIDER],
- kymograph_skeleton = infos['intensities_skeleton'],
- kymograph_skeleton_normals = infos['intensities_skeleton_normals'],
- distance_from_nose = SKELETON_INTENSITY_STEPS_UM,
- normal_scales_pixel = np.arange(-SKELETON_NORMALS_HALF_LENGTH_PIXELS, SKELETON_NORMALS_HALF_LENGTH_PIXELS+1).astype(np.float32),
- )
- )
- #
- _ = gc.collect()
- def plotting_function(kymograph, fp_write, title = None):
- plt.ioff()
- plt.figure(figsize=FIGURE_SIZE_LARGE, dpi=FIGURE_DPI)
- plt.title(title)
- plt.imshow( kymograph, cmap='gray')
- # X ticks
- _ticks = np.arange(NT)
- _labels = (1000*(times_beh[INDICES_TO_CONSIDER] - times_beh[INDICES_TO_CONSIDER[0]]).round(5) ).astype(np.int64)
- plt.xticks(ticks=_ticks[::TICK_INTERVAL_TIME], labels=_labels[::TICK_INTERVAL_TIME], rotation=90)
- plt.xlabel("Milliseconds")
- # Y ticks
- _n = kymograph.shape[0]
- _ticks = np.arange(len(dists_to_interpolate_px))[:_n]
- _labels = ( dists_to_interpolate_px * UM_PER_PIXEL ).round(1)[-_n:]
- plt.yticks(ticks=_ticks[::TICK_INTERVAL_DISTANCE], labels=_labels[::TICK_INTERVAL_DISTANCE])
- plt.ylabel("Distance from nose (um)")
- plt.savefig(fp_write, bbox_inches='tight')
- plt.clf()
- plt.cla()
- plt.close('all')
- _ = gc.collect()
- return
- ############################################
- plotting_function(
- kymograph = infos['intensities_skeleton'].T[:,:],
- fp_write = FP_WRITE_IMAGE_KYMOGRAPH,
- title = "Kymograph of intensity along the body"
- )
- plotting_function(
- kymograph = infos['intensities_skeleton_normals'].T[:,:],
- fp_write = FP_WRITE_IMAGE_KYMOGRAPH_NORMALS,
- title = "Kymograph of intensity along normals along the body"
- )
- ############################################
- # Normalize the kymograph?!
- _tmp = infos['intensities_skeleton'].T.copy()
- _tmp = _tmp[:, :]
- _tmp = np.clip(
- 128*(_tmp / np.nanmean(_tmp, axis=0, keepdims=True))**1.5,
- 0, 255
- )
- plt.ioff()
- plt.figure(figsize=FIGURE_SIZE_XLARGE, dpi=FIGURE_DPI)
- plt.title("Kymograph of intensity along the body - Rescaled")
- plt.imshow( _tmp, cmap='gray')
- # X ticks
- _ticks = np.arange(NT)
- _labels = (1000*(times_beh[INDICES_TO_CONSIDER] - times_beh[INDICES_TO_CONSIDER[0]]).round(5) ).astype(np.int64)
- plt.xticks(ticks=_ticks[::TICK_INTERVAL_TIME], labels=INDICES_TO_CONSIDER[::TICK_INTERVAL_TIME], rotation=90)
- plt.xlabel("Milliseconds")
- # Y ticks
- _ticks = np.arange(len(dists_to_interpolate_px))[:100]
- _labels = ( dists_to_interpolate_px * UM_PER_PIXEL ).round(1)[:100]
- plt.yticks(ticks=_ticks[::TICK_INTERVAL_DISTANCE], labels=_labels[::TICK_INTERVAL_DISTANCE])
- plt.ylabel("Distance from nose (um)")
- plt.savefig(FP_WRITE_IMAGE_KYMOGRAPH_SCALED, bbox_inches='tight')
- plt.clf()
- plt.cla()
- plt.close('all')
- ############################################
- # Normalize the kymograph?!
- _tmp = infos['intensities_skeleton_normals'].T.copy()
- _tmp = _tmp[:, :]
- _tmp = np.clip(
- 128*(_tmp / np.nanmean(_tmp, axis=0, keepdims=True))**1.5,
- 0, 255
- )
- plt.ioff()
- plt.figure(figsize=FIGURE_SIZE_XLARGE, dpi=FIGURE_DPI)
- plt.title("Kymograph of intensity along the body - Rescaled")
- plt.imshow( _tmp, cmap='gray')
- # X ticks
- _ticks = np.arange(NT)
- _labels = (1000*(times_beh[INDICES_TO_CONSIDER] - times_beh[INDICES_TO_CONSIDER[0]]).round(5) ).astype(np.int64)
- plt.xticks(ticks=_ticks[::TICK_INTERVAL_TIME], labels=INDICES_TO_CONSIDER[::TICK_INTERVAL_TIME], rotation=90)
- plt.xlabel("Milliseconds")
- # Y ticks
- _ticks = np.arange(len(dists_to_interpolate_px))[:100]
- _labels = ( dists_to_interpolate_px * UM_PER_PIXEL ).round(1)[:100]
- plt.yticks(ticks=_ticks[::TICK_INTERVAL_DISTANCE], labels=_labels[::TICK_INTERVAL_DISTANCE])
- plt.ylabel("Distance from nose (um)")
- plt.savefig(FP_WRITE_IMAGE_KYMOGRAPH_NORMALS_SCALED, bbox_inches='tight')
- plt.clf()
- plt.cla()
- plt.close('all')
- _ = gc.collect()
- #######################################################################
- # Main Function
- #
- # This function is the entry point for the script. It loads the files,
- # processes the kymographs, and saves the results.
- #######################################################################
- def download_sample_data(data_dir):
- """
- Download and extract the sample data from the GitHub release.
- The ZIP file is downloaded from the Yeon-2025-pumping-analysis GitHub
- release page and extracted into the data directory.
- Compatible with Windows, macOS, and Linux. Uses only Python standard
- library modules (plus tqdm for progress display).
- Args:
- data_dir: Path to the data directory where files will be extracted
- """
- import urllib.request
- import zipfile
- import tempfile
- DATA_URL = "https://github.com/venkatachalamlab/Yeon-2025-pumping-analysis/releases/download/v1.0/data.zip"
- CHUNK_SIZE = 8192 # 8 KB chunks for streaming download
- print(f"Sample data not found at: {data_dir}")
- print(f"Downloading sample data from GitHub release...")
- print(f" URL: {DATA_URL}")
- # Use a temporary file for the download (cross-platform safe)
- tmp_fd, zip_path = tempfile.mkstemp(suffix=".zip")
- os.close(tmp_fd) # Close the file descriptor; we'll open it ourselves
- try:
- # Download with progress bar
- response = urllib.request.urlopen(DATA_URL)
- total_size = int(response.headers.get("Content-Length", 0))
- with open(zip_path, "wb") as f:
- with tqdm(total=total_size, unit="B", unit_scale=True,
- unit_divisor=1024, desc="Downloading") as pbar:
- while True:
- chunk = response.read(CHUNK_SIZE)
- if not chunk:
- break
- f.write(chunk)
- pbar.update(len(chunk))
- print(f" Download complete.")
- except Exception as e:
- print(f"ERROR: Failed to download sample data: {e}")
- print(f"Please download manually from:")
- print(f" {DATA_URL}")
- print(f"Then extract into: {data_dir}")
- if os.path.exists(zip_path):
- os.remove(zip_path)
- exit(1)
- # Extract with progress bar
- print(f"Extracting to: {data_dir}")
- os.makedirs(data_dir, exist_ok=True)
- try:
- with zipfile.ZipFile(zip_path, "r") as zip_ref:
- members = zip_ref.infolist()
- with tqdm(total=len(members), unit="file", desc="Extracting") as pbar:
- for member in members:
- zip_ref.extract(member, data_dir)
- pbar.update(1)
- print(f" Extraction complete.")
- except Exception as e:
- print(f"ERROR: Failed to extract sample data: {e}")
- exit(1)
- finally:
- # Clean up the temporary ZIP file
- if os.path.exists(zip_path):
- os.remove(zip_path)
- print(f"Sample data is ready at: {data_dir}")
- if __name__ == "__main__":
- # Check if any behavior recording folders exist in the data directory.
- # Note: config.py creates data/ and its subdirectories on import, so
- # we check for actual *_behavior folders with .h5 files instead.
- _behavior_folders = glob(os.path.join(FP_READ_FOLDER, "*_behavior"))
- if len(_behavior_folders) == 0:
- download_sample_data(FP_READ_FOLDER)
- print(f"Starting kymograph generation for data in: {FP_READ_FOLDER}")
- # Expected folder and files structure
- # FP_READ_FOLDER
- # |-> WORM1_behavior
- # | |-> 000.h5
- # | |-> 001.h5
- # |-> WORM2_behavior
- # | |-> 000.h5
- # ...
- # Run
- files_beh, _, times_beh, series_beh = load_files_data_times_persistent(
- fp_folder=os.path.join( FP_READ_FOLDER, "*_behavior" ),
- fp_folder_persistence=os.path.join( FP_READ_FOLDER, "metadata" )
- )
- _, NX, NY = series_beh.shape
- # Annotation texts
- _n = len(times_beh)
- texts = [ "" ]
- for idx in range(1,_n):
- _T = times_beh[idx]-times_beh[0]
- _fps = 1/(times_beh[idx]-times_beh[idx-1])
- texts.append(
- ", {:>6.2f}s [fps: {:>4.1f}]".format(_T, _fps)
- )
- print(f"Behavior Series Loaded: records={len(series_beh)}")
- # Figure out files frame counts
- ns_per_recording = dict()
- recording_states = list()
- for file, n in zip(files_beh, series_beh.ns):
- name_recording = file.filename.split(os.sep)[-2]
- worm_id, condition, strain = name_recording.split("_")[:3]
- if name_recording not in ns_per_recording:
- ns_per_recording[name_recording] = n
- recording_states.append((worm_id, condition, strain))
- else:
- ns_per_recording[name_recording] += n
- indices_per_recording = np.array([0] + [
- ns_per_recording[k] for k in sorted(ns_per_recording)
- ])
- indices_per_recording = np.cumsum(indices_per_recording)
- print(list(zip( indices_per_recording[:-1], indices_per_recording[1:] )))
- # Manual Head-Tail confusion fixes
- # Possible good candidate that might have pumping as well
- from collections import defaultdict
- ## Intervals to extrct pumping from
- # e.g. this is by default the whole recording
- indices_inbetween_pairs = list(zip( indices_per_recording[:-1], indices_per_recording[1:] ))
- print(indices_inbetween_pairs)
- ## Indices where skeleton head-tail is confused
- ## Make sure you only add indices once, and only do it once for each interval
- # e.g. the head-tail remains consistent until the worm bends or the skeleton is not extractable by tierpsy
- # after the skeleton is again calculable, it might get head and tail confused.
- indices_skeleton_reverse = defaultdict(set)
- indices_skeleton_reverse[(0, 400)] = {0,}
- indices_skeleton_reverse[(400, 800)] = {400,}
- # indices_skeleton_reverse[(0, len(times_beh))] = set() # Add frame indices from the annotated video where the confusion starts and it will be flipped for the following frames as well
- # E.g. indices_skeleton_reverse[(0, 5956)] = { 1772, 2567, 4428, }
- # This means that the skeleton is reversed at frames 1772, 2567, 4428.
- # MANUAL PART! -> you need to add the indices manually if needed.
- # Make videos, kymographs and store data for all
- for (idx_start, idx_end), (worm_id, condition, strain) in zip(indices_inbetween_pairs,recording_states):
- print(f"##### {datetime.datetime.now()} Interval processing: {str(idx_start).zfill(8)}_{str(idx_end).zfill(8)}")
- # Extract all
- key = (idx_start, idx_end)
- ## Create Paths
- fp_read_pumping_kymograph = os.path.join(
- FP_PUMPING_EXTRACTS,
- f"{worm_id}_{condition}_{strain}_pumping_{str(idx_start).zfill(8)}_{str(idx_end).zfill(8)}_kymograph_all.npz"
- )
- fp_write_pumping_kymograph_png = os.path.join(
- FP_PUMPING_EXTRACTS,
- f"{worm_id}_{condition}_{strain}_pumping_{str(idx_start).zfill(8)}_{str(idx_end).zfill(8)}_kymograph_normals_true.png"
- )
- # Skip if exists
- if os.path.exists(fp_write_pumping_kymograph_png):
- continue
- # Report
- print("####"*20)
- print(f"#### {datetime.datetime.now()} Procesing interval: {str(idx_start).zfill(8)}-{str(idx_end).zfill(8)}")
- print("####"*20)
- # Do the thing
- process_kymograph_segment(idx_start, idx_end, indices_skeleton_reverse=indices_skeleton_reverse[key], worm_id=worm_id, condition=condition, strain=strain )
- print("####"*20)
- print(f"#### {datetime.datetime.now()} Finished!")
- print("####"*20)
- # Load Kymograph convert and write
- with np.load(fp_read_pumping_kymograph) as file_kymograph:
- # Load & Convert
- food_entry_normals_kymograph_uint8 = file_kymograph['kymograph_skeleton_normals']
- food_entry_normals_kymograph_uint8[np.isnan(food_entry_normals_kymograph_uint8)] = 255.0
- food_entry_normals_kymograph_uint8 = np.clip(food_entry_normals_kymograph_uint8, 0.0, 255.0).astype(np.uint8)
- ## Resize
- nx, ny = food_entry_normals_kymograph_uint8.shape
- food_entry_normals_kymograph_uint8 = cv.resize( food_entry_normals_kymograph_uint8, (ny, nx*IMAGE_RESIZE_FACTOR) )
- # Write
- plt.imsave(
- fp_write_pumping_kymograph_png,
- food_entry_normals_kymograph_uint8.T, cmap='gray',
- vmin=np.floor(np.nanmin(food_entry_normals_kymograph_uint8)), vmax=np.ceil(np.nanmax(food_entry_normals_kymograph_uint8))
- )
- # Load all NPZ files and create rolled kymographs
- fps_cases = sorted(glob(
- os.path.join(FP_PUMPING_EXTRACTS, "*.npz")
- ))
- print(f"Found {len(fps_cases)} cases")
- if len(fps_cases) == 0:
- raise ValueError(f"No NPZ files found in {FP_PUMPING_EXTRACTS}")
- # Generate aligned kymograph images for manual annotation
- for fp_npz in tqdm(fps_cases):
- # File path
- filename = fp_npz.split(os.sep)[-1]
- fp_write_png = os.path.join(
- FP_PUMPING_ANALYSIS,
- f"{filename[:-4]}.png"
- )
- # Skip if exists
- if os.path.exists(fp_write_png):
- continue
- # Load
- intensities_skeleton_normals_avg, intensities_skeleton_normals_avg_rolled = load_and_align_kymograph(fp_npz)
- # Store
- plt.imsave(
- fp_write_png,
- intensities_skeleton_normals_avg_rolled.T,
- cmap='gray',
- vmin=np.nanquantile( intensities_skeleton_normals_avg_rolled, 0.01 ),
- vmax=np.nanquantile( intensities_skeleton_normals_avg_rolled, 0.99 )
- )
01_make_kymographs.py at commit 2157177, under MIT · at the source
Overview
- Department of Biology, Brandeis University, Waltham, MA USA
- Department of Physics, Northeastern University, Boston, MA USA
- Department of Applied Biological Chemistry, Graduate School of Agricultural and Life Sciences, The University of Tokyo, Tokyo, Japan
- Present Address: Department of Biology, State University of New York, Albany, NY USA
Abstract
The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.
Repositories
Its files are read in the Code ↔ Paper reader above.
venkatachalamlab/Yeon-2025-pumping-analysis
21571776c1725b6b5da0816ac40a3da81de16fd0, 10 February 2026Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
208 files
- 01_make_kymographs.py, Python, 944 lines
- 02_load_annotated_kymogr
aphs_and_export.py , Python, 281 lines - config.py, Python, 160 lines
- tierpsy-tracker/
_old/ , Shell, 12 linescreate_binaries/ _test/ test.sh - tierpsy-tracker/
_old/ , Python, 48 linescreate_binaries/ _test/ test_pyinstaller.py - tierpsy-tracker/
_old/ , Python, 31 linescreate_binaries/ _test/ test_pyinstaller_reader. py - tierpsy-tracker/
_old/ , Shell, 38 linescreate_binaries/ create_binaries.sh - tierpsy-tracker/
_old/ , Shell, 88 linesinstallation/ installation_script.sh - tierpsy-tracker/
_old/ , Python, 53 linesinstallation/ installation_test.py - tierpsy-tracker/
_old/ , Python, 5 linesscripts/ tierpsy_gui.py - tierpsy-tracker/
_old/ , Python, 6 linesscripts/ tierpsy_gui_simple.py - tierpsy-tracker/
_old/ , Python, 10 linesscripts/ tierpsy_process.py - tierpsy-tracker/
_old/ , Python, 10 linesscripts/ tierpsy_tests.py - tierpsy-tracker/
docker/ , Shell, 15 linesis_tierpsy_running.sh - tierpsy-tracker/
docker/ , Shell, 89 linesrun_tierpsy_docker.sh - tierpsy-tracker/
recipe/ , Shell, 2 linesbuild.sh - tierpsy-tracker/
setup.py , Python, 77 lines - tierpsy-tracker/
tierpsy/ , Python, 63 lines__init__.py - tierpsy-tracker/
tierpsy/ , Python, 1 lineanalysis/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 81 linesanalysis/ _dev/ nn_bgnd/ add_bgnd.py - tierpsy-tracker/
tierpsy/ , Python, 144 linesanalysis/ _dev/ nn_bgnd/ unet.py - tierpsy-tracker/
tierpsy/ , Python, 87 linesanalysis/ _dev/ nn_poses/ extract_poses.py - tierpsy-tracker/
tierpsy/ , Python, 327 linesanalysis/ _dev/ nn_poses/ models.py - tierpsy-tracker/
tierpsy/ , Python, 19 linesanalysis/ blob_feats/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 158 linesanalysis/ blob_feats/ getBlobsFeats.py - tierpsy-tracker/
tierpsy/ , Python, 340 linesanalysis/ compress/ BackgroundSubtractor.py - tierpsy-tracker/
tierpsy/ , Python, 145 linesanalysis/ compress/ Readers/ ReadVideoFFMPEG.py - tierpsy-tracker/
tierpsy/ , Python, 1 lineanalysis/ compress/ Readers/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 80 linesanalysis/ compress/ Readers/ readDatFiles.py - tierpsy-tracker/
tierpsy/ , Python, 60 linesanalysis/ compress/ Readers/ readImages.py - tierpsy-tracker/
tierpsy/ , Python, 51 linesanalysis/ compress/ Readers/ readLoopBio.py - tierpsy-tracker/
tierpsy/ , Python, 28 linesanalysis/ compress/ Readers/ readTifStack.py - tierpsy-tracker/
tierpsy/ , Python, 53 linesanalysis/ compress/ Readers/ readVideoCapture.py - tierpsy-tracker/
tierpsy/ , Python, 64 linesanalysis/ compress/ Readers/ readVideoHDF5.py - tierpsy-tracker/
tierpsy/ , Python, 72 linesanalysis/ compress/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 462 linesanalysis/ compress/ compressVideo.py - tierpsy-tracker/
tierpsy/ , Python, 237 linesanalysis/ compress/ extractMetaData.py - tierpsy-tracker/
tierpsy/ , Python, 164 linesanalysis/ compress/ processVideo.py - tierpsy-tracker/
tierpsy/ , Python, 51 linesanalysis/ compress/ selectVideoReader.py - tierpsy-tracker/
tierpsy/ , Python, 17 linesanalysis/ compress_add_data/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 254 linesanalysis/ compress_add_data/ getAdditionalData.py - tierpsy-tracker/
tierpsy/ , Python, 24 linesanalysis/ contour_orient/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 96 linesanalysis/ contour_orient/ correctVentralDorsal.py - tierpsy-tracker/
tierpsy/ , Python, 37 linesanalysis/ feat_create/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 422 linesanalysis/ feat_create/ obtainFeatures.py - tierpsy-tracker/
tierpsy/ , Python, 474 linesanalysis/ feat_create/ obtainFeaturesHelper.py - tierpsy-tracker/
tierpsy/ , Python, 23 linesanalysis/ feat_init/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 357 linesanalysis/ feat_init/ smooth_skeletons_table.p y - tierpsy-tracker/
tierpsy/ , Python, 22 linesanalysis/ feat_manual_create/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 25 linesanalysis/ feat_tierpsy/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 265 linesanalysis/ feat_tierpsy/ get_tierpsy_features.py - tierpsy-tracker/
tierpsy/ , Python, 24 linesanalysis/ food_cnt/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 77 linesanalysis/ food_cnt/ getFoodContour.py - tierpsy-tracker/
tierpsy/ , Python, 337 linesanalysis/ food_cnt/ getFoodContourMorph.py - tierpsy-tracker/
tierpsy/ , Python, 481 linesanalysis/ food_cnt/ getFoodContourNN.py - tierpsy-tracker/
tierpsy/ , Python, 35 linesanalysis/ int_profile/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 320 linesanalysis/ int_profile/ getIntensityProfile.py - tierpsy-tracker/
tierpsy/ , Python, 24 linesanalysis/ int_ske_orient/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 279 linesanalysis/ int_ske_orient/ checkFinalOrientation.py - tierpsy-tracker/
tierpsy/ , Python, 568 linesanalysis/ int_ske_orient/ correctHeadTailIntensity .py - tierpsy-tracker/
tierpsy/ , Python, 81 linesanalysis/ nn_bgnd/ add_bgnd.py - tierpsy-tracker/
tierpsy/ , Python, 144 linesanalysis/ nn_bgnd/ unet.py - tierpsy-tracker/
tierpsy/ , Python, 32 linesanalysis/ ske_create/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 398 linesanalysis/ ske_create/ getSkeletonsTables.py - tierpsy-tracker/
tierpsy/ , Python, 161 linesanalysis/ ske_create/ helperIterROI.py - tierpsy-tracker/
tierpsy/ , Python, 1 lineanalysis/ ske_create/ segWormPython/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 553 linesanalysis/ ske_create/ segWormPython/ cleanWorm.py - tierpsy-tracker/
tierpsy/ , Python, 1 lineanalysis/ ske_create/ segWormPython/ cython_files/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 31 linesanalysis/ ske_create/ segWormPython/ cython_files/ _old/ setup.py - tierpsy-tracker/
tierpsy/ , C, 225 linesanalysis/ ske_create/ segWormPython/ cython_files/ c_circCurvature.c - tierpsy-tracker/
tierpsy/ , C, 151 linesanalysis/ ske_create/ segWormPython/ cython_files/ c_curvspace.c - tierpsy-tracker/
tierpsy/ , Python, 159 linesanalysis/ ske_create/ segWormPython/ getHeadTail.py - tierpsy-tracker/
tierpsy/ , Python, 430 linesanalysis/ ske_create/ segWormPython/ linearSkeleton.py - tierpsy-tracker/
tierpsy/ , Python, 324 linesanalysis/ ske_create/ segWormPython/ mainSegworm.py - tierpsy-tracker/
tierpsy/ , Python, 1 lineanalysis/ ske_create/ zebrafishAnalysis/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 529 linesanalysis/ ske_create/ zebrafishAnalysis/ zebrafishAnalysis.py - tierpsy-tracker/
tierpsy/ , Python, 209 linesanalysis/ ske_create/ zebrafishAnalysis/ zebrafishSkeleton.py - tierpsy-tracker/
tierpsy/ , Python, 23 linesanalysis/ ske_filt/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 504 linesanalysis/ ske_filt/ getFilteredSkels.py - tierpsy-tracker/
tierpsy/ , Python, 32 linesanalysis/ ske_init/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 149 linesanalysis/ ske_init/ filterTrajectModel.py - tierpsy-tracker/
tierpsy/ , Python, 315 linesanalysis/ ske_init/ processTrajectoryData.py - tierpsy-tracker/
tierpsy/ , Python, 168 linesanalysis/ ske_init/ pytorchModelHelper.py - tierpsy-tracker/
tierpsy/ , Python, 167 linesanalysis/ ske_orient/ WormClass.py - tierpsy-tracker/
tierpsy/ , Python, 22 linesanalysis/ ske_orient/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 213 linesanalysis/ ske_orient/ checkHeadOrientation.py - tierpsy-tracker/
tierpsy/ , Python, 10 linesanalysis/ ske_pharynx/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 163 linesanalysis/ ske_pharynx/ orient_pharynx.py - tierpsy-tracker/
tierpsy/ , Python, 66 linesanalysis/ ske_pharynx/ pharynx_velocity.py - tierpsy-tracker/
tierpsy/ , Python, 1,082 linesanalysis/ split_fov/ FOVMultiWellsSplitter.py - tierpsy-tracker/
tierpsy/ , Python, 1 lineanalysis/ split_fov/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 428 linesanalysis/ split_fov/ helper.py - tierpsy-tracker/
tierpsy/ , Python, 11 linesanalysis/ stage_aligment/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 219 linesanalysis/ stage_aligment/ alignStageMotion.py - tierpsy-tracker/
tierpsy/ , Python, 1,323 linesanalysis/ stage_aligment/ findStageMovement.py - tierpsy-tracker/
tierpsy/ , Python, 33 linesanalysis/ traj_create/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 483 linesanalysis/ traj_create/ getBlobTrajectories.py - tierpsy-tracker/
tierpsy/ , Python, 23 linesanalysis/ traj_join/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 415 linesanalysis/ traj_join/ joinBlobsTrajectories.py - tierpsy-tracker/
tierpsy/ , Python, 21 linesanalysis/ vid_subsample/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 143 linesanalysis/ vid_subsample/ createSampleVideo.py - tierpsy-tracker/
tierpsy/ , Python, 25 linesanalysis/ wcon_export/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 286 linesanalysis/ wcon_export/ exportWCON.py - tierpsy-tracker/
tierpsy/ , Python, 50 linesdebugging/ add_default_attrs.py - tierpsy-tracker/
tierpsy/ , Python, 42 linesdebugging/ catch_infinite_loop.py - tierpsy-tracker/
tierpsy/ , Python, 87 linesdebugging/ check4corrupt_frames.py - tierpsy-tracker/
tierpsy/ , Python, 111 linesdebugging/ check_default_attrs.py - tierpsy-tracker/
tierpsy/ , Python, 53 linesdebugging/ check_flow.py - tierpsy-tracker/
tierpsy/ , Python, 44 linesdebugging/ check_roi_flow.py - tierpsy-tracker/
tierpsy/ , Python, 19 linesdebugging/ export2images.py - tierpsy-tracker/
tierpsy/ , Python, 115 linesdebugging/ test_fovsplitting.py - tierpsy-tracker/
tierpsy/ , Python, 155 linesdebugging/ test_pipeline.py - tierpsy-tracker/
tierpsy/ , Python, 1 linefeatures/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 49 linesfeatures/ open_worm_analysis_toolb ox/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 48 linesfeatures/ open_worm_analysis_toolb ox/ config.py - tierpsy-tracker/
tierpsy/ , Python, 1 linefeatures/ open_worm_analysis_toolb ox/ features/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 1,096 linesfeatures/ open_worm_analysis_toolb ox/ features/ events.py - tierpsy-tracker/
tierpsy/ , Python, 240 linesfeatures/ open_worm_analysis_toolb ox/ features/ feature_manipulations.py - tierpsy-tracker/
tierpsy/ , Python, 305 linesfeatures/ open_worm_analysis_toolb ox/ features/ feature_processing_optio ns.py - tierpsy-tracker/
tierpsy/ , Python, 415 linesfeatures/ open_worm_analysis_toolb ox/ features/ generic_features.py - tierpsy-tracker/
tierpsy/ , Python, 1,731 linesfeatures/ open_worm_analysis_toolb ox/ features/ locomotion_bends.py - tierpsy-tracker/
tierpsy/ , Python, 490 linesfeatures/ open_worm_analysis_toolb ox/ features/ locomotion_features.py - tierpsy-tracker/
tierpsy/ , Python, 1,364 linesfeatures/ open_worm_analysis_toolb ox/ features/ locomotion_turns.py - tierpsy-tracker/
tierpsy/ , Python, 167 linesfeatures/ open_worm_analysis_toolb ox/ features/ morphology_features.py - tierpsy-tracker/
tierpsy/ , Python, 552 linesfeatures/ open_worm_analysis_toolb ox/ features/ path_features.py - tierpsy-tracker/
tierpsy/ , Python, 1,379 linesfeatures/ open_worm_analysis_toolb ox/ features/ posture_features.py - tierpsy-tracker/
tierpsy/ , Python, 559 linesfeatures/ open_worm_analysis_toolb ox/ features/ velocity.py - tierpsy-tracker/
tierpsy/ , Python, 1,259 linesfeatures/ open_worm_analysis_toolb ox/ features/ worm_features.py - tierpsy-tracker/
tierpsy/ , Python, 1 linefeatures/ open_worm_analysis_toolb ox/ prefeatures/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 717 linesfeatures/ open_worm_analysis_toolb ox/ prefeatures/ basic_worm.py - tierpsy-tracker/
tierpsy/ , Python, 789 linesfeatures/ open_worm_analysis_toolb ox/ prefeatures/ normalized_worm.py - tierpsy-tracker/
tierpsy/ , Python, 310 linesfeatures/ open_worm_analysis_toolb ox/ prefeatures/ pre_features.py - tierpsy-tracker/
tierpsy/ , Python, 245 linesfeatures/ open_worm_analysis_toolb ox/ prefeatures/ pre_features_helpers.py - tierpsy-tracker/
tierpsy/ , Python, 769 linesfeatures/ open_worm_analysis_toolb ox/ prefeatures/ skeleton_calculator1.py - tierpsy-tracker/
tierpsy/ , Python, 217 linesfeatures/ open_worm_analysis_toolb ox/ prefeatures/ video_info.py - tierpsy-tracker/
tierpsy/ , Python, 18 linesfeatures/ open_worm_analysis_toolb ox/ user_config.py - tierpsy-tracker/
tierpsy/ , Python, 1,197 linesfeatures/ open_worm_analysis_toolb ox/ utils.py - tierpsy-tracker/
tierpsy/ , Python, 5 linesfeatures/ open_worm_analysis_toolb ox/ version.py - tierpsy-tracker/
tierpsy/ , Python, 9 linesfeatures/ tierpsy_features/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 297 linesfeatures/ tierpsy_features/ curvatures.py - tierpsy-tracker/
tierpsy/ , Python, 417 linesfeatures/ tierpsy_features/ events.py - tierpsy-tracker/
tierpsy/ , Python, 130 linesfeatures/ tierpsy_features/ features.py - tierpsy-tracker/
tierpsy/ , Python, 125 linesfeatures/ tierpsy_features/ food.py - tierpsy-tracker/
tierpsy/ , Python, 156 linesfeatures/ tierpsy_features/ helper.py - tierpsy-tracker/
tierpsy/ , Python, 373 linesfeatures/ tierpsy_features/ path.py - tierpsy-tracker/
tierpsy/ , Python, 176 linesfeatures/ tierpsy_features/ postures.py - tierpsy-tracker/
tierpsy/ , Python, 380 linesfeatures/ tierpsy_features/ smooth.py - tierpsy-tracker/
tierpsy/ , Python, 469 linesfeatures/ tierpsy_features/ summary_stats.py - tierpsy-tracker/
tierpsy/ , Python, 324 linesfeatures/ tierpsy_features/ velocities.py - tierpsy-tracker/
tierpsy/ , Python, 13 linesfeatures/ tierpsy_features/ version.py - tierpsy-tracker/
tierpsy/ , Python, 164 linesgui/ AnalysisProgress.py - tierpsy-tracker/
tierpsy/ , Python, 36 linesgui/ AnalysisProgress_ui.py - tierpsy-tracker/
tierpsy/ , Python, 314 linesgui/ BatchProcessing.py - tierpsy-tracker/
tierpsy/ , Python, 202 linesgui/ BatchProcessing_ui.py - tierpsy-tracker/
tierpsy/ , Python, 303 linesgui/ GetAllParameters.py - tierpsy-tracker/
tierpsy/ , Python, 557 linesgui/ GetMaskParams.py - tierpsy-tracker/
tierpsy/ , Python, 455 linesgui/ GetMaskParams_ui.py - tierpsy-tracker/
tierpsy/ , Python, 572 linesgui/ HDF5VideoPlayer.py - tierpsy-tracker/
tierpsy/ , Python, 151 linesgui/ HDF5VideoPlayer_ui.py - tierpsy-tracker/
tierpsy/ , Python, 1,175 linesgui/ MWTrackerViewer.py - tierpsy-tracker/
tierpsy/ , Python, 382 linesgui/ MWTrackerViewer_ui.py - tierpsy-tracker/
tierpsy/ , Python, 251 linesgui/ PlotFeatures.py - tierpsy-tracker/
tierpsy/ , Python, 271 linesgui/ SWTrackerViewer.py - tierpsy-tracker/
tierpsy/ , Python, 183 linesgui/ SWTrackerViewer_ui.py - tierpsy-tracker/
tierpsy/ , Python, 93 linesgui/ SelectApp.py - tierpsy-tracker/
tierpsy/ , Python, 88 linesgui/ Summarizer.py - tierpsy-tracker/
tierpsy/ , Python, 345 linesgui/ Summarizer_ui.py - tierpsy-tracker/
tierpsy/ , Python, 538 linesgui/ TrackerViewerAux.py - tierpsy-tracker/
tierpsy/ , Python, 157 linesgui/ TrackerViewerAux_ui.py - tierpsy-tracker/
tierpsy/ , Python, 3 linesgui/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 1 linehelper/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 3 lineshelper/ misc/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 65 lineshelper/ misc/ file_processing.py - tierpsy-tracker/
tierpsy/ , Python, 81 lineshelper/ misc/ misc.py - tierpsy-tracker/
tierpsy/ , Python, 57 lineshelper/ misc/ time_counter.py - tierpsy-tracker/
tierpsy/ , Python, 4 lineshelper/ params/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 76 lineshelper/ params/ docs_analysis_points.py - tierpsy-tracker/
tierpsy/ , Python, 109 lineshelper/ params/ docs_process_param.py - tierpsy-tracker/
tierpsy/ , Python, 171 lineshelper/ params/ docs_summarizer_param.py - tierpsy-tracker/
tierpsy/ , Python, 426 lineshelper/ params/ docs_tracker_param.py - tierpsy-tracker/
tierpsy/ , Python, 114 lineshelper/ params/ get_defaults.py - tierpsy-tracker/
tierpsy/ , Python, 41 lineshelper/ params/ helper.py - tierpsy-tracker/
tierpsy/ , Python, 48 lineshelper/ params/ models_path.py - tierpsy-tracker/
tierpsy/ , Python, 236 lineshelper/ params/ read_attrs.py - tierpsy-tracker/
tierpsy/ , Python, 311 lineshelper/ params/ tracker_param.py - tierpsy-tracker/
tierpsy/ , Python, 155 linesprocessing/ AnalysisPoints.py - tierpsy-tracker/
tierpsy/ , Python, 247 linesprocessing/ CheckFilesForProcessing. py - tierpsy-tracker/
tierpsy/ , Python, 101 linesprocessing/ CheckFinished.py - tierpsy-tracker/
tierpsy/ , Python, 316 linesprocessing/ ProcessLocal.py - tierpsy-tracker/
tierpsy/ , Python, 108 linesprocessing/ ProcessWorker.py - tierpsy-tracker/
tierpsy/ , Python, 1 lineprocessing/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 134 linesprocessing/ helper.py - tierpsy-tracker/
tierpsy/ , Python, 133 linesprocessing/ processMultipleFilesFun. py - tierpsy-tracker/
tierpsy/ , Python, 218 linesprocessing/ run_multi_cmd.py - tierpsy-tracker/
tierpsy/ , Python, 50 linesprocessing/ trackProvenance.py - tierpsy-tracker/
tierpsy/ , Python, 1 linesummary/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 490 linessummary/ collect.py - tierpsy-tracker/
tierpsy/ , Python, 77 linessummary/ filtering.py - tierpsy-tracker/
tierpsy/ , Python, 189 linessummary/ helper.py - tierpsy-tracker/
tierpsy/ , Python, 269 linessummary/ parsers.py - tierpsy-tracker/
tierpsy/ , Python, 154 linessummary/ process_ow.py - tierpsy-tracker/
tierpsy/ , Python, 464 linessummary/ process_tierpsy.py - tierpsy-tracker/
tierpsy/ , Python, 1 linetests/ __init__.py - tierpsy-tracker/
tierpsy/ , Python, 287 linestests/ run_tests.py - tierpsy-tracker/
tierpsy/ , Python, 137 linesversion.py - utils.py, Python, 279 lines
- LICENSE, License, 21 lines
- README.md, Text, 233 lines
Zenodo 13748735
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
- 28 September 2026: the link answers (HTTP 200)
Code availability statement
The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it points to the authors' code: venkatachalamlab/
Yeon-2025-pumping-analys , Zenodo 13748735is
Read it in the paper: doi.org/10.1038/s41586-026-10348-3.
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;
- 206 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
Datasets cited
- bioproject:PRJNA1227491, at NCBI BioProject; found in “Data availability”
- figshare:28611836, at figshare; found in “Data availability”
Data availability statement
The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it points to 2 datasets: figshare 28611836, NCBI BioProject PRJNA1227491
Read it in the paper: doi.org/10.1038/s41586-026-10348-3.
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 2, 28 September 2026
- Publisher: n/a → Nature Portfolio
Version 1, 28 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 13 authors, 3 keywords, 11 MeSH terms, 3 funders, 75 references, 1 RRID.
Cite
This paper
Yeon, J., Kim, J., Sato, K., Nurrish, S., Chen, L., Krishnan, N., Bates, S., Ihara, S., Rasouli, S., Porwal, C., Venkatachalam, V., Touhara, K., & Sengupta, P. (2026). An enteric neuron ionotropic receptor regulates salt stress resistance. Nature, 654(8120), 1023-1032. https://
BibTeX
@article{yeon2026enteric
author = {Yeon, Jihye and Kim, Jinmahn and Sato, Koji and Nurrish, Stephen and Chen, Laurie and Krishnan, Nikhila and Bates, Sam and Ihara, Sayoko and Rasouli, Sina and Porwal, Charmi and Venkatachalam, Vivek and Touhara, Kazushige and Sengupta, Piali},
title = {{An enteric neuron ionotropic receptor regulates salt stress resistance}},
journal = {Nature},
year = {2026},
month = apr,
volume = {654},
number = {8120},
pages = {1023--1032},
publisher = {Nature Portfolio},
issn = {0028-0836},
doi = {10.1038/
url = {https://
pmid = {41922765},
pmcid = {PMC13293861}
}
RIS
TY - JOUR
AU - Yeon, Jihye
AU - Kim, Jinmahn
AU - Sato, Koji
AU - Nurrish, Stephen
AU - Chen, Laurie
AU - Krishnan, Nikhila
AU - Bates, Sam
AU - Ihara, Sayoko
AU - Rasouli, Sina
AU - Porwal, Charmi
AU - Venkatachalam, Vivek
AU - Touhara, Kazushige
AU - Sengupta, Piali
TI - An enteric neuron ionotropic receptor regulates salt stress resistance
T2 - Nature
J2 - Nature
PY - 2026
DA - 2026/
VL - 654
IS - 8120
SP - 1023
EP - 1032
SN - 0028-0836
PB - Nature Portfolio
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "An enteric neuron ionotropic receptor regulates salt stress resistance",
"container-title": "Nature",
"author": [
{
"family": "Yeon",
"given": "Jihye"
},
{
"family": "Kim",
"given": "Jinmahn"
},
{
"family": "Sato",
"given": "Koji"
},
{
"family": "Nurrish",
"given": "Stephen"
},
{
"family": "Chen",
"given": "Laurie"
},
{
"family": "Krishnan",
"given": "Nikhila"
},
{
"family": "Bates",
"given": "Sam"
},
{
"family": "Ihara",
"given": "Sayoko"
},
{
"family": "Rasouli",
"given": "Sina"
},
{
"family": "Porwal",
"given": "Charmi"
},
{
"family": "Venkatachalam",
"given": "Vivek"
},
{
"family": "Touhara",
"given": "Kazushige"
},
{
"family": "Sengupta",
"given": "Piali"
}
],
"container-title-short":
"volume": "654",
"issue": "8120",
"page": "1023-1032",
"DOI": "10.1038/
"PMID": "41922765",
"PMCID": "PMC13293861",
"ISSN": "0028-0836",
"publisher": "Nature Portfolio",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
1
]
]
}
}
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.1038/s41593-026-02257-5 [code]
- Neural sequences underlying directed turning in Caenorhabditis elegans.Journal: Nature neuroscienceIn common: h5py, Pillow, PyTorch, 4 other tools, C. elegans, 7 references
- [2] doi:10.1038/s41467-026-72057-9 [code]
- Sex-specific behavioral feedback modulates sensorimotor processing and drives flexible social behavior.Journal: Nature communicationsIn common: Keras, Numba, OpenCV, 10 other tools
- [3] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: Keras, Numba, OpenCV, 9 other tools, cellular / molecular
- [4] doi: [code]
- Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement controlJournal: eLifeIn common: Keras, Numba, OpenCV, 9 other tools
- [5] doi:10.1016/j.isci.2026.116825 [code]
- Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.Journal: iScienceIn common: Numba, OpenCV, scikit-image, 9 other tools, 1 reference
- [6] 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: Keras, Numba, OpenCV, 8 other tools, cellular / molecular, 1 reference
- [7] 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: Keras, Numba, OpenCV, 9 other tools
- [8] doi:10.1016/j.cub.2026.06.016 [code]
- Neuronal RNAi and oxygen-sensing circuit shape germline resilience to heat stress.Journal: Current biology : CBIn common: Numba, statsmodels, scikit-learn, 4 other tools, C. elegans, 4 references
- [9] doi:10.1038/s41593-026-02232-0 [code]
- Entorhinal cortex represents task-relevant remote locations independently of CA1.Journal: Nature neuroscienceIn common: Numba, OpenCV, scikit-image, 9 other tools
- [10] doi:10.1038/s41467-026-72709-w [code]
- An epifluorescence microscope design for naturalistic behavior and cellular activity in freely moving Caenorhabditis elegans.Journal: Nature communicationsIn common: OpenCV, scikit-image, h5py, 6 other tools, C. elegans, cellular / molecular, 1 reference
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, 206 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:11034af2281a81ca…
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.
