Disentangling cephalopod chromatophores motor units with computer vision.
The 21 matches
- [1] § Materials and methods › Chromatophore video acquisition and analysis › Computational analyses ↔ chromas/source/training/neural_net_training.py, lines 1–64 · score 0.89 · cross entropy loss, neural network, Dice loss, loss function, scheduler, FCN
- [2] § Materials and methods › Chromatophore video acquisition and analysis › Motor unit shape and structure ↔ motor_units/mu_structure.py, lines 181–313 · score 0.89 · minimum spanning tree, epicenter_x, epicenter_y, nearest neighbor, chromatophore epicenters, FND
- [3] § Materials and methods › Chromatophore video acquisition and analysis › Motor unit shape and structure ↔ motor_units/distances.py, lines 1–23 · score 0.87 · furthest neighbor distances, nearest neighbor distances, epicenter_x, epicenter_y, Epicenter coordinates, NND
- [4] § Materials and methods › Chromatophore video acquisition and analysis › Number and influence of motor neuron per chromatophore ↔ motor_units/anisotropy.py, lines 1–63 · score 0.79 · percentage influence profile, mixing matrix, motor neuron, radial slice, ICA, component
- [5] § Materials and methods › Chromatophore video acquisition and analysis › Number and influence of motor neuron per chromatophore ↔ motor_units/anisotropy.py, lines 1–63 · score 0.78 · meaningful components, motor neuron, radial slices, detrended, signal, PCA
- [6] § Materials and methods › Chromatophore video acquisition and analysis › Expansion and contraction speeds ↔ motor_units/exp_vs_contr.py, lines 6–29 · score 0.77 · contraction dynamics, detected peak, Peak centered, radii, smoothing, traces
- [7] § Materials and methods › Chromatophore video acquisition and analysis › Expansion and contraction speeds ↔ motor_units/exp_vs_contr.py, lines 747–887 · score 0.73 · percentile tails, remove outliers, suppress, IQR, noise, amplitude
- [8] § Materials and methods › Chromatophore video acquisition and analysis › Number and influence of motor neuron per chromatophore ↔ chromas/source/ica/ica_utils.py, lines 10–56 · score 0.70 · n_components, elbow point, kneed, detrended, signal, ICA
- [9] § Materials and methods › Chromatophore video acquisition and analysis › Number of chromatophores per motor unit ↔ chromas/source/analysis/clustering_utils.py, lines 7–21 · score 0.65 · sklearn.cluster.AffinityPropagation, scikit-learn, metric, correlation
- [10] § Materials and methods › Chromatophore video acquisition and analysis › Second-order innervation ↔ motor_units/mu_cooccurence.py, lines 1–45 · score 0.65 · cluster memberships, co occurrence, simulations, chromatophore
- [11] § Results › Structure and size of chromatophore motor units ↔ motor_units/mu_cooccurence.py, lines 1–45 · score 0.63 · co occurrence, co occurring, chromatophore pairs, randomized, memberships, motor unit
- [12] § Results › Structure and size of chromatophore motor units ↔ motor_units/mu_structure.py, lines 315–347 · score 0.60 · convex hull area, cluster area, encloses, motor units, median, smaller
- [13] § Results › Structure and size of chromatophore motor units ↔ motor_units/mu_structure.py, lines 181–313 · score 0.59 · global nearest neighbor, furthest distance, NND, MU, motor unit, clusters
- [14] § Materials and methods › Chromatophore video acquisition and analysis › Computational analyses ↔ chromas/source/training/neural_net_training.py, lines 526–545 · score 0.59 · 0.05–0.5, brightness, flipping, vertical, shifts, training
- [15] § Materials and methods › Chromatophore video acquisition and analysis › Computational analyses ↔ chromas/source/training/unet_training.py, lines 258–277 · score 0.59 · 0.05–0.5, brightness, flipping, vertical, shifts, training
- [16] § Results › Interpretation of fine single-chromatophore motion ↔ motor_units/anisotropy.py, lines 136–180 · score 0.58 · cumulative explained variance, principal components, curve, PCA
- [17] § Results › Methodological development with Euprymna berryi ↔ chromas/source/segmentation/segmentation_neuralnet.py, lines 1–37 · score 0.58 · Sepia officinalis, individual chromatophores, species, berryi, Euprymna, segmented
- [18] § Results › Dynamics of chromatophore expansion and contraction ↔ motor_units/exp_vs_contr.py, lines 747–887 · score 0.57 · aligned event, band, split, IQR, outliers, tail
- [19] § Results › Structure and size of chromatophore motor units ↔ motor_units/mu_structure.py, lines 349–396 · score 0.54 · convex hull area, log scale, px2, motor unit, density, chromatophore
- [20] § Results › Structure and size of chromatophore motor units ↔ motor_units/distances.py, lines 1–23 · score 0.53 · nearest neighbor distance, closest, furthest, NND, motor unit, clusters
- [21] § Results › Analysis of chromatophore motion in S. officinalis ↔ motor_units/anisotropy.py, lines 136–180 · score 0.50 · Cumulative explained variance, principal component, PCA, motor
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 · 408 lines · 16 KB · BSD-3-Clause · 4 matches
- import pandas as pd
- import numpy as np
- import matplotlib.pyplot as plt
- from scipy.spatial import distance_matrix
- from scipy.sparse.csgraph import minimum_spanning_tree
- from matplotlib.patches import Wedge
- from collections import defaultdict
- from matplotlib.image import imread
- from scipy.spatial import ConvexHull
- from scipy.stats import pearsonr
- """
- This script loads chromatophore cluster assignments and spatial coordinates,
- builds a set of lookup tables that map clusters to chromatophores and vice
- versa, and provides tools to explore the structure of those clusters. It prints
- cluster groups sorted by size and offers plotting utilities to visualize spatial
- organization, distances, and geometric structure such as convex hulls,
- projections, and pairwise relationships.
- Usage:
- Call `load_data(csv_path)` with a CSV file containing `chrom_id`, `label`,
- `epicenter_x`, and `epicenter_y`. After the data are loaded, run
- `list_clusters()` to inspect cluster sizes, or use the plotting functions to
- visualize spatial layouts and relationships. All analysis functions assume that
- `load_data` has been executed first.
- Author:
- Mathieu D. M. Renard (Laurent Lab, 2025)
- """
- plt.rcParams.update({
- 'font.size': 20, # smaller font
- 'axes.titlesize': 18, # smaller title
- 'axes.labelsize': 18, # smaller axis labels
- 'xtick.labelsize': 18,
- 'ytick.labelsize': 18,
- 'legend.fontsize': 14,
- 'lines.linewidth': 2, # thinner lines
- 'lines.markersize': 5, # smaller markers
- 'figure.dpi': 150, # control overall figure size/dpi
- 'figure.figsize': (6, 8), # smaller figure dimensions
- 'xtick.major.size': 5,
- 'ytick.major.size': 5,
- 'xtick.major.width': 1.5,
- 'ytick.major.width': 1.5,
- })
- # === Global state ===
- df_global = None
- label_to_chroms = None
- chrom_to_labels = None
- coords_lookup = None
- # === Load and prepare data ===
- def load_data(csv_path):
- global df_global, label_to_chroms, chrom_to_labels, coords_lookup
- df = pd.read_csv(csv_path)
- df = df[['chrom_id', 'label', 'epicenter_x', 'epicenter_y']].dropna().drop_duplicates()
- df_global = df
- label_to_chroms = df.groupby('label')['chrom_id'].apply(lambda x: list(set(x)))
- chrom_to_labels = defaultdict(set)
- for _, row in df.iterrows():
- chrom_to_labels[row['chrom_id']].add(row['label'])
- unique_chroms = df[['chrom_id', 'epicenter_x', 'epicenter_y']].drop_duplicates()
- coords_lookup = dict(zip(unique_chroms['chrom_id'], zip(unique_chroms['epicenter_x'], unique_chroms['epicenter_y'])))
- # === Cluster listing ===
- def list_clusters():
- print("\n📊 Cluster groups by size:\n")
- size_to_labels = defaultdict(list)
- for label, chrom_list in label_to_chroms.items():
- size_to_labels[len(chrom_list)].append(label)
- for size in sorted(size_to_labels.keys(), reverse=True):
- labels = size_to_labels[size]
- print(f"{size}: {', '.join(map(str, labels))}")
- # === Plotting ===
- def get_color_map(n):
- return plt.cm.get_cmap('tab10' if n <= 10 else 'hsv', n)
- def plot_clusters(selected_labels, title_suffix="", wedge_scale=0.003, dot_size=350, background_img_path=None):
- cmap = get_color_map(len(selected_labels))
- label_to_color = {label: cmap(i) for i, label in enumerate(selected_labels)}
- clustered = df_global[df_global['label'].isin(selected_labels)].copy()
- x_vals = [x for (x, _) in coords_lookup.values()]
- y_vals = [y for (_, y) in coords_lookup.values()]
- x_range = max(x_vals) - min(x_vals)
- y_range = max(y_vals) - min(y_vals)
- wedge_r = wedge_scale * max(x_range, y_range)
- plt.figure(figsize=(8, 8))
- if background_img_path:
- img = imread(background_img_path)
- img_height, img_width = img.shape[:2]
- plt.imshow(np.swapaxes(img, 0, 1), extent=[0, img_width, img_height, 0], alpha=0.5)
- # Compute extent from chromatophore coordinates
- all_coords = []
- for label in selected_labels:
- chrom_ids = label_to_chroms.get(label, [])
- all_coords.extend([coords_lookup[cid] for cid in chrom_ids if cid in coords_lookup])
- if all_coords:
- all_coords = np.array(all_coords)
- x_min, x_max = np.min(all_coords[:, 0]), np.max(all_coords[:, 0])
- y_min, y_max = np.min(all_coords[:, 1]), np.max(all_coords[:, 1])
- # Add margin
- margin_x = 0.5 * (x_max - x_min)
- margin_y = 0.5 * (y_max - y_min)
- # Set axis limits (note y axis is top-down to match image)
- plt.xlim(x_min - margin_x, x_max + margin_x)
- plt.ylim(y_max + margin_y, y_min - margin_y)
- for label in selected_labels:
- chrom_ids = label_to_chroms.get(label, [])
- coords = np.array([coords_lookup[cid] for cid in chrom_ids])
- if len(coords) < 2:
- continue
- # Add label next to the cluster (at centroid)
- # centroid_x = np.mean(coords[:, 0])
- # centroid_y = np.mean(coords[:, 1])
- # plt.text(centroid_x, centroid_y, str(label), fontsize=8, ha='center', va='center', color='black', zorder=4)
- dist = distance_matrix(coords, coords)
- mst = minimum_spanning_tree(dist).toarray()
- for i in range(len(coords)):
- for j in range(len(coords)):
- if mst[i, j] > 0:
- plt.plot([coords[i, 0], coords[j, 0]], [coords[i, 1], coords[j, 1]],
- color=label_to_color[label], linewidth=1, alpha=0.6)
- for chrom_id, all_labels in chrom_to_labels.items():
- active_labels = list(all_labels & set(selected_labels))
- if not active_labels:
- continue
- x, y = coords_lookup[chrom_id]
- if len(active_labels) == 1:
- plt.scatter(x, y, color=label_to_color[active_labels[0]], s=dot_size, zorder=3)
- else:
- angle_step = 360 / len(active_labels)
- for i, lbl in enumerate(active_labels):
- wedge = Wedge(center=(x, y), r=wedge_r,
- theta1=i*angle_step, theta2=(i+1)*angle_step,
- facecolor=label_to_color[lbl], edgecolor='black', linewidth=0.1)
- plt.gca().add_patch(wedge)
- #plt.title(f"Chromatophore Clusters {title_suffix}")
- plt.xlabel("epicenter_x")
- plt.ylabel("epicenter_y")
- plt.gca().set_aspect('equal')
- # plt.grid(True, linestyle='--', alpha=0.3)
- plt.tight_layout()
- plt.show()
- def plot_clusters_by_labels(labels, wedge_scale=0.002, background_img_path=None):
- plot_clusters(labels, title_suffix=f"(labels: {', '.join(map(str, labels))})", wedge_scale=wedge_scale, background_img_path=background_img_path)
- def plot_clusters_by_size(size, wedge_scale=0.003, background_img_path=None):
- labels = [label for label, chroms in label_to_chroms.items() if len(chroms) == size]
- if not labels:
- print(f"❌ No clusters found with size {size}")
- else:
- print(f"🔍 Plotting all clusters with size {size}: {labels}")
- plot_clusters(labels, title_suffix=f"(size = {size})", wedge_scale=wedge_scale, background_img_path=background_img_path)
- # === Main NND + FND function with plotting and area filtering ===
- def compute_and_plot_global_nnd(csv_path, within_cluster_only=False, area_threshold_min=None, area_threshold_max=None, background_img_path=None):
- df = pd.read_csv(csv_path)
- df = df[['chrom_id', 'label', 'epicenter_y', 'epicenter_x']].dropna().drop_duplicates()
- if within_cluster_only:
- coords = df.groupby('chrom_id')[['epicenter_y', 'epicenter_x']].mean().reset_index()
- df = df[['chrom_id', 'label']].drop_duplicates().merge(coords, on='chrom_id')
- else:
- df = df.groupby('chrom_id')[['epicenter_y', 'epicenter_x']].mean().reset_index()
- if len(df) < 2:
- print("❌ Not enough valid chromatophores to compute NND.")
- return np.nan, [], []
- all_nnd = []
- all_furthest = []
- if within_cluster_only:
- print("🔎 Computing Nearest-Neighbor, Furthest Distances, and Areas within each cluster...")
- grouped = df.groupby('label')
- plt.figure(figsize=(8, 8))
- cmap = plt.cm.get_cmap('tab10', len(grouped))
- for label, group in grouped:
- coords = group[['epicenter_y', 'epicenter_x']].to_numpy()
- if len(coords) < 2:
- continue
- # your plotting code for links
- # Place label once per cluster
- centroid_x = np.mean(coords[:, 0])
- centroid_y = np.mean(coords[:, 1])
- plt.text(centroid_x, centroid_y, str(label), fontsize=8, ha='center', va='center', color='black', zorder=4)
- if background_img_path:
- img = imread(background_img_path)
- img_height, img_width = img.shape[0], img.shape[1]
- plt.imshow(img, extent=[0, img_width, img_height, 0], alpha=0.5)
- for i, (label, group) in enumerate(grouped):
- coords = group[['epicenter_y', 'epicenter_x']].to_numpy()
- if len(coords) < 2:
- continue
- dist_matrix = distance_matrix(coords, coords)
- dist_matrix_nnd = dist_matrix.copy()
- np.fill_diagonal(dist_matrix_nnd, np.inf)
- nnd = np.min(dist_matrix_nnd, axis=1)
- dist_matrix_furthest = dist_matrix.copy()
- np.fill_diagonal(dist_matrix_furthest, -np.inf)
- furthest = np.max(dist_matrix_furthest, axis=1)
- all_nnd.extend(nnd)
- all_furthest.extend(furthest)
- area = (np.max(coords[:, 0]) - np.min(coords[:, 0])) * (np.max(coords[:, 1]) - np.min(coords[:, 1]))
- if (area_threshold_min is None or area >= area_threshold_min) and (area_threshold_max is None or area <= area_threshold_max):
- mst = minimum_spanning_tree(dist_matrix).toarray()
- for a in range(len(coords)):
- for b in range(len(coords)):
- if mst[a, b] > 0:
- plt.plot([coords[a, 0], coords[b, 0]], [coords[a, 1], coords[b, 1]],
- color=cmap(i), linewidth=0.6, alpha=0.5)
- plt.scatter(coords[:, 0], coords[:, 1], s=15, label=f"Cluster {label} (area={area:.1f})",
- color=cmap(i), alpha=0.8)
- # print(f"Cluster {label} → Area: {area:.2f}, Mean NND: {np.mean(nnd):.2f}, Mean Furthest: {np.mean(furthest):.2f}")
- # plt.title("Filtered Clusters by Area Range")
- plt.xlabel("epicenter_x")
- plt.ylabel("epicenter_y")
- plt.gca().set_aspect('equal')
- # plt.grid(True, linestyle='--', alpha=0.3)
- # plt.legend()
- plt.tight_layout()
- plt.show()
- else:
- print("🌐 Computing Global Nearest-Neighbor and Furthest Distances...")
- coords = df[['epicenter_y', 'epicenter_x']].to_numpy()
- dist_matrix = distance_matrix(coords, coords)
- dist_matrix_nnd = dist_matrix.copy()
- np.fill_diagonal(dist_matrix_nnd, np.inf)
- nnd = np.min(dist_matrix_nnd, axis=1)
- dist_matrix_furthest = dist_matrix.copy()
- np.fill_diagonal(dist_matrix_furthest, -np.inf)
- furthest = np.max(dist_matrix_furthest, axis=1)
- nearest_indices = np.argmin(dist_matrix_nnd, axis=1)
- nearest_coords = coords[nearest_indices]
- all_nnd = nnd
- all_furthest = furthest
- plt.figure(figsize=(8, 8))
- if background_img_path:
- img = imread(background_img_path)
- img_height, img_width = img.shape[0], img.shape[1]
- plt.imshow(img, extent=[0, img_width, img_height, 0], alpha=0.5)
- plt.scatter(coords[:, 0], coords[:, 1], s=10, color='black', label='Chromatophores')
- for i in range(len(coords)):
- plt.plot([coords[i, 0], nearest_coords[i, 0]], [coords[i, 1], nearest_coords[i, 1]],
- color='lightgray', linewidth=0.5)
- # plt.title("Chromatophore Epicenters and Nearest Neighbors (Global)")
- plt.xlabel("epicenter_x")
- plt.ylabel("epicenter_y")
- plt.gca().set_aspect('equal')
- # plt.grid(True, linestyle='--', alpha=0.3)
- # plt.legend()
- plt.tight_layout()
- plt.show()
- all_nnd = np.array(all_nnd)
- global_nnd = np.mean(all_nnd)
- std_nnd = np.std(all_nnd)
- global_furthest = np.mean(all_furthest)
- std_furthest = np.std(all_furthest)
- print(f"✅ Mean NND ± SD: {global_nnd:.3f} ± {std_nnd:.3f} pixels")
- print(f"📏 Mean Furthest Distance ± SD: {global_furthest:.3f} ± {std_furthest:.3f} pixels")
- return global_nnd, all_nnd, all_furthest
- # === Area distribution using convex hull ===
- def plot_cluster_area_distribution():
- areas = []
- for label, chrom_ids in label_to_chroms.items():
- coords = np.array([coords_lookup[cid] for cid in chrom_ids if cid in coords_lookup])
- if len(coords) < 3:
- continue
- try:
- hull = ConvexHull(coords)
- area = hull.volume # for 2D, volume is the area enclosed
- areas.append(area)
- except:
- continue
- areas = np.array(areas)
- plt.figure(figsize=(8, 5))
- plt.hist(areas, bins=30, color='skyblue', edgecolor='black')
- # plt.title("Distribution of Cluster Convex Hull Areas (pixels²)")
- plt.xlabel("area [pixels²]")
- plt.ylabel("number of clusters")
- # plt.grid(True, linestyle='--', alpha=0.5)
- plt.tight_layout()
- plt.show()
- threshold_area = np.percentile(areas, 90)
- print(f"📐 90% of clusters have a convex hull area smaller than {threshold_area:.2f} px²")
- print(f"📈 Total clusters: {len(areas)}")
- print(f"📏 Mean area: {np.mean(areas):.2f} px²")
- print(f"🔺 Median area: {np.median(areas):.2f} px²")
- print(f"🔽 Min area: {np.min(areas):.2f} px²")
- print(f"🔼 Max area: {np.max(areas):.2f} px²")
- # === Density and Correlation Plots ===
- def plot_density_relationships():
- areas, chrom_counts, densities = [], [], []
- for label, chrom_ids in label_to_chroms.items():
- coords = np.array([coords_lookup[cid] for cid in chrom_ids if cid in coords_lookup])
- if len(coords) < 3:
- continue
- try:
- hull = ConvexHull(coords)
- area = hull.volume
- except:
- continue
- n_chroms = len(coords)
- density = n_chroms / area if area > 0 else 0
- areas.append(area)
- chrom_counts.append(n_chroms)
- densities.append(density)
- areas = np.array(areas)
- chrom_counts = np.array(chrom_counts)
- densities = np.array(densities)
- fig, axs = plt.subplots(1, 2, figsize=(12, 5))
- axs[0].scatter(areas, chrom_counts, c=densities, cmap='viridis', s=10)
- axs[0].set_xlabel("convex hull area [px²]")
- axs[0].set_ylabel("# chromatophores")
- axs[0].set_title("Chrom Count vs Area")
- axs[0].grid(True)
- axs[1].scatter(areas, densities, color='steelblue', s=10)
- axs[1].set_xscale('log')
- axs[1].set_yscale('log')
- axs[1].set_xlabel("convex hull area [px², log scale]")
- axs[1].set_ylabel("density [chroms/px², log scale]")
- axs[1].set_title("Density vs Area (Log-Log)")
- axs[1].grid(True, which='both', linestyle='--', alpha=0.5)
- plt.tight_layout()
- plt.show()
- print("📊 Correlation coefficients:")
- print(f"Chrom count ↔ Area: r = {pearsonr(chrom_counts, areas)[0]:.2f}")
- print(f"Area ↔ Density: r = {pearsonr(areas, densities)[0]:.2f}")
- print(f"Chrom count ↔ Density: r = {pearsonr(chrom_counts, densities)[0]:.2f}")
- # csv_path = '/gpfs/laur/data/renardm/output_dir/holiday_runs/240823/GLC-06560-headfix/240823-008/ics_chunk_0_chrom_all.csv'
- csv_path = '/your/path/ics_chunk_1_chrom_all.csv'
- load_data(csv_path)
- # list_clusters()
- plot_clusters_by_labels([42, 400, 246, 150, 650, 660, 227, 228, 211, 304 ], background_img_path='/your/path/240823-024_chunk_1_frame_1.png')
- plot_clusters_by_size(9, background_img_path='/your/path/240823-024_chunk_1_frame_1.png')
- #compute_and_plot_global_nnd(csv_path)
- plot_cluster_area_distribution()
- plot_density_relationships()
mu_structure.py at commit a1fe9de, under BSD-3-Clause · at the source
Overview
- Max Planck Institute for Brain Research, Frankfurt, Germany
- IDG/McGovern Institute for Brain Research, Peking University, Beijing, China
- Peking-Tsinghua Center for Life Sciences, Academy for Advanced Interdisciplinary Studies, Peking University, Beijing, China
- State Key Laboratory of Membrane Biology, School of Life Sciences, Peking University, Beijing, China
- Center for Quantitative Biology, Academy for Advanced Interdisciplinary Studies, Peking University, Beijing, China
Abstract
Cephalopod chromatophores are skin pigment organs enabling rapid, neurally controlled camouflage, yet the organization of their motor control remains poorly understood. Previously, we developed CHROMAS, a computer-vision pipeline for high-resolution analysis of chromatophore dynamics (Ukrow et al., 2025). Here, we apply it to investigate motor control and innervation in Euprymna berryi and Sepia officinalis. By segmenting chromatophores into radial slices and analyzing anisotropic deformations, we used dimensionality reduction and source separation to estimate the number and spatial influence of motor neurons controlling individual chromatophores and groups thereof. On average, four independent components were detected per chromatophore, each forming contiguous petal-shaped domains. Clustering thousands of components revealed motor units spanning multiple chromatophores, most involving fewer than 14, with diverse geometries ranging from compact local groups to elongated or fragmented structures; chromatophore pairs were co-innervated more often than expected by chance. Expansion was consistently faster and more stereotyped than relaxation, consistent with active contraction and passive recoil. These results show that chromatophores are not uniform pixels but contrast elements fractionable into sub-territories coordinated across neighbors. This geometry of neural control enables the generation of ‘virtual chromatophores’, that is, functional groupings of adjacent chromatophore territories that act as single units, as well as that of noise in the distribution of pixel shapes.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 21 matches between paragraphs and lines of code.
gitlab.mpcdf.mpg.de/mpibr/laur/cuttlefish/chromas
a1fe9de3c592f020697ba60ce328671cf1c1cd8d, 24 November 2025Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
72 files
- chromas/
__init__.py , Python, 1 line - chromas/
chromas.py , Python, 981 lines - chromas/
source/ , Python, 1 line__init__.py - chromas/
source/ , Python, 213 linesanalysis/ areas_clustering.py - chromas/
source/ , Python, 21 lines, 1 matchanalysis/ clustering_utils.py - chromas/
source/ , Python, 350 linesanalysis/ ica_slice_clustering.py - chromas/
source/ , Python, 263 linesanalysis/ sliceareas_clustering.py - chromas/
source/ , Python, 1 lineareas/ __init__.py - chromas/
source/ , Python, 388 linesareas/ areas.py - chromas/
source/ , Python, 1 linechunking/ __init__.py - chromas/
source/ , Python, 381 lineschunking/ chunking.py - chromas/
source/ , Python, 49 linesica/ ica_areas.py - chromas/
source/ , Python, 103 linesica/ ica_slices.py - chromas/
source/ , Python, 56 lines, 1 matchica/ ica_utils.py - chromas/
source/ , Python, 17 linesmoving_least_squares/ setup.py - chromas/
source/ , Python, 1 lineregistration/ __init__.py - chromas/
source/ , C, 4,429 linesregistration/ mls.c - chromas/
source/ , Python, 901 linesregistration/ registration.py - chromas/
source/ , Python, 1 linesegmentation/ __init__.py - chromas/
source/ , Python, 111 linessegmentation/ lookup_utils.py - chromas/
source/ , Python, 42 linessegmentation/ neuralnet_utils.py - chromas/
source/ , Python, 229 linessegmentation/ segmentation_lookup.py - chromas/
source/ , Python, 363 lines, 1 matchsegmentation/ segmentation_neuralnet.p y - chromas/
source/ , Python, 184 linessegmentation/ segmentation_neuralnet_g pu.py - chromas/
source/ , Python, 228 linessegmentation/ segmentation_randomfores t.py - chromas/
source/ , Python, 1 lineslicing/ __init__.py - chromas/
source/ , Python, 602 linesslicing/ generate.py - chromas/
source/ , Python, 283 linesslicing/ interactive_motion_marke r_tuner.py - chromas/
source/ , Python, 51 linesslicing/ motion_marker_utils.py - chromas/
source/ , Python, 694 linesslicing/ slicing.py - chromas/
source/ , Python, 1,091 linesstitching/ image_point_selector.py - chromas/
source/ , Python, 267 linesstitching/ libreg.py - chromas/
source/ , Python, 68 linesstitching/ minimal_removal.py - chromas/
source/ , Python, 700 linesstitching/ stitching.py - chromas/
source/ , Python, 276 linessuperstitching/ superstitching.py - chromas/
source/ , Python, 133 linestools/ generate_segmentation_te st_video_letters.py - chromas/
source/ , Python, 15 linestools/ print_dataset.py - chromas/
source/ , Python, 87 linestools/ select_chroms.py - chromas/
source/ , Python, 68 linestools/ status_dataset.py - chromas/
source/ , Python, 1 linetraining/ __init__.py - chromas/
source/ , Python, 788 lines, 2 matchestraining/ neural_net_training.py - chromas/
source/ , Python, 83 linestraining/ random_forest_training.p y - chromas/
source/ , Python, 273 linestraining/ random_forest_utils.py - chromas/
source/ , Python, 92 linestraining/ show_training_image.py - chromas/
source/ , Python, 503 lines, 1 matchtraining/ unet_training.py - chromas/
source/ , Python, 165 linestraining/ unet_utils.py - chromas/
source/ , Python, 281 linestraining/ unify_training_data.py - chromas/
source/ , Python, 1 lineutils/ __init__.py - chromas/
source/ , Python, 142 linesutils/ decorators.py - chromas/
source/ , Python, 82 linesutils/ image.py - chromas/
source/ , Python, 136 linesutils/ plot.py - chromas/
source/ , Python, 732 linesutils/ utils.py - chromas/
source/ , Python, 421 linesutils/ video.py - chromas/
tests/ , Python, 1 line__init__.py - chromas/
tests/ , Python, 11 linestest_cli/ test_cli.py - chromas/
tests/ , Python, 43 linestest_installation/ test_gpu_installation.py - chromas/
tests/ , Python, 56 linestest_installation/ test_installation.py - docs/
_build/ , JavaScript, 149 lineshtml/ _static/ doctools.js - docs/
_build/ , JavaScript, 13 lineshtml/ _static/ documentation_options.js - docs/
_build/ , JavaScript, 192 lineshtml/ _static/ language_data.js - docs/
_build/ , JavaScript, 632 lineshtml/ _static/ searchtools.js - docs/
_build/ , JavaScript, 154 lineshtml/ _static/ sphinx_highlight.js - docs/
_build/ , JavaScript, 1 linehtml/ searchindex.js - docs/
conf.py , Python, 100 lines - motor_units/
anisotropy.py , Python, 634 lines, 4 matches - motor_units/
cluster_size.py , Python, 115 lines - motor_units/
distances.py , Python, 143 lines, 2 matches - motor_units/
exp_vs_contr.py , Python, 918 lines, 3 matches - motor_units/
mu_cooccurence.py , Python, 158 lines, 2 matches - motor_units/
mu_structure.py , Python, 408 lines, 4 matches - LICENSE, License, 28 lines
- README.md, Text, 427 lines
The paper's code and data availability statement is in the Data section.
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:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 70 scripts, each with its path and the digest of its content;
- 21 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- 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 image analysis software CHROMAS is distributed via the pypi package index (https://
The following dataset was generated:
Renard MDM, Ukrow J, Elmaleh M, Evans DA, Wu Y, Liang X, Laurent G. 2026. Disentangling cephalopod chromatophores motor units with computer vision. Edmond.
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, 27 September 2026: the first record
Recorded: type, language, journal, volume, pages, dates, 7 authors, 1 keyword, 4 MeSH terms, 2 funders, 36 references, 6 RRIDs.
Cite
This paper
Renard, M. D., Ukrow, J., Elmaleh, M., Evans, D. A., Wu, Y., Liang, X., & Laurent, G. (2026). Disentangling cephalopod chromatophores motor units with computer vision. eLife, 15, RP110074. https://
BibTeX
@article{renard2026disen
author = {Renard, Mathieu DM and Ukrow, Johann and Elmaleh, Margot and Evans, Dominic A and Wu, Yifan and Liang, Xitong and Laurent, Gilles},
title = {{Disentangling cephalopod chromatophores motor units with computer vision}},
journal = {eLife},
year = {2026},
month = jul,
volume = {15},
pages = {RP110074},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/
url = {https://
pmid = {42418335},
pmcid = {PMC13345625}
}
RIS
TY - JOUR
AU - Renard, Mathieu DM
AU - Ukrow, Johann
AU - Elmaleh, Margot
AU - Evans, Dominic A
AU - Wu, Yifan
AU - Liang, Xitong
AU - Laurent, Gilles
TI - Disentangling cephalopod chromatophores motor units with computer vision
T2 - eLife
J2 - eLife
PY - 2026
DA - 2026/
VL - 15
SP - RP110074
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
DO - 10.7554/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.7554/
"type": "article-journal",
"title": "Disentangling cephalopod chromatophores motor units with computer vision",
"container-title": "eLife",
"author": [
{
"family": "Renard",
"given": "Mathieu DM"
},
{
"family": "Ukrow",
"given": "Johann"
},
{
"family": "Elmaleh",
"given": "Margot"
},
{
"family": "Evans",
"given": "Dominic A"
},
{
"family": "Wu",
"given": "Yifan"
},
{
"family": "Liang",
"given": "Xitong"
},
{
"family": "Laurent",
"given": "Gilles"
}
],
"container-title-short":
"volume": "15",
"page": "RP110074",
"DOI": "10.7554/
"PMID": "42418335",
"PMCID": "PMC13345625",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
8
]
]
}
}
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-02232-0 [code]
- Entorhinal cortex represents task-relevant remote locations independently of CA1.Journal: Nature neuroscienceIn common: xarray, NetworkX, OpenCV, 8 other tools
- [2] doi:10.7554/elife.107393 [code]
- Chromosome-scale genome assembly of the European common cuttlefish &
lt;i& gt;Sepia officinalis& lt;/ i& gt;. Journal: eLifeIn common: pandas, SciPy, Matplotlib, 1 other tool, other, 2 references, author Gilles Laurent - [3] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: NetworkX, Plotly, OpenCV, 8 other tools
- [4] 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: NetworkX, Plotly, OpenCV, 8 other tools
- [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: NetworkX, Plotly, OpenCV, 8 other tools
- [6] doi:10.3389/fnsys.2026.1822122 [code]
- Convergence-divergence circuits for multimodal integration of innate and learned opponent valences.Journal: Frontiers in systems neuroscienceIn common: NetworkX, Plotly, scikit-image, 7 other tools, 1 reference
- [7] doi:10.1371/journal.pcbi.1013441 [code]
- Large vision model framework for automated C. elegans analysis: From static morphometry to dynamic neural activity.Journal: PLoS computational biologyIn common: NetworkX, OpenCV, scikit-image, 6 other tools, 2 references
- [8] doi: [code]
- Naturalistic behavior and self-generated neural activity predictive of self-correctionJournal: bioRxiv : the preprint server for biologyIn common: xarray, NetworkX, OpenCV, 7 other tools
- [9] doi:10.1038/s41467-026-76045-x [code]
- A manufacturability-inform
ed topology framework for AI-guided design of fibrous network materials. Journal: Nature communicationsIn common: NetworkX, OpenCV, Pillow, 6 other tools, 2 references - [10] doi:10.1038/s41467-026-72057-9 [code]
- Sex-specific behavioral feedback modulates sensorimotor processing and drives flexible social behavior.Journal: Nature communicationsIn common: xarray, OpenCV, scikit-image, 7 other tools
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: 1 repository of the authors' code, each at its verified commit and with its license, 70 scripts, and 21 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:e28d1b43724a99ea…
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
[.
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.
