Chromosome-scale genome assembly of the European common cuttlefish <i>Sepia officinalis</i>.
The 40 matches · 7 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
- [1] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ analysis/genome_alignments/breakpoints/breakpoint_analysis.ipynb, lines 81–128 · score 0.95 · DToL_31, DToL_41, DToL_44, DToL_5, MPIBR_2, MPIBR_40
- [2] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ analysis/genome_alignments/wga/assembly_comparison.R, lines 176–234 · score 0.94 · DToL_31, DToL_41, DToL_44, DToL_5, MPIBR_2, MPIBR_40
- [3] § Materials and methods › Nuclear genome annotation ↔ analysis/gene_families/cafe5/large_families_heatmap.R, lines 50–126 · score 0.92 · Pecten maximus, Nautilus pompilius, Octopus vulgaris, Octopus bimaculoides, Doryteuthis pealeii, Euprymna scolopes
- [4] § Materials and methods › Nuclear genome annotation ↔ annotation/task_braker_softmasked.sh, the whole file · a weak match · score 0.91 · addUTR, prot_seq, metazoa_odb10, BUSCO lineage, softmasked, BRAKER3
- [5] § Results › Gene modeling and annotation ↔ analysis/gene_families/cafe5/large_families_heatmap.R, lines 50–126 · score 0.89 · Pecten maximus, Nautilus pompilius, Octopus vulgaris, Octopus bimaculoides, Doryteuthis pealeii, Euprymna scolopes
- [6] § Materials and methods › Bulk RNA-seq analysis ↔ alignment/task_star_rna.sh, lines 26–89 · score 0.85 · RemoveNoncanonical, SortedByCoordinate, outFilterIntronMotifs, outSAMmultNmax, outSAMtype, STAR
- [7] § Materials and methods › Bulk RNA-seq analysis ↔ analysis/rna_seq/bulkRNA_Deseq2_braker.Rmd, lines 200–273 · score 0.85 · log2 fold change, DESeq2, target tissue, Wald, apeglm, binary
- [8] § Materials and methods › Gene family expansion analysis ↔ analysis/gene_families/cafe5/gene_repeat_overlap.py, lines 271–346 · score 0.84 · avoid double, overlap fraction, CDS length, repeat intervals, gene families, intersect
- [9] § Materials and methods › Bulk RNA-seq analysis ↔ alignment/task_star_scell.sh, lines 23–91 · score 0.84 · RemoveNoncanonical, SortedByCoordinate, outFilterIntronMotifs, outSAMmultNmax, outSAMtype, STAR
- [10] § Materials and methods › Coverage analysis ↔ analysis/genome_alignments/breakpoints/breakpoint_analysis.ipynb, lines 1143–1258 · score 0.81 · breakpoint rate, intra scaffold, trans pair, DToL, contact rates, empirical
- [11] § Materials and methods › Nuclear genome assembly ↔ assembly/run_scaffolding.sh, lines 44–101 · score 0.80 · JBAT, telo, motif, tool, YAHS, mapping
- [12] § Materials and methods › Coverage analysis ↔ analysis/genome_alignments/breakpoints/count_hic_trans_background.sh, lines 1–49 · score 0.78 · intra scaffold pairs, trans pair, scaffold lengths, product, background, Mb2
- [13] § Materials and methods › Bulk RNA-seq analysis ↔ analysis/rna_seq/parse_interproscan_go.R, lines 328–409 · score 0.76 · DESeq2, clusterProfiler, DE genes, GO terms, enricher, background
- [14] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ analysis/genome_alignments/breakpoints/extract_hic_trans_pairs.sh, lines 1–68 · score 0.76 · breakpoint scaffold pairs, scaffold_40, scaffold length, pairtools, BP1, BP3
- [15] § Materials and methods › Bulk RNA-seq analysis ↔ analysis/gene_families/gene_family_expression_analysis.Rmd, lines 414–472 · score 0.73 · clusterProfiler, GO enrichment, GO annotations, GO terms, enricher, FDR
- [16] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ analysis/genome_alignments/breakpoints/count_hic_trans_background.sh, lines 1–49 · score 0.73 · intra chromosomal contacts, intra scaffold pairs, scaffold length, background, pairtools, HiC
- [17] § Materials and methods › Bulk RNA-seq analysis ↔ alignment/task_star_count_braker.sh, lines 44–93 · score 0.72 · FeatureCounts, countReadPairs, gene_id, STAR, exon
- [18] § Materials and methods › Bulk RNA-seq analysis ↔ analysis/rna_seq/parse_interproscan_go.R, lines 159–223 · score 0.72 · reduce noise, unique GO terms, generic, union, collapsed, enrichment
- [19] § Materials and methods › Coverage analysis ↔ analysis/genome_alignments/breakpoints/breakpoint_analysis.ipynb, lines 131–240 · score 0.71 · mapping quality threshold, count_coverage, pysam, querying, depth, alignments
- [20] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ analysis/genome_alignments/breakpoints/run_repeatmasker_junctions.sh, lines 67–144 · score 0.71 · breakpoint junctions, junction window, scaffold_40, scaffold ends, BP2, RepeatMasker
- [21] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ analysis/genome_alignments/breakpoints/breakpoint_analysis.ipynb, lines 1143–1258 · score 0.69 · intra scaffold, control scaffolds, background pairs, DToL, Contact rates, empirical
- [22] § Materials and methods › Gene family expansion analysis ↔ analysis/gene_families/cafe5/run_cafe5.sh, lines 45–100 · score 0.66 · Gamma models, log likelihood, Gene family, lambda, lowest, CAFE5
- [23] § Materials and methods › Bulk RNA-seq analysis ↔ analysis/rna_seq/parse_interproscan_go.R, lines 328–409 · score 0.64 · DESeq2, clusterProfiler, GO term, enricher, tissue, RNA
- [24] § Materials and methods › Bulk RNA-seq analysis ↔ analysis/gene_families/gene_family_expression_analysis.Rmd, lines 414–472 · score 0.64 · clusterProfiler, GO enrichment, GO term, enricher, gene family, VST
- [25] § Materials and methods › Coverage analysis ↔ analysis/genome_alignments/breakpoints/extract_hic_trans_pairs.sh, lines 1–68 · score 0.64 · uninvolved scaffold, trans pairs, UU, pairtools, breakpoints, Hi
- [26] § Materials and methods › Gene family expansion analysis ↔ analysis/gene_families/cafe5/large_families_heatmap.R, lines 1–48 · score 0.63 · OrthoFinder, species tree, ape, ultrametric, rooted, Orthogroups
- [27] § Results › Gene modeling and annotation ↔ analysis/rna_seq/emapper_interproscan_LUT.R, the whole file · a weak match · score 0.63 · officinalis proteome, eggNOG, InterProScan, mapper, transcriptomic, filters
- [28] § Materials and methods › Bulk RNA-seq analysis ↔ alignment/task_countreads.sh, the whole file · a weak match · score 0.61 · featureCounts, countReadPairs, Subread, GTF, exon, id
- [29] § Materials and methods › Short-read RNA library preparation and sequencing ↔ analysis/rna_seq/bulkRNA_Deseq2_braker.Rmd, lines 48–65 · score 0.60 · NextSeq2000, library prep, mRNA, RNA seq, stranded, Illumina
- [30] § Materials and methods › Gene family expansion analysis ↔ analysis/gene_families/orthofinder/orthofinder_to_cafe.R, the whole file · a weak match · score 0.60 · OrthoFinder, species tree, ultrametric, rooted, proteomes, Orthogroups
- [31] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ analysis/genome_alignments/breakpoints/breakpoint_analysis.ipynb, lines 739–748 · score 0.59 · Hi Fi, DToL, kb, boundary, windows, breakpoint
- [32] § Materials and methods › Gene family expansion analysis ↔ analysis/gene_families/gene_family_expression_analysis.Rmd, lines 722–755 · score 0.57 · birth death model, extreme copy, Gene families, likelihood, orthogroups, species
- [33] § Materials and methods › Nuclear genome annotation ↔ analysis/rna_seq/emapper_interproscan_LUT.R, the whole file · a weak match · score 0.56 · eggNOG, InterProScan, lookup, mapper, GO, officinalis
- [34] § Materials and methods › Coverage analysis ↔ analysis/genome_alignments/breakpoints/breakpoint_analysis.ipynb, lines 1488–1527 · score 0.56 · LTR, RepeatMasker, SINE, parsed, classes, unknown
- [35] § Results › Genome size and heterozygosity ↔ assembly/run_busco.sh, the whole file · a weak match · score 0.55 · mollusca_odb12, metazoa_odb12, BUSCO, assembly, genome
- [36] § Materials and methods › Nuclear genome annotation ↔ annotation/task_repeatmasker_custom_softmask.sh, lines 47–77 · score 0.55 · RepeatModeler, xsmall, gff, softmasked, genome
- [37] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ analysis/genome_alignments/breakpoints/extract_spanning_reads.py, lines 1–37 · score 0.53 · Hi Fi, terminal, breakpoint, boundary, windows, alignments
- [38] § Materials and methods › Nuclear genome annotation ↔ analysis/genome_alignments/breakpoints/run_repeatmasker_junctions.sh, lines 146–198 · score 0.51 · repeat library, RepeatMasker, xsmall, gff, genome
- [39] § Results › Tissue-specific expression of expanded gene families ↔ analysis/rna_seq/bulkRNA_Deseq2_braker.Rmd, lines 83–117 · score 0.50 · Bulk RNA seq, DESeq2, posterior, vertical, retina, skin
- [40] § Results › Comparison with another chromosome-scale Sepia officinalis assembly ↔ assembly/run_busco.sh, the whole file · a weak match · score 0.50 · mollusca_odb12, metazoa_odb12, BUSCO, assemblies
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 1,598 lines · 67 KB · MIT · 6 matches
- # %% [markdown]
- # # Sepia officinalis Genome Assembly — Breakpoint Analysis
- # Jupyter notebook consolidating all visualisation and statistical analysis scripts for the MPIBR vs DToL assembly comparison.
- #
- # **Sections**
- # 1. HiFi spanning reads + trans HiC contacts at breakpoints
- # 2. Trans HiC contact rate statistics
- # 3. Repeat content at junction windows
- #
- # ---
- # %% [markdown]
- # ## Shared imports and plot settings
- # %%
- import os, sys, warnings
- import numpy as np
- import pandas as pd
- import matplotlib
- import matplotlib.pyplot as plt
- import matplotlib.patches as mpatches
- from matplotlib.patches import ConnectionPatch
- from matplotlib.gridspec import GridSpec, GridSpecFromSubplotSpec
- from matplotlib.ticker import MaxNLocator
- from scipy import stats
- warnings.filterwarnings("ignore")
- matplotlib.rcParams.update({
- "font.family": "sans-serif",
- "font.sans-serif": ["Arial", "Helvetica", "DejaVu Sans"],
- "font.size": 7,
- "axes.linewidth": 0.5,
- "xtick.major.width": 0.5,
- "ytick.major.width": 0.5,
- "xtick.major.size": 2,
- "ytick.major.size": 2,
- "pdf.fonttype": 42,
- })
- try:
- import pysam
- except ImportError:
- sys.exit("ERROR: pysam is required — pip install pysam")
- print("Imports OK")
- # %% [markdown]
- # ---
- # ## 1. HiFi spanning reads + trans HiC contacts
- #
- # Visualises HiFi read alignments at scaffold junction ends alongside trans HiC contact dots and arcs.
- # Each breakpoint row shows: genome-wide contact density | terminal read track | HiFi coverage.
- # Control rows show terminal read track and coverage only.
- # %% [markdown]
- # ### 1.1 Parameters
- # %%
- # ── Input paths ─────────────────────────────────────────────────────────────
- BASE = "/gpfs/scic/data/projects/CuttlefishOmics/genome/assembly/soff250801"
- WD = "/gpfs/scic/data/projects/CuttlefishOmics/sandbox/resubmission"
- SPANNING_DIR = f"{WD}/spanning_reads"
- TRANS_HIC_DIR = f"{WD}/hic_trans_pairs"
- MPIBR_BAM = f"{BASE}/bams_hifi_scaffolds/soff250801_mpibr.hic_hifi_scaffolds.bam"
- DTOL_BAM = f"{BASE}/bams_hifi_scaffolds/soff250801_sanger.hic_hifi_scaffolds.bam"
- MPIBR_FAI = (f"{BASE}/yahs/soff250801_mpibr.hic/"
- "soff250801_mpibr.hic_scaffolds_final.fa.fai")
- DTOL_FAI = (f"{BASE}/yahs/soff250801_sanger.hic/"
- "soff250801_sanger.hic_scaffolds_final.fa.fai")
- # ── Run options ──────────────────────────────────────────────────────────────
- VIEW_WINDOW = 200_000 # bp shown in terminal read / coverage tracks
- MIN_MAPQ = 10
- OUTPUT_SPANNING = "spanning_reads.pdf"
- # %% [markdown]
- # ### 1.2 Constants, breakpoint definitions, colours
- # %%
- # ── Font sizes (edit here to scale all labels together) ─────────────
- FONT = {
- "title": 10, # scaffold name on density track
- "label": 9, # axis labels (ylabel, xlabel)
- "tick": 8, # tick labels
- "annot": 7, # small annotations ("200 kb view" etc.)
- "gap": 9, # gap-column text (pair counts, spanning reads)
- "legend": 9, # figure legend
- "boundary": 7, # "scaffold end / start" rotated label
- }
- MB = 1_000_000
- COV_BIN = 1000
- DENS_BIN = 500_000
- DOT_Y = -0.7
- MAX_ARCS = 200
- BREAKPOINTS_SPANNING = [
- ("mpibr", "scaffold_40", "scaffold_44", "BP1_MPIBR_40-44", "MPIBR_40", "MPIBR_44"),
- ("dtol", "scaffold_31", "scaffold_40", "BP2_DToL_31-40", "DToL_31", "DToL_40"),
- ("dtol", "scaffold_41", "scaffold_46", "BP3_DToL_41-46", "DToL_41", "DToL_46"),
- ("dtol", "scaffold_44", "scaffold_45", "BP4_DToL_44-45", "DToL_44", "DToL_45"),
- ]
- # Correct single scaffold from the other assembly for each breakpoint
- # (the chromosome that the broken assembly should have kept intact)
- CORRECT_SCAFFOLDS = [
- ("dtol", "scaffold_5", "BP1", "DToL_5"), # BP1: MPIBR split → DToL kept intact
- ("mpibr", "scaffold_2", "BP2", "MPIBR_2"), # BP2: DToL split → MPIBR kept intact
- ("mpibr", "scaffold_6", "BP3", "MPIBR_6"), # BP3: DToL split → MPIBR kept intact
- ("mpibr", "scaffold_7", "BP4", "MPIBR_7"), # BP4: DToL split → MPIBR kept intact
- ]
- COLOURS_SPANNING = {
- "mpibr": "#2166AC",
- "dtol": "#e65c00",
- "spanning": "#2CA02C",
- "background": "#CCCCCC",
- "hic_contact": "#9467BD",
- "boundary": "#444444",
- "coverage": {"mpibr": "#AEC6E8", "dtol": "#F4A582"},
- "density": {"mpibr": "#6BAED6", "dtol": "#FC8D59"},
- }
- # %% [markdown]
- # ### 1.3 Data-loading helpers
- # %%
- def load_fai(fai_path):
- lengths = {}
- with open(fai_path) as fh:
- for line in fh:
- parts = line.strip().split("\t")
- if len(parts) >= 2:
- lengths[parts[0]] = int(parts[1])
- return lengths
- def find_trans_tsv(trans_dir, label, assembly):
- return os.path.join(trans_dir, f"{label}_{assembly}_trans.tsv")
- def find_ctrl_trans_tsv(trans_dir, assembly, ctrl_scaf):
- ctrl_n = ctrl_scaf.replace("scaffold_", "")
- prefix = f"Ctrl_{assembly.upper()}_{ctrl_n}-"
- for fname in os.listdir(trans_dir):
- if fname.startswith(prefix) and fname.endswith("_trans.tsv"):
- return os.path.join(trans_dir, fname)
- return ""
- def load_trans_pairs(tsv_path):
- if not tsv_path or not os.path.isfile(tsv_path):
- return pd.DataFrame()
- df = pd.read_csv(tsv_path, sep="\t")
- return df if not df.empty else pd.DataFrame()
- def compute_density(positions, scaffold_len, bin_size=DENS_BIN):
- n_bins = max(1, scaffold_len // bin_size)
- counts, edges = np.histogram(positions, bins=n_bins, range=(0, scaffold_len))
- mids = (edges[:-1] + edges[1:]) / 2
- return mids, counts.astype(float)
- def normalised_contact_rate(n_pairs, len_A, len_B):
- la, lb = len_A / MB, len_B / MB
- return 0.0 if la == 0 or lb == 0 else n_pairs / (la * lb)
- def get_reads_in_window(bam_path, scaffold, win_start, win_end,
- spanning_names, min_mapq=10):
- reads = []
- try:
- bam = pysam.AlignmentFile(bam_path, "rb")
- for read in bam.fetch(scaffold, win_start, win_end):
- if read.is_unmapped or read.is_secondary:
- continue
- if read.mapping_quality < min_mapq:
- continue
- reads.append({
- "read_name": read.query_name,
- "start": max(read.reference_start, win_start),
- "end": min(read.reference_end, win_end),
- "strand": "-" if read.is_reverse else "+",
- "is_spanning": read.query_name in spanning_names,
- })
- bam.close()
- except (ValueError, KeyError) as e:
- print(f" WARNING: fetch failed {scaffold}:{win_start}-{win_end}: {e}")
- return reads
- def get_coverage(bam_path, scaffold, win_start, win_end,
- min_mapq=10, bin_size=COV_BIN):
- try:
- bam = pysam.AlignmentFile(bam_path, "rb")
- cov_arrays = bam.count_coverage(
- scaffold, win_start, win_end,
- quality_threshold=0,
- read_callback=lambda r: (
- not r.is_unmapped and not r.is_secondary and
- r.mapping_quality >= min_mapq
- )
- )
- bam.close()
- except (ValueError, KeyError) as e:
- print(f" WARNING: coverage failed {scaffold}:{win_start}-{win_end}: {e}")
- n = (win_end - win_start) // bin_size + 1
- return np.zeros(n), np.zeros(n)
- depth = sum(np.array(a) for a in cov_arrays)
- n_bins = len(depth) // bin_size
- if n_bins == 0:
- return np.array([len(depth) / 2]), np.array([depth.mean()])
- binned = depth[:n_bins * bin_size].reshape(n_bins, bin_size).mean(axis=1)
- mids = np.arange(n_bins) * bin_size + bin_size / 2
- return mids, binned
- def stack_reads(reads):
- reads_sorted = sorted(reads, key=lambda r: (not r["is_spanning"], r["start"]))
- track_ends = []
- for read in reads_sorted:
- placed = False
- for i, end in enumerate(track_ends):
- if read["start"] > end + 500:
- track_ends[i] = read["end"]
- read["track"] = i
- placed = True
- break
- if not placed:
- read["track"] = len(track_ends)
- track_ends.append(read["end"])
- return reads_sorted
- # %% [markdown]
- # ### 1.4 Drawing helpers
- # %%
- def draw_density_track(ax, mids, counts, scaffold_len, assembly,
- global_ymax, title, view_win, boundary_x,
- show_boundary_side, view_win_other=None):
- colour = COLOURS_SPANNING["density"][assembly]
- line_c = COLOURS_SPANNING[assembly]
- ax.fill_between(mids / MB, 0, counts,
- color=colour, alpha=0.7, linewidth=0, step="mid", zorder=1)
- ax.step(mids / MB, counts, color=line_c, linewidth=0.5, where="mid", zorder=2)
- # Other-end window (pale gold, drawn first)
- if view_win_other is not None:
- vw2_start, vw2_end = view_win_other
- ax.axvspan(vw2_start / MB, vw2_end / MB, color="gold", alpha=0.4, zorder=3, linewidth=0)
- inner_edge2 = vw2_end / MB if vw2_start == 0 else vw2_start / MB
- ax.axvline(inner_edge2, color="goldenrod", linewidth=0.8, linestyle="-", zorder=6)
- mid_vw2 = (vw2_start + vw2_end) / 2 / MB
- # ax.annotate("other\nend", xy=(mid_vw2, global_ymax * 1.05),
- # xycoords="data", ha="center", va="bottom",
- # fontsize=FONT["annot"], color="goldenrod", zorder=7, annotation_clip=False)
- # Junction-facing window (bright gold)
- vw_start, vw_end = view_win
- ax.axvspan(vw_start / MB, vw_end / MB, color="gold", alpha=0.9, zorder=3, linewidth=0)
- inner_edge = vw_start / MB if boundary_x > 0 else vw_end / MB
- ax.axvline(inner_edge, color="goldenrod", linewidth=1.2, linestyle="-", zorder=6)
- mid_vw = (vw_start + vw_end) / 2 / MB
- # ax.annotate("junction\nend", xy=(mid_vw, global_ymax * 1.05),
- # xycoords="data", ha="center", va="bottom",
- # fontsize=FONT["annot"], color="goldenrod", zorder=7, annotation_clip=False)
- ax.annotate("200 kb\nview", xy=(mid_vw, global_ymax * 1.05),
- xycoords="data", ha="center", va="bottom",
- fontsize=FONT["annot"], color="goldenrod", zorder=7, annotation_clip=False)
- ax.axvline(boundary_x / MB, color=COLOURS_SPANNING["boundary"],
- linewidth=1.0, linestyle="--", zorder=5)
- ax.set_xlim(0, scaffold_len / MB)
- ax.set_ylim(0, global_ymax * 1.08)
- ax.yaxis.set_major_locator(MaxNLocator(nbins=3, min_n_ticks=2, integer=True))
- ax.tick_params(axis="both", labelsize=FONT["tick"], pad=1)
- ax.tick_params(axis="x", labelbottom=True)
- ax.spines["top"].set_visible(False)
- ax.spines["right"].set_visible(False)
- ax.set_title(title, fontsize=FONT["title"], fontweight="bold",
- color=COLOURS_SPANNING[assembly], pad=3)
- def draw_read_track(ax, reads, win_start, win_end, trans_df, pos_col,
- read_height=0.6):
- n_tracks = max((r["track"] for r in reads), default=0) + 1
- for read in reads:
- x0 = read["start"] - win_start
- x1 = read["end"] - win_start
- colour = COLOURS_SPANNING["spanning"] if read["is_spanning"] else COLOURS_SPANNING["background"]
- alpha = 0.9 if read["is_spanning"] else 0.4
- zorder = 3 if read["is_spanning"] else 1
- ax.barh(read["track"], x1 - x0, left=x0, height=read_height,
- color=colour, alpha=alpha, zorder=zorder, linewidth=0)
- if not trans_df.empty:
- in_win = trans_df[
- (trans_df[pos_col] >= win_start) &
- (trans_df[pos_col] < win_end)].copy()
- if not in_win.empty:
- ax.scatter(in_win[pos_col] - win_start, [DOT_Y] * len(in_win),
- s=4, color=COLOURS_SPANNING["hic_contact"],
- alpha=0.6, zorder=4, linewidths=0, clip_on=False)
- ax.set_xlim(0, win_end - win_start)
- ax.set_ylim(DOT_Y - 0.5, n_tracks + 1)
- ax.set_yticks([])
- ax.set_xticks([])
- for spine in ["top", "right", "left", "bottom"]:
- ax.spines[spine].set_visible(False)
- return n_tracks
- def draw_hic_arcs(fig, ax_A, ax_B, trans_df, win_A, win_B):
- if trans_df.empty:
- return
- win_A_start, win_A_end = win_A
- win_B_start, win_B_end = win_B
- in_win = trans_df[
- (trans_df["pos1"] >= win_A_start) & (trans_df["pos1"] < win_A_end) &
- (trans_df["pos2"] >= win_B_start) & (trans_df["pos2"] < win_B_end)]
- if in_win.empty:
- return
- df_plot = in_win.sample(min(MAX_ARCS, len(in_win)), random_state=42)
- for _, row in df_plot.iterrows():
- con = ConnectionPatch(
- xyA=(row["pos1"] - win_A_start, DOT_Y), coordsA="data", axesA=ax_A,
- xyB=(row["pos2"] - win_B_start, DOT_Y), coordsB="data", axesB=ax_B,
- color=COLOURS_SPANNING["hic_contact"],
- linewidth=0.35, alpha=0.25, clip_on=False, zorder=5)
- fig.add_artist(con)
- def draw_coverage_track(ax, mids, depth, win_start, win_end, assembly, global_ymax):
- colour = COLOURS_SPANNING["coverage"][assembly]
- ax.fill_between(mids, 0, depth, color=colour, alpha=0.7, linewidth=0)
- ax.plot(mids, depth, color=COLOURS_SPANNING[assembly], linewidth=0.5)
- ax.set_xlim(0, win_end - win_start)
- ax.set_ylim(0, global_ymax * 1.08)
- ax.yaxis.set_major_locator(MaxNLocator(nbins=3, min_n_ticks=2))
- ax.tick_params(axis="y", labelsize=FONT["tick"], pad=1)
- ax.spines["top"].set_visible(False)
- ax.spines["right"].set_visible(False)
- tick_pos = np.linspace(0, win_end - win_start, 5)
- tick_lbl = [f"{(win_start + p) / MB:.2f}" for p in tick_pos]
- ax.set_xticks(tick_pos)
- ax.set_xticklabels(tick_lbl, fontsize=FONT["tick"])
- ax.set_xlabel("Position (Mb)", fontsize=FONT["label"], labelpad=2)
- def draw_boundary_line(axes, x):
- for ax in axes:
- ax.axvline(x, color=COLOURS_SPANNING["boundary"],
- linewidth=1.0, linestyle="--", zorder=10)
- def add_boundary_label(ax, x, side):
- text = "scaffold end" if side == "right" else "scaffold start"
- ax.text(x, ax.get_ylim()[1] * 0.92, text,
- ha=side, va="top", fontsize=FONT["boundary"],
- color=COLOURS_SPANNING["boundary"], rotation=90)
- # %% [markdown]
- # ### 1.5 Build row metadata and pre-compute coverage
- # %%
- lengths = {
- "mpibr": load_fai(MPIBR_FAI),
- "dtol": load_fai(DTOL_FAI),
- }
- bams = {"mpibr": MPIBR_BAM, "dtol": DTOL_BAM}
- ASM_DISPLAY = {"mpibr": "MPIBR", "dtol": "DToL"}
- # Correct-scaffold entries — one per breakpoint, matched in order
- controls = [
- (asm, scaf, None, f"Correct_{bp_lbl}", lbl, None, True)
- for asm, scaf, bp_lbl, lbl in CORRECT_SCAFFOLDS
- ]
- all_rows = BREAKPOINTS_SPANNING + controls
- n_rows = len(all_rows)
- print("Pre-computing HiFi coverage...")
- cov_store = {}
- def get_cov(assembly, scaffold, ws, we, bin_size=COV_BIN):
- key = (assembly, scaffold, ws, we)
- if key not in cov_store:
- mids, depth = get_coverage(bams[assembly], scaffold, ws, we,
- min_mapq=MIN_MAPQ, bin_size=bin_size)
- cov_store[key] = (mids, depth)
- print(f" {assembly} {scaffold}:{ws}-{we} mean={depth.mean():.1f}x")
- return cov_store[key]
- # ── Trans HiC diagnostic ───────────────────────────────────────────────
- _hic_dir_ok = os.path.isdir(TRANS_HIC_DIR)
- if _hic_dir_ok:
- _hic_files = [f for f in os.listdir(TRANS_HIC_DIR) if f.endswith('_trans.tsv')]
- print(f"Trans HiC dir: {TRANS_HIC_DIR} ({len(_hic_files)} TSV files)")
- else:
- print(f"WARNING: TRANS_HIC_DIR not found: {TRANS_HIC_DIR}")
- print(" → Run extract_hic_trans_pairs.sh first, then re-run this cell.")
- row_meta = []
- for entry in all_rows:
- is_ctrl = len(entry) == 7
- assembly = entry[0]
- scaf_A = entry[1]
- scaf_B = entry[2]
- label = entry[3]
- lbl_A = entry[4]
- lbl_B = entry[5]
- lens = lengths[assembly]
- len_A = lens.get(scaf_A, 0)
- end_A, end_B = "end", "start"
- spanning_names = set()
- if not is_ctrl:
- tsv = os.path.join(SPANNING_DIR, f"{label}_{assembly}.tsv")
- if os.path.isfile(tsv):
- sdf = pd.read_csv(tsv, sep="\t")
- spanning_names = set(sdf["read_name"]) if not sdf.empty else set()
- if not sdf.empty:
- a_rows = sdf[sdf["primary_scaffold"] == scaf_A]
- b_rows = sdf[sdf["primary_scaffold"] == scaf_B]
- a_supp = sdf[sdf["supp_scaffold"] == scaf_A]
- b_supp = sdf[sdf["supp_scaffold"] == scaf_B]
- if not a_rows.empty: end_A = a_rows["primary_end_label"].mode()[0]
- elif not a_supp.empty: end_A = a_supp["supp_end_label"].mode()[0]
- if not b_rows.empty: end_B = b_rows["primary_end_label"].mode()[0]
- elif not b_supp.empty: end_B = b_supp["supp_end_label"].mode()[0]
- win_A = (max(0, len_A - VIEW_WINDOW), len_A) if end_A == "end" else (0, min(VIEW_WINDOW, len_A))
- win_A_other = (0, min(VIEW_WINDOW, len_A)) if end_A == "end" else (max(0, len_A - VIEW_WINDOW), len_A)
- win_B = None
- win_B_other = None
- len_B = 0
- if not is_ctrl:
- len_B = lens.get(scaf_B, 0)
- win_B = (0, min(VIEW_WINDOW, len_B)) if end_B == "start" else (max(0, len_B - VIEW_WINDOW), len_B)
- win_B_other = (max(0, len_B - VIEW_WINDOW), len_B) if end_B == "start" else (0, min(VIEW_WINDOW, len_B))
- get_cov(assembly, scaf_A, *win_A)
- get_cov(assembly, scaf_A, *win_A_other) # both ends for all rows
- if win_B:
- get_cov(assembly, scaf_B, *win_B)
- get_cov(assembly, scaf_B, *win_B_other)
- trans_df = pd.DataFrame()
- len_partner = 0
- if is_ctrl:
- tsv_path = find_ctrl_trans_tsv(TRANS_HIC_DIR, assembly, scaf_A)
- if tsv_path:
- fname = os.path.basename(tsv_path)
- partner_n = fname.split("-")[1].split("_")[0]
- len_partner = lens.get(f"scaffold_{partner_n}", 0)
- trans_df = load_trans_pairs(tsv_path)
- if trans_df.empty:
- _expected = os.path.basename(tsv_path) if tsv_path else "(no TSV path)"
- print(f" WARNING: no trans HiC pairs loaded for {label} "
- f"-- file not found or empty: {_expected}")
- n_trans = len(trans_df)
- rate = normalised_contact_rate(n_trans, len_A, len_partner) \
- if len_partner > 0 else 0.0
- print(f" {label}: {n_trans} trans HiC pairs ({rate:.2f} pairs/Mb²)")
- else:
- tsv_path = find_trans_tsv(TRANS_HIC_DIR, label, assembly)
- len_partner = len_B
- trans_df = load_trans_pairs(tsv_path)
- if trans_df.empty:
- _expected = os.path.basename(tsv_path) if tsv_path else "(no TSV path)"
- print(f" WARNING: no trans HiC pairs loaded for {label} "
- f"-- file not found or empty: {_expected}")
- n_trans = len(trans_df)
- rate = normalised_contact_rate(n_trans, len_A, len_partner) \
- if len_partner > 0 else 0.0
- print(f" {label}: {n_trans} trans HiC pairs ({rate:.2f} pairs/Mb²)")
- row_meta.append({
- "assembly": assembly, "scaf_A": scaf_A, "scaf_B": scaf_B,
- "label": label, "lbl_A": lbl_A, "lbl_B": lbl_B,
- "is_ctrl": is_ctrl, "end_A": end_A, "end_B": end_B,
- "len_A": len_A, "len_B": len_B,
- "win_A": win_A, "win_A_other": win_A_other,
- "win_B": win_B, "win_B_other": win_B_other,
- "spanning_names": spanning_names, "trans_df": trans_df,
- "contact_rate": rate,
- })
- def global_cov_ymax(assembly):
- vals = [cov_store[k][1] for k in cov_store if k[0] == assembly]
- return float(np.quantile(np.concatenate(vals), 0.995)) if vals else 1.0
- cov_ymax = {"mpibr": global_cov_ymax("mpibr"), "dtol": global_cov_ymax("dtol")}
- dens_ymax = {"mpibr": 1.0, "dtol": 1.0}
- if os.path.isdir(TRANS_HIC_DIR):
- for asm in ["mpibr", "dtol"]:
- all_counts = []
- for meta in row_meta:
- if meta["assembly"] != asm or meta["trans_df"].empty:
- continue
- df = meta["trans_df"]
- if meta["len_A"] > 0:
- _, c = compute_density(df["pos1"].values, meta["len_A"], DENS_BIN)
- all_counts.extend(c.tolist())
- if meta["len_B"] > 0:
- _, c = compute_density(df["pos2"].values, meta["len_B"], DENS_BIN)
- all_counts.extend(c.tolist())
- if all_counts:
- dens_ymax[asm] = float(np.quantile(all_counts, 0.995))
- print("\nMetadata ready.")
- # %% [markdown]
- # ### 1.6 Generate plot
- # %%
- # ── Breakpoints figure ───────────────────────────────────────────────
- bp_meta = [m for m in row_meta if not m["is_ctrl"]]
- ctrl_meta = [m for m in row_meta if m["is_ctrl"]]
- def _draw_rows(fig, outer_gs, meta_list, bams, cov_store,
- cov_ymax, dens_ymax):
- """Draw one figure worth of rows; meta_list is bp_meta or ctrl_meta."""
- for row, meta in enumerate(meta_list):
- assembly = meta["assembly"]
- scaf_A = meta["scaf_A"]
- scaf_B = meta["scaf_B"]
- label = meta["label"]
- lbl_A = meta["lbl_A"]
- lbl_B = meta["lbl_B"]
- is_ctrl = meta["is_ctrl"]
- end_A = meta["end_A"]
- end_B = meta["end_B"]
- len_A = meta["len_A"]
- len_B = meta["len_B"]
- win_A_start, win_A_end = meta["win_A"]
- spanning_names = meta["spanning_names"]
- trans_df = meta["trans_df"]
- contact_rate = meta["contact_rate"]
- bam = bams[assembly]
- ymax_cov = cov_ymax[assembly]
- ymax_dens = dens_ymax[assembly]
- if is_ctrl:
- inner_A = GridSpecFromSubplotSpec(
- 5, 1, subplot_spec=outer_gs[row, 0],
- height_ratios=[2, 2, 1, 2, 1], hspace=0.55)
- else:
- inner_A = GridSpecFromSubplotSpec(
- 5, 1, subplot_spec=outer_gs[row, 0],
- height_ratios=[2, 2, 1, 2, 1], hspace=0.55)
- inner_B = GridSpecFromSubplotSpec(
- 5, 1, subplot_spec=outer_gs[row, 2],
- height_ratios=[2, 2, 1, 2, 1], hspace=0.55)
- if is_ctrl:
- win_A_o_start, win_A_o_end = meta["win_A_other"]
- ax_dens_A = fig.add_subplot(inner_A[0])
- ax_reads_end = fig.add_subplot(inner_A[1])
- ax_cov_end = fig.add_subplot(inner_A[2])
- ax_reads_str = fig.add_subplot(inner_A[3])
- ax_cov_str = fig.add_subplot(inner_A[4])
- # Density track (full scaffold, same as breakpoints)
- if not trans_df.empty and len_A > 0:
- mids_d, counts_d = compute_density(trans_df["pos1"].values, len_A, DENS_BIN)
- else:
- mids_d, counts_d = np.array([len_A / 2]), np.array([0.0])
- bnd_genome_A = len_A if end_A == "end" else 0
- draw_density_track(
- ax_dens_A, mids_d, counts_d, len_A, assembly, ymax_dens,
- title=lbl_A, view_win=meta["win_A"], boundary_x=bnd_genome_A,
- show_boundary_side="right" if end_A == "end" else "left",
- view_win_other=meta["win_A_other"])
- ax_dens_A.set_ylabel("Trans HiC\ncontacts", fontsize=FONT["label"], labelpad=3)
- # End terminal (scaffold end — win_A)
- reads_end = stack_reads(get_reads_in_window(
- bam, scaf_A, win_A_start, win_A_end, spanning_names, MIN_MAPQ))
- mids_end, dep_end = cov_store[(assembly, scaf_A, win_A_start, win_A_end)]
- draw_read_track(ax_reads_end, reads_end, win_A_start, win_A_end,
- trans_df, "pos1")
- draw_coverage_track(ax_cov_end, mids_end, dep_end,
- win_A_start, win_A_end, assembly, ymax_cov)
- ax_cov_end.set_ylabel("Depth (x)", fontsize=FONT["label"], labelpad=3)
- ax_cov_end.set_xlabel("")
- ax_cov_end.tick_params(axis="x", labelbottom=False)
- draw_boundary_line([ax_reads_end, ax_cov_end], win_A_end - win_A_start)
- # Start terminal (scaffold start — win_A_other)
- reads_str = stack_reads(get_reads_in_window(
- bam, scaf_A, win_A_o_start, win_A_o_end, spanning_names, MIN_MAPQ))
- mids_str, dep_str = cov_store[(assembly, scaf_A, win_A_o_start, win_A_o_end)]
- draw_read_track(ax_reads_str, reads_str, win_A_o_start, win_A_o_end,
- trans_df, "pos1")
- draw_coverage_track(ax_cov_str, mids_str, dep_str,
- win_A_o_start, win_A_o_end, assembly, ymax_cov)
- ax_cov_str.set_ylabel("Depth (x)", fontsize=FONT["label"], labelpad=3)
- draw_boundary_line([ax_reads_str, ax_cov_str], 0)
- # Turn off right panel
- ax_right = fig.add_subplot(outer_gs[row, 2])
- ax_right.set_axis_off()
- else:
- reads_A_j = stack_reads(get_reads_in_window(
- bam, scaf_A, win_A_start, win_A_end, spanning_names, MIN_MAPQ))
- mids_A_j, depth_A_j = cov_store[(assembly, scaf_A, win_A_start, win_A_end)]
- win_A_o_start, win_A_o_end = meta["win_A_other"]
- reads_A_o = stack_reads(get_reads_in_window(
- bam, scaf_A, win_A_o_start, win_A_o_end, spanning_names, MIN_MAPQ))
- mids_A_o, depth_A_o = cov_store[(assembly, scaf_A, win_A_o_start, win_A_o_end)]
- ax_dens_A = fig.add_subplot(inner_A[0])
- ax_reads_A_j = fig.add_subplot(inner_A[1])
- ax_cov_A_j = fig.add_subplot(inner_A[2])
- ax_reads_A_o = fig.add_subplot(inner_A[3])
- ax_cov_A_o = fig.add_subplot(inner_A[4])
- if not trans_df.empty and len_A > 0:
- mids_d, counts_d = compute_density(trans_df["pos1"].values, len_A, DENS_BIN)
- else:
- mids_d, counts_d = np.array([len_A / 2]), np.array([0.0])
- bnd_genome_A = len_A if end_A == "end" else 0
- draw_density_track(
- ax_dens_A, mids_d, counts_d, len_A, assembly, ymax_dens,
- title=lbl_A, view_win=meta["win_A"], boundary_x=bnd_genome_A,
- show_boundary_side="right" if end_A == "end" else "left",
- view_win_other=meta["win_A_other"])
- ax_dens_A.set_ylabel("Trans HiC\ncontacts", fontsize=FONT["label"], labelpad=3)
- draw_read_track(ax_reads_A_j, reads_A_j, win_A_start, win_A_end, trans_df, "pos1")
- draw_coverage_track(ax_cov_A_j, mids_A_j, depth_A_j,
- win_A_start, win_A_end, assembly, ymax_cov)
- ax_cov_A_j.set_ylabel("Depth (x)", fontsize=FONT["label"], labelpad=3)
- ax_cov_A_j.set_xlabel("")
- ax_cov_A_j.tick_params(axis="x", labelbottom=False)
- bnd_x_A_j = (win_A_end - win_A_start) if end_A == "end" else 0
- draw_boundary_line([ax_reads_A_j, ax_cov_A_j], bnd_x_A_j)
- end_A_other = "start" if end_A == "end" else "end"
- draw_read_track(ax_reads_A_o, reads_A_o, win_A_o_start, win_A_o_end, trans_df, "pos1")
- draw_coverage_track(ax_cov_A_o, mids_A_o, depth_A_o,
- win_A_o_start, win_A_o_end, assembly, ymax_cov)
- ax_cov_A_o.set_ylabel("Depth (x)", fontsize=FONT["label"], labelpad=3)
- bnd_x_A_o = 0 if end_A_other == "start" else (win_A_o_end - win_A_o_start)
- draw_boundary_line([ax_reads_A_o, ax_cov_A_o], bnd_x_A_o)
- ax_gap = fig.add_subplot(outer_gs[row, 1])
- ax_gap.set_axis_off()
- if is_ctrl:
- #bp_ref = meta["label"].replace("Correct_", "")
- # ax_gap.text(0.5, 0.75,
- # f"{bp_ref}\ncorrect\nassembly",
- # ha="center", va="center", fontsize=FONT["gap"],
- # color="grey", style="italic",
- # transform=ax_gap.transAxes)
- ax_gap.text(0.5, 0.45, f"{len(trans_df):,}\ntrans HiC\npairs",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["hic_contact"], fontweight="bold",
- transform=ax_gap.transAxes)
- ax_gap.text(0.5, 0.20, f"{contact_rate:.2f}\npairs/Mb\u00b2",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["hic_contact"],
- transform=ax_gap.transAxes)
- else:
- win_B_start, win_B_end = meta["win_B"]
- win_B_o_start, win_B_o_end = meta["win_B_other"]
- reads_B_j = stack_reads(get_reads_in_window(
- bam, scaf_B, win_B_start, win_B_end, spanning_names, MIN_MAPQ))
- mids_B_j, depth_B_j = cov_store[(assembly, scaf_B, win_B_start, win_B_end)]
- reads_B_o = stack_reads(get_reads_in_window(
- bam, scaf_B, win_B_o_start, win_B_o_end, spanning_names, MIN_MAPQ))
- mids_B_o, depth_B_o = cov_store[(assembly, scaf_B, win_B_o_start, win_B_o_end)]
- ax_dens_B = fig.add_subplot(inner_B[0])
- ax_reads_B_j = fig.add_subplot(inner_B[1])
- ax_cov_B_j = fig.add_subplot(inner_B[2])
- ax_reads_B_o = fig.add_subplot(inner_B[3])
- ax_cov_B_o = fig.add_subplot(inner_B[4])
- if not trans_df.empty and len_B > 0:
- mids_dB, counts_dB = compute_density(trans_df["pos2"].values, len_B, DENS_BIN)
- else:
- mids_dB, counts_dB = np.array([len_B / 2]), np.array([0.0])
- bnd_genome_B = 0 if end_B == "start" else len_B
- draw_density_track(
- ax_dens_B, mids_dB, counts_dB, len_B, assembly, ymax_dens,
- title=lbl_B, view_win=meta["win_B"], boundary_x=bnd_genome_B,
- show_boundary_side="left" if end_B == "start" else "right",
- view_win_other=meta["win_B_other"])
- ax_dens_B.set_yticklabels([])
- draw_read_track(ax_reads_B_j, reads_B_j, win_B_start, win_B_end, trans_df, "pos2")
- draw_coverage_track(ax_cov_B_j, mids_B_j, depth_B_j,
- win_B_start, win_B_end, assembly, ymax_cov)
- ax_cov_B_j.set_yticklabels([])
- ax_cov_B_j.set_xlabel("")
- ax_cov_B_j.tick_params(axis="x", labelbottom=False)
- bnd_x_B_j = 0 if end_B == "start" else (win_B_end - win_B_start)
- draw_boundary_line([ax_reads_B_j, ax_cov_B_j], bnd_x_B_j)
- end_B_other = "end" if end_B == "start" else "start"
- draw_read_track(ax_reads_B_o, reads_B_o, win_B_o_start, win_B_o_end, trans_df, "pos2")
- draw_coverage_track(ax_cov_B_o, mids_B_o, depth_B_o,
- win_B_o_start, win_B_o_end, assembly, ymax_cov)
- ax_cov_B_o.set_yticklabels([])
- bnd_x_B_o = (win_B_o_end - win_B_o_start) if end_B_other == "end" else 0
- draw_boundary_line([ax_reads_B_o, ax_cov_B_o], bnd_x_B_o)
- if not trans_df.empty:
- draw_hic_arcs(fig, ax_reads_A_j, ax_reads_B_j, trans_df,
- meta["win_A"], meta["win_B"])
- n_span = len(spanning_names)
- ax_gap.text(0.5, 0.88, f"{len(trans_df):,}\ntrans HiC\npairs",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["hic_contact"], fontweight="bold",
- transform=ax_gap.transAxes)
- ax_gap.text(0.5, 0.70, f"{contact_rate:.2f}\npairs/Mb\u00b2",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["hic_contact"], transform=ax_gap.transAxes)
- ax_gap.text(0.5, 0.50, f"{n_span}\nspanning\nread{'s' if n_span != 1 else ''}",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["spanning"], fontweight="bold",
- transform=ax_gap.transAxes)
- handles_leg = [
- mpatches.Patch(color=COLOURS_SPANNING["spanning"], alpha=0.9, label="HiFi spanning read"),
- mpatches.Patch(color=COLOURS_SPANNING["background"], alpha=0.5, label="HiFi non-spanning read"),
- mpatches.Patch(color=COLOURS_SPANNING["hic_contact"], alpha=0.7, label="Trans HiC contact"),
- mpatches.Patch(color=COLOURS_SPANNING["coverage"]["mpibr"], alpha=0.7, label="HiFi depth (MPIBR)"),
- mpatches.Patch(color=COLOURS_SPANNING["coverage"]["dtol"], alpha=0.7, label="HiFi depth (DToL)"),
- plt.Line2D([0], [0], color=COLOURS_SPANNING["boundary"], linewidth=0.9,
- linestyle="--", label="Scaffold boundary"),
- mpatches.Patch(color="yellow", alpha=0.4, label="200 kb view window"),
- ]
- # ── Breakpoints figure ────────────────────────────────────────────────
- n_bp = len(bp_meta)
- fig_bp = plt.figure(figsize=(18.0 / 2.54, 14)) #old height:(15.0 * n_bp + 2.5) / 2.54
- outer_gs_bp = GridSpec(
- n_bp, 3, figure=fig_bp,
- width_ratios=[1, 0.20, 1],
- height_ratios=[15.0] * n_bp,
- hspace=0.25, wspace=0.08,
- left=0.11, right=0.97, top=0.97, bottom=0.07,
- )
- _draw_rows(fig_bp, outer_gs_bp, bp_meta, bams, cov_store, cov_ymax, dens_ymax)
- fig_bp.legend(handles=handles_leg, loc="lower center",
- ncol=4, fontsize=FONT["legend"], frameon=False,
- bbox_to_anchor=(0.5, 0.0))
- fig_bp.savefig(OUTPUT_SPANNING, dpi=300, bbox_inches="tight")
- plt.show()
- print(f"Saved: {OUTPUT_SPANNING}")
- # ── Controls figure ───────────────────────────────────────────────────
- output_ctrl = OUTPUT_SPANNING.replace(".pdf", "_controls.pdf")
- n_ctrl = len(ctrl_meta)
- fig_ctrl = plt.figure(figsize=(4, 13))
- outer_gs_ctrl = GridSpec(
- n_ctrl, 3, figure=fig_ctrl,
- width_ratios=[1, 0.20, 0.20],
- height_ratios=[15.0] * n_ctrl,
- hspace=0.25, wspace=0.08,
- left=0.11, right=0.97, top=0.97, bottom=0.04,
- )
- _draw_rows(fig_ctrl, outer_gs_ctrl, ctrl_meta, bams, cov_store, cov_ymax, dens_ymax)
- fig_ctrl.savefig(output_ctrl, dpi=300, bbox_inches="tight")
- plt.show()
- print(f"Saved: {output_ctrl}")
- # %%
- # ── Breakpoints figure ───────────────────────────────────────────────
- bp_meta = [m for m in row_meta if not m["is_ctrl"]]
- ctrl_meta = [m for m in row_meta if m["is_ctrl"]]
- def _draw_rows(fig, outer_gs, meta_list, bams, cov_store,
- cov_ymax, dens_ymax):
- """Draw one figure worth of rows; meta_list is bp_meta or ctrl_meta."""
- for row, meta in enumerate(meta_list):
- assembly = meta["assembly"]
- scaf_A = meta["scaf_A"]
- scaf_B = meta["scaf_B"]
- label = meta["label"]
- lbl_A = meta["lbl_A"]
- lbl_B = meta["lbl_B"]
- is_ctrl = meta["is_ctrl"]
- end_A = meta["end_A"]
- end_B = meta["end_B"]
- len_A = meta["len_A"]
- len_B = meta["len_B"]
- win_A_start, win_A_end = meta["win_A"]
- spanning_names = meta["spanning_names"]
- trans_df = meta["trans_df"]
- contact_rate = meta["contact_rate"]
- bam = bams[assembly]
- ymax_cov = cov_ymax[assembly]
- ymax_dens = dens_ymax[assembly]
- if is_ctrl:
- inner_A = GridSpecFromSubplotSpec(
- 3, 1, subplot_spec=outer_gs[row, 0],
- height_ratios=[2, 3, 1], hspace=0.55)
- else:
- inner_A = GridSpecFromSubplotSpec(
- 5, 1, subplot_spec=outer_gs[row, 0],
- height_ratios=[2, 2, 1, 2, 1], hspace=0.55)
- inner_B = GridSpecFromSubplotSpec(
- 5, 1, subplot_spec=outer_gs[row, 2],
- height_ratios=[2, 2, 1, 2, 1], hspace=0.55)
- if is_ctrl:
- reads_A = stack_reads(get_reads_in_window(
- bam, scaf_A, win_A_start, win_A_end, spanning_names, MIN_MAPQ))
- mids_A, depth_A = cov_store[(assembly, scaf_A, win_A_start, win_A_end)]
- ax_dens_A = fig.add_subplot(inner_A[0])
- ax_reads_A = fig.add_subplot(inner_A[1])
- ax_cov_A = fig.add_subplot(inner_A[2])
- if not trans_df.empty and len_A > 0:
- mids_d, counts_d = compute_density(trans_df["pos1"].values, len_A, DENS_BIN)
- else:
- mids_d, counts_d = np.array([len_A / 2]), np.array([0.0])
- bnd_genome_A = len_A if end_A == "end" else 0
- draw_density_track(
- ax_dens_A, mids_d, counts_d, len_A, assembly, ymax_dens,
- title=lbl_A, view_win=meta["win_A"], boundary_x=bnd_genome_A,
- show_boundary_side="right" if end_A == "end" else "left")
- ax_dens_A.set_ylabel("Trans HiC\ncontacts", fontsize=FONT["label"], labelpad=3)
- draw_read_track(ax_reads_A, reads_A, win_A_start, win_A_end, trans_df, "pos1")
- draw_coverage_track(ax_cov_A, mids_A, depth_A,
- win_A_start, win_A_end, assembly, ymax_cov)
- ax_cov_A.set_ylabel("Depth (x)", fontsize=FONT["label"], labelpad=3)
- bnd_x_A = (win_A_end - win_A_start) if end_A == "end" else 0
- draw_boundary_line([ax_reads_A, ax_cov_A], bnd_x_A)
- else:
- reads_A_j = stack_reads(get_reads_in_window(
- bam, scaf_A, win_A_start, win_A_end, spanning_names, MIN_MAPQ))
- mids_A_j, depth_A_j = cov_store[(assembly, scaf_A, win_A_start, win_A_end)]
- win_A_o_start, win_A_o_end = meta["win_A_other"]
- reads_A_o = stack_reads(get_reads_in_window(
- bam, scaf_A, win_A_o_start, win_A_o_end, spanning_names, MIN_MAPQ))
- mids_A_o, depth_A_o = cov_store[(assembly, scaf_A, win_A_o_start, win_A_o_end)]
- ax_dens_A = fig.add_subplot(inner_A[0])
- ax_reads_A_j = fig.add_subplot(inner_A[1])
- ax_cov_A_j = fig.add_subplot(inner_A[2])
- ax_reads_A_o = fig.add_subplot(inner_A[3])
- ax_cov_A_o = fig.add_subplot(inner_A[4])
- if not trans_df.empty and len_A > 0:
- mids_d, counts_d = compute_density(trans_df["pos1"].values, len_A, DENS_BIN)
- else:
- mids_d, counts_d = np.array([len_A / 2]), np.array([0.0])
- bnd_genome_A = len_A if end_A == "end" else 0
- draw_density_track(
- ax_dens_A, mids_d, counts_d, len_A, assembly, ymax_dens,
- title=lbl_A, view_win=meta["win_A"], boundary_x=bnd_genome_A,
- show_boundary_side="right" if end_A == "end" else "left",
- view_win_other=meta["win_A_other"])
- ax_dens_A.set_ylabel("Trans HiC\ncontacts", fontsize=FONT["label"], labelpad=3)
- draw_read_track(ax_reads_A_j, reads_A_j, win_A_start, win_A_end, trans_df, "pos1")
- draw_coverage_track(ax_cov_A_j, mids_A_j, depth_A_j,
- win_A_start, win_A_end, assembly, ymax_cov)
- ax_cov_A_j.set_ylabel("Depth (x)", fontsize=FONT["label"], labelpad=3)
- ax_cov_A_j.set_xlabel("")
- ax_cov_A_j.tick_params(axis="x", labelbottom=True)
- bnd_x_A_j = (win_A_end - win_A_start) if end_A == "end" else 0
- draw_boundary_line([ax_reads_A_j, ax_cov_A_j], bnd_x_A_j)
- end_A_other = "start" if end_A == "end" else "end"
- draw_read_track(ax_reads_A_o, reads_A_o, win_A_o_start, win_A_o_end, trans_df, "pos1")
- draw_coverage_track(ax_cov_A_o, mids_A_o, depth_A_o,
- win_A_o_start, win_A_o_end, assembly, ymax_cov)
- ax_cov_A_o.set_ylabel("Depth (x)", fontsize=FONT["label"], labelpad=3)
- bnd_x_A_o = 0 if end_A_other == "start" else (win_A_o_end - win_A_o_start)
- draw_boundary_line([ax_reads_A_o, ax_cov_A_o], bnd_x_A_o)
- ax_gap = fig.add_subplot(outer_gs[row, 1])
- ax_gap.set_axis_off()
- if is_ctrl:
- ax_gap.text(0.5, 0.65, f"{len(trans_df):,}\ntrans HiC\npairs",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["hic_contact"], fontweight="bold",
- transform=ax_gap.transAxes)
- ax_gap.text(0.5, 0.28, f"{contact_rate:.2f}\npairs/Mb\u00b2",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["hic_contact"], transform=ax_gap.transAxes)
- else:
- win_B_start, win_B_end = meta["win_B"]
- win_B_o_start, win_B_o_end = meta["win_B_other"]
- reads_B_j = stack_reads(get_reads_in_window(
- bam, scaf_B, win_B_start, win_B_end, spanning_names, MIN_MAPQ))
- mids_B_j, depth_B_j = cov_store[(assembly, scaf_B, win_B_start, win_B_end)]
- reads_B_o = stack_reads(get_reads_in_window(
- bam, scaf_B, win_B_o_start, win_B_o_end, spanning_names, MIN_MAPQ))
- mids_B_o, depth_B_o = cov_store[(assembly, scaf_B, win_B_o_start, win_B_o_end)]
- ax_dens_B = fig.add_subplot(inner_B[0])
- ax_reads_B_j = fig.add_subplot(inner_B[1])
- ax_cov_B_j = fig.add_subplot(inner_B[2])
- ax_reads_B_o = fig.add_subplot(inner_B[3])
- ax_cov_B_o = fig.add_subplot(inner_B[4])
- if not trans_df.empty and len_B > 0:
- mids_dB, counts_dB = compute_density(trans_df["pos2"].values, len_B, DENS_BIN)
- else:
- mids_dB, counts_dB = np.array([len_B / 2]), np.array([0.0])
- bnd_genome_B = 0 if end_B == "start" else len_B
- draw_density_track(
- ax_dens_B, mids_dB, counts_dB, len_B, assembly, ymax_dens,
- title=lbl_B, view_win=meta["win_B"], boundary_x=bnd_genome_B,
- show_boundary_side="left" if end_B == "start" else "right",
- view_win_other=meta["win_B_other"])
- ax_dens_B.set_yticklabels([])
- draw_read_track(ax_reads_B_j, reads_B_j, win_B_start, win_B_end, trans_df, "pos2")
- draw_coverage_track(ax_cov_B_j, mids_B_j, depth_B_j,
- win_B_start, win_B_end, assembly, ymax_cov)
- ax_cov_B_j.set_yticklabels([])
- ax_cov_B_j.set_xlabel("")
- ax_cov_B_j.tick_params(axis="x", labelbottom=True)
- bnd_x_B_j = 0 if end_B == "start" else (win_B_end - win_B_start)
- draw_boundary_line([ax_reads_B_j, ax_cov_B_j], bnd_x_B_j)
- end_B_other = "end" if end_B == "start" else "start"
- draw_read_track(ax_reads_B_o, reads_B_o, win_B_o_start, win_B_o_end, trans_df, "pos2")
- draw_coverage_track(ax_cov_B_o, mids_B_o, depth_B_o,
- win_B_o_start, win_B_o_end, assembly, ymax_cov)
- ax_cov_B_o.set_yticklabels([])
- bnd_x_B_o = (win_B_o_end - win_B_o_start) if end_B_other == "end" else 0
- draw_boundary_line([ax_reads_B_o, ax_cov_B_o], bnd_x_B_o)
- if not trans_df.empty:
- draw_hic_arcs(fig, ax_reads_A_j, ax_reads_B_j, trans_df,
- meta["win_A"], meta["win_B"])
- n_span = len(spanning_names)
- ax_gap.text(0.5, 0.88, f"{len(trans_df):,}\ntrans HiC\npairs",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["hic_contact"], fontweight="bold",
- transform=ax_gap.transAxes)
- ax_gap.text(0.5, 0.70, f"{contact_rate:.2f}\npairs/Mb\u00b2",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["hic_contact"], transform=ax_gap.transAxes)
- ax_gap.text(0.5, 0.50, f"{n_span}\nspanning\nread{'s' if n_span != 1 else ''}",
- ha="center", va="center", fontsize=FONT["gap"],
- color=COLOURS_SPANNING["spanning"], fontweight="bold",
- transform=ax_gap.transAxes)
- handles_leg = [
- mpatches.Patch(color=COLOURS_SPANNING["spanning"], alpha=0.9, label="HiFi spanning read"),
- mpatches.Patch(color=COLOURS_SPANNING["background"], alpha=0.5, label="HiFi non-spanning read"),
- mpatches.Patch(color=COLOURS_SPANNING["hic_contact"], alpha=0.7, label="Trans HiC contact"),
- mpatches.Patch(color=COLOURS_SPANNING["coverage"]["mpibr"], alpha=0.7, label="HiFi depth (MPIBR)"),
- mpatches.Patch(color=COLOURS_SPANNING["coverage"]["dtol"], alpha=0.7, label="HiFi depth (DToL)"),
- plt.Line2D([0], [0], color=COLOURS_SPANNING["boundary"], linewidth=0.9,
- linestyle="--", label="Scaffold boundary"),
- mpatches.Patch(color="yellow", alpha=0.4, label="200 kb view window"),
- ]
- # ── Breakpoints figure ────────────────────────────────────────────────
- n_bp = len(bp_meta)
- fig_bp = plt.figure(figsize=(18.0 / 2.54, 14)) #old height:(15.0 * n_bp + 2.5) / 2.54
- outer_gs_bp = GridSpec(
- n_bp, 3, figure=fig_bp,
- width_ratios=[1, 0.20, 1],
- height_ratios=[15.0] * n_bp,
- hspace=0.25, wspace=0.08,
- left=0.11, right=0.97, top=0.97, bottom=0.07,
- )
- _draw_rows(fig_bp, outer_gs_bp, bp_meta, bams, cov_store, cov_ymax, dens_ymax)
- fig_bp.legend(handles=handles_leg, loc="lower center",
- ncol=4, fontsize=FONT["legend"], frameon=False,
- bbox_to_anchor=(0.5, 0.0))
- fig_bp.savefig(OUTPUT_SPANNING, dpi=300, bbox_inches="tight")
- plt.show()
- print(f"Saved: {OUTPUT_SPANNING}")
- # ── Controls figure ───────────────────────────────────────────────────
- output_ctrl = OUTPUT_SPANNING.replace(".pdf", "_controls.pdf")
- n_ctrl = len(ctrl_meta)
- fig_ctrl = plt.figure(figsize=(6, 13))
- outer_gs_ctrl = GridSpec(
- n_ctrl, 3, figure=fig_ctrl,
- width_ratios=[1, 0.20, 1],
- height_ratios=[15.0] * n_ctrl,
- hspace=0.25, wspace=0.08,
- left=0.11, right=0.97, top=0.97, bottom=0.04,
- )
- _draw_rows(fig_ctrl, outer_gs_ctrl, ctrl_meta, bams, cov_store, cov_ymax, dens_ymax)
- fig_ctrl.savefig(output_ctrl, dpi=300, bbox_inches="tight")
- plt.show()
- print(f"Saved: {output_ctrl}")
- # %% [markdown]
- # ---
- # ## 2. Trans HiC contact rate statistics
- #
- # Tests whether the normalised trans contact rate (pairs per Mb²) between breakpoint scaffold pairs is significantly elevated above the background distribution of all trans scaffold pairs.
- #
- # - **Empirical p-value**: fraction of background pairs with rate ≥ observed
- # - **Wilcoxon rank-sum** (one-tailed): DToL only (3 breakpoints)
- # - **Plot A**: violin/strip plot of background, positive control, and breakpoint rates
- # - **Plot B**: histogram of background distribution with breakpoint lines
- # %% [markdown]
- # ### 2.1 Parameters
- # %%
- MPIBR_TRANS = f"{WD}/hic_trans_background/mpibr_trans_counts.tsv"
- MPIBR_INTRA = f"{WD}/hic_trans_background/mpibr_intra_counts.tsv"
- DTOL_TRANS = f"{WD}/hic_trans_background/dtol_trans_counts.tsv"
- DTOL_INTRA = f"{WD}/hic_trans_background/dtol_intra_counts.tsv"
- OUTPUT_STATS = "hic_trans_stats" # prefix; .pdf and _distribution.pdf produced
- # %% [markdown]
- # ### 2.2 Breakpoint definitions and colours
- # %%
- BREAKPOINTS_STATS = {
- "mpibr": [("BP1", "scaffold_40", "scaffold_44")],
- "dtol": [
- ("BP2", "scaffold_31", "scaffold_40"),
- ("BP3", "scaffold_41", "scaffold_46"),
- ("BP4", "scaffold_44", "scaffold_45"),
- ],
- }
- COLOURS_STATS = {
- "negative": "#AAAAAA", # grey — background trans
- "positive": "#4DAF4A", # green — intra-scaffold
- "breakpoint": {
- "mpibr": "#2166AC", # blue — MPIBR breakpoints
- "dtol": "#FF6600", # orange — DToL breakpoints
- },
- }
- # %% [markdown]
- # ### 2.3 Helper functions
- # %%
- def load_trans_bg(path):
- df = pd.read_csv(path, sep="\t")
- df.columns = df.columns.str.strip()
- df["rate_per_mb2"] = pd.to_numeric(df["rate_per_mb2"], errors="coerce")
- return df.dropna(subset=["rate_per_mb2"])
- def load_intra_bg(path):
- df = pd.read_csv(path, sep="\t")
- df.columns = df.columns.str.strip()
- df["rate_per_mb2"] = pd.to_numeric(df["rate_per_mb2"], errors="coerce")
- return df.dropna(subset=["rate_per_mb2"])
- def get_breakpoint_rates(trans_df, assembly):
- rows = []
- for label, sa, sb in BREAKPOINTS_STATS.get(assembly, []):
- key_a, key_b = sorted([sa, sb])
- match = trans_df[
- (trans_df["scaffold_A"] == key_a) &
- (trans_df["scaffold_B"] == key_b)]
- if match.empty:
- print(f" WARNING: {sa}/{sb} not found in trans table")
- continue
- rate = float(match["rate_per_mb2"].values[0])
- rows.append({"label": label, "scaffold_A": sa, "scaffold_B": sb,
- "rate_per_mb2": rate})
- return pd.DataFrame(rows)
- def empirical_pvalue(observed, background):
- n = len(background)
- count = np.sum(background >= observed)
- return (count + 1) / (n + 1)
- def wilcoxon_ranksum(bp_rates, background_rates):
- stat, p = stats.mannwhitneyu(bp_rates, background_rates, alternative="greater")
- return stat, p
- def jitter(n, width=0.15, seed=42):
- rng = np.random.default_rng(seed)
- return rng.uniform(-width, width, n)
- def draw_distribution(ax, rates, x_pos, colour, label, show_violin=True):
- if len(rates) > 1 and show_violin:
- parts = ax.violinplot([rates], positions=[x_pos],
- widths=0.6, showmedians=True, showextrema=False)
- for pc in parts["bodies"]:
- pc.set_facecolor(colour)
- pc.set_alpha(0.35)
- pc.set_edgecolor(colour)
- parts["cmedians"].set_color(colour)
- parts["cmedians"].set_linewidth(1.5)
- ax.scatter(np.full(len(rates), x_pos) + jitter(len(rates)),
- rates, s=8, color=colour, alpha=0.5, linewidths=0, zorder=3)
- ax.errorbar(x_pos, rates.mean(), yerr=rates.std(),
- fmt="D", color=colour, markersize=4, capsize=3,
- linewidth=1.0, zorder=5)
- def draw_breakpoint_lines(ax, bp_df, colour, x_range):
- y_span = ax.get_ylim()[1] - ax.get_ylim()[0]
- min_y_gap = y_span * 0.02
- nudge = y_span * 0.02
- sorted_df = bp_df.sort_values("rate_per_mb2")
- prev_y_label = -np.inf
- for _, row in sorted_df.iterrows():
- ax.axhline(row["rate_per_mb2"], color=colour, linewidth=0.5,
- linestyle="--", alpha=0.8, zorder=4)
- y_label = row["rate_per_mb2"]
- if y_label - prev_y_label < min_y_gap:
- y_label = prev_y_label + nudge
- ax.text(x_range[1] - 0.05, y_label, f" {row['label']}",
- va="center", ha="left", fontsize=8, color=colour)
- prev_y_label = y_label
- # %% [markdown]
- # ### 2.4 Run analysis and generate plots
- # %%
- assemblies = [("mpibr", MPIBR_TRANS, MPIBR_INTRA),
- ("dtol", DTOL_TRANS, DTOL_INTRA)]
- n_asm = len(assemblies)
- fig_w = 6 #16.0 / 2.54
- fig_h = 6 # 8.0 / 2.54 * n_asm
- fig, axes = plt.subplots(1, n_asm, figsize=(fig_w, fig_h), sharey=False)
- if n_asm == 1:
- axes = [axes]
- all_results = []
- dist_store = {}
- for ax, (assembly, trans_path, intra_path) in zip(axes, assemblies):
- print(f"\n{'='*60}\nAssembly: {assembly.upper()}\n{'='*60}")
- trans_df = load_trans_bg(trans_path)
- intra_df = load_intra_bg(intra_path)
- print(f" Negative control pairs: {len(trans_df)}")
- print(f" Positive control scaffolds: {len(intra_df)}")
- bp_df = get_breakpoint_rates(trans_df, assembly)
- if bp_df.empty:
- print(" No breakpoint rates found — skipping.")
- ax.set_visible(False)
- continue
- bp_keys = set()
- for _, sa, sb in BREAKPOINTS_STATS.get(assembly, []):
- bp_keys.add(tuple(sorted([sa, sb])))
- bg_mask = ~trans_df.apply(
- lambda r: tuple(sorted([r["scaffold_A"], r["scaffold_B"]])) in bp_keys, axis=1)
- background = trans_df[bg_mask]["rate_per_mb2"].values
- intra_rates = intra_df["rate_per_mb2"].values
- bp_rates = bp_df["rate_per_mb2"].values
- print(f"\n Background: mean={background.mean():.3f} "
- f"median={np.median(background):.3f} sd={background.std():.3f} n={len(background)}")
- print(f" Intra-scaffold: mean={intra_rates.mean():.3f} "
- f"median={np.median(intra_rates):.3f} sd={intra_rates.std():.3f} n={len(intra_rates)}")
- emp_pvals = []
- print(f"\n Breakpoint rates (n={len(background)} background pairs):")
- for _, row in bp_df.iterrows():
- p_emp = empirical_pvalue(row["rate_per_mb2"], background)
- emp_pvals.append(p_emp)
- print(f" {row['label']} ({row['scaffold_A']} / {row['scaffold_B']}): "
- f"rate={row['rate_per_mb2']:.3f} empirical p={p_emp:.4f}")
- all_results.append({
- "assembly": assembly, "label": row["label"],
- "scaffold_A": row["scaffold_A"], "scaffold_B": row["scaffold_B"],
- "rate_per_mb2": row["rate_per_mb2"],
- "bg_mean": background.mean(), "bg_median": np.median(background),
- "bg_sd": background.std(), "bg_n": len(background),
- "empirical_p": p_emp, "wilcoxon_U": np.nan, "wilcoxon_p": np.nan,
- })
- colour_bp = COLOURS_STATS["breakpoint"][assembly]
- colour_asm = "#2166AC" if assembly == "mpibr" else "#e65c00"
- x_neg, x_pos = 1.0, 2.0
- x_range = (0.3, 2.9)
- draw_distribution(ax, background, x_neg, COLOURS_STATS["negative"], "Background trans")
- draw_distribution(ax, intra_rates, x_pos, COLOURS_STATS["positive"], "Intra-scaffold")
- draw_breakpoint_lines(ax, bp_df, colour_bp, x_range)
- for _, row in bp_df.iterrows():
- ax.scatter(x_neg, row["rate_per_mb2"], s=40, color=colour_bp,
- zorder=6, marker="*", linewidths=0)
- ax.set_xlim(*x_range)
- ax.set_xticks([x_neg, x_pos])
- ax.set_xticklabels(["Background\ntrans pairs", "Intra-scaffold\n(positive ctrl)"],
- fontsize=8)
- ax.set_ylabel("Contact rate (pairs / Mb²)", fontsize=9)
- ax.set_title(assembly.upper(), fontsize=10, fontweight="bold", color=colour_asm)
- ax.spines["top"].set_visible(False)
- ax.spines["right"].set_visible(False)
- _wilcoxon_p = np.nan
- if len(bp_rates) > 1:
- stat, _wilcoxon_p = wilcoxon_ranksum(bp_rates, background)
- print(f"\n Wilcoxon (joint) n={len(bp_rates)} vs n={len(background)}: "
- f"U={stat:.1f} p={_wilcoxon_p:.4f} (one-tailed, greater)")
- all_results[-1]["wilcoxon_U"] = stat
- all_results[-1]["wilcoxon_p"] = _wilcoxon_p
- else:
- print(f"\n Wilcoxon skipped for {assembly.upper()} (single breakpoint)")
- dist_store[assembly] = {
- "background": background, "bp_df": bp_df,
- "emp_pvals": emp_pvals, "wilcoxon_p": _wilcoxon_p,
- "colour_bp": colour_bp, "colour_asm": colour_asm,
- }
- handles_stats = [
- mpatches.Patch(color=COLOURS_STATS["negative"], alpha=0.7, label="Background trans pairs"),
- mpatches.Patch(color=COLOURS_STATS["positive"], alpha=0.7, label="Intra-scaffold long-range"),
- plt.Line2D([0], [0], color=COLOURS_STATS["breakpoint"]["mpibr"],
- linewidth=1.0, linestyle="--", label="MPIBR breakpoint rate"),
- plt.Line2D([0], [0], color=COLOURS_STATS["breakpoint"]["dtol"],
- linewidth=1.0, linestyle="--", label="DToL breakpoint rate"),
- plt.Line2D([0], [0], marker="*", color="w",
- markerfacecolor=COLOURS_STATS["breakpoint"]["mpibr"],
- markersize=8, label="Breakpoint observed"),
- ]
- plt.tight_layout()
- fig.subplots_adjust(bottom=0.18)
- fig.legend(handles=handles_stats, loc="lower center", ncol=3, fontsize=9,
- frameon=False, bbox_to_anchor=(0.5, 0.01))
- plot_path = f"{OUTPUT_STATS}.pdf"
- fig.savefig(plot_path, dpi=300, bbox_inches="tight")
- plt.show()
- print(f"\nSaved: {plot_path}")
- # %% [markdown]
- # ### 2.5 Background distribution histogram
- # %%
- n_dist = len(dist_store)
- fig2, axes2 = plt.subplots(1, n_dist,
- figsize=(6, 3),
- sharey=False)
- if n_dist == 1:
- axes2 = [axes2]
- for ax2, (assembly, store) in zip(axes2, dist_store.items()):
- bg = store["background"]
- bp_df = store["bp_df"]
- emp_pvals = store["emp_pvals"]
- colour_bp = store["colour_bp"]
- colour_asm= store["colour_asm"]
- counts, _, _ = ax2.hist(bg, bins=40, color=COLOURS_STATS["negative"],
- alpha=0.75, edgecolor="white", linewidth=0.3,
- zorder=1, label="Background trans pairs")
- y_max = counts.max() * 1.15
- ax2.set_ylim(0, y_max)
- bp_sorted = sorted(zip(bp_df.iterrows(), emp_pvals),
- key=lambda t: t[0][1]["rate_per_mb2"])
- min_x_gap = (bg.max() - bg.min()) * 0.06
- label_step = y_max * 0.18
- prev_x, nudge_level = -np.inf, 0
- for (_, row), p_emp in bp_sorted:
- x_val = row["rate_per_mb2"]
- ax2.axvline(x_val, color=colour_bp, linewidth=0.5, linestyle="--", zorder=3)
- p_str = f"p = {p_emp:.4f}" if p_emp >= 0.0001 else "p < 0.0001"
- if x_val - prev_x < min_x_gap:
- nudge_level += 1
- else:
- nudge_level = 0
- y_label = y_max * 0.97 - nudge_level * label_step
- ax2.text(x_val, y_label, f" {row['label']}\n {p_str}",
- va="top", ha="left", fontsize=8, color=colour_bp,
- style="italic", zorder=4)
- prev_x = x_val
- wilcoxon_p = store["wilcoxon_p"]
- if not np.isnan(wilcoxon_p):
- p_str = f"p = {wilcoxon_p:.4f}" if wilcoxon_p >= 0.0001 else "p < 0.0001"
- ax2.text(0.99, 0.15, f"Wilcoxon (joint)\n{p_str}",
- transform=ax2.transAxes, va="bottom", ha="right",
- fontsize=8, color="#FF6600",
- bbox=dict(boxstyle="round,pad=0.2", fc="white",
- ec="#FF6600", linewidth=0.6, alpha=0.6))
- ax2.set_xlabel("Contact rate (pairs / Mb²)", fontsize=9)
- ax2.set_ylabel("Number of scaffold pairs", fontsize=9)
- ax2.set_title(assembly.upper(), fontsize=10, fontweight="bold", color=colour_asm)
- ax2.spines["top"].set_visible(False)
- ax2.spines["right"].set_visible(False)
- plt.tight_layout()
- dist_path = f"{OUTPUT_STATS}_distribution.pdf"
- fig2.savefig(dist_path, dpi=300, bbox_inches="tight")
- plt.show()
- print(f"Saved: {dist_path}")
- # %% [markdown]
- # ### 2.6 Results table
- # %%
- if all_results:
- results_df = pd.DataFrame(all_results)
- tsv_path = f"{OUTPUT_STATS}_results.tsv"
- results_df.to_csv(tsv_path, sep="\t", index=False, float_format="%.4f")
- print(f"Saved: {tsv_path}\n")
- display(results_df)
- # %% [markdown]
- # ---
- # ## 3. Repeat content at junction windows
- #
- # Parses RepeatMasker `.out` files for junction and control scaffold windows and produces a stacked bar chart showing repeat class fractions per window.
- # %% [markdown]
- # ### 3.1 Parameters
- # %%
- REPEAT_OUTPUT_DIR = f"{BASE}/junction_repeats"
- OUTPUT_REPEATS = "junction_repeat_content" # prefix; .tsv and .pdf produced
- WINDOW_SIZE = 200_000
- # %% [markdown]
- # ### 3.2 Repeat class colours and label metadata
- # %%
- CLASS_COLOURS_RM = {
- "SINE": "#EE6677",
- "LINE": "#377EB8",
- "DNA": "#4DAF4A",
- "LTR": "#984EA3",
- "Simple_repeat": "#FF7F00",
- "Low_complexity": "#A65628",
- "Unknown": "#999999",
- "Other": "#CCCCCC",
- }
- LABEL_META_RM = {
- "BP1_MPIBR_40_end": ("mpibr", False),
- "BP1_MPIBR_44_start": ("mpibr", False),
- "BP2_DToL_31_end": ("dtol", False),
- "BP2_DToL_40_start": ("dtol", False),
- "BP3_DToL_41_end": ("dtol", False),
- "BP3_DToL_46_start": ("dtol", False),
- "BP4_DToL_44_end": ("dtol", False),
- "BP4_DToL_45_start": ("dtol", False),
- "Ctrl_MPIBR_3_end": ("mpibr", True),
- "Ctrl_MPIBR_3_start": ("mpibr", True),
- "Ctrl_MPIBR_5_end": ("mpibr", True),
- "Ctrl_MPIBR_5_start": ("mpibr", True),
- "Ctrl_DToL_1_end": ("dtol", True),
- "Ctrl_DToL_1_start": ("dtol", True),
- "Ctrl_DToL_2_end": ("dtol", True),
- "Ctrl_DToL_2_start": ("dtol", True),
- }
- LABEL_ASSEMBLY_RM = {k: v[0] for k, v in LABEL_META_RM.items()}
- ASSEMBLY_COLOURS_RM = {"mpibr": "#2166AC", "dtol": "#e65c00"}
- ROW_ORDER_RM = [
- "BP1_MPIBR_40_end", "BP1_MPIBR_44_start",
- "BP2_DToL_31_end", "BP2_DToL_40_start",
- "BP3_DToL_41_end", "BP3_DToL_46_start",
- "BP4_DToL_44_end", "BP4_DToL_45_start",
- "Ctrl_MPIBR_3_end", "Ctrl_MPIBR_3_start",
- "Ctrl_MPIBR_5_end", "Ctrl_MPIBR_5_start",
- "Ctrl_DToL_1_end", "Ctrl_DToL_1_start",
- "Ctrl_DToL_2_end", "Ctrl_DToL_2_start",
- ]
- # %% [markdown]
- # ### 3.3 Parsing helpers
- # %%
- def normalise_class_rm(repeat_class):
- rc = repeat_class.upper()
- if rc.startswith("SINE"): return "SINE"
- if rc.startswith("LINE"): return "LINE"
- if rc.startswith("DNA"): return "DNA"
- if rc.startswith("LTR"): return "LTR"
- if "SIMPLE" in rc: return "Simple_repeat"
- if "LOW_COMPLEX" in rc: return "Low_complexity"
- if rc in ("UNKNOWN", "UNSPECIFIED"): return "Unknown"
- return "Other"
- def parse_repeatmasker_out(out_path):
- rows = []
- if not os.path.isfile(out_path):
- print(f" WARNING: .out file not found: {out_path}")
- return pd.DataFrame()
- with open(out_path) as fh:
- for _ in range(3):
- fh.readline()
- for line in fh:
- line = line.rstrip("\n")
- if not line.strip():
- continue
- parts = line.split()
- if len(parts) < 11:
- continue
- try:
- seq_name = parts[4]
- start = int(parts[5])
- end = int(parts[6])
- rep_class = parts[10]
- rep_length = end - start + 1
- rows.append({
- "seq_name": seq_name,
- "start": start,
- "end": end,
- "rep_class": normalise_class_rm(rep_class),
- "rep_length": rep_length,
- })
- except (ValueError, IndexError):
- continue
- return pd.DataFrame(rows)
- def window_length_from_fasta(fa_path):
- lengths = {}
- current, length = None, 0
- with open(fa_path) as fh:
- for line in fh:
- line = line.strip()
- if line.startswith(">"):
- if current:
- lengths[current] = length
- current = line[1:].split()[0]
- length = 0
- else:
- length += len(line)
- if current:
- lengths[current] = length
- return lengths
- def summarise_repeats_rm(rm_df, seq_lengths):
- rows = []
- all_classes = list(CLASS_COLOURS_RM.keys())
- for seq_name, seq_len in seq_lengths.items():
- sub = rm_df[rm_df["seq_name"] == seq_name] if not rm_df.empty else pd.DataFrame()
- row = {"label": seq_name, "seq_len": seq_len}
- total_masked = 0
- for cls in all_classes:
- cls_bp = 0 if sub.empty else int(sub[sub["rep_class"] == cls]["rep_length"].sum())
- row[f"{cls}_bp"] = cls_bp
- row[f"{cls}_fraction"] = cls_bp / seq_len if seq_len > 0 else 0.0
- total_masked += cls_bp
- row["total_masked_bp"] = total_masked
- row["total_repeat_fraction"] = total_masked / seq_len if seq_len > 0 else 0.0
- rows.append(row)
- return pd.DataFrame(rows)
- # %% [markdown]
- # ### 3.4 Parse RepeatMasker output
- # %%
- all_summaries = []
- for asm in ["mpibr", "dtol"]:
- rm_dir = os.path.join(REPEAT_OUTPUT_DIR, f"wd/{asm}")
- fa_path = os.path.join(REPEAT_OUTPUT_DIR, f"fa/{asm}_junction_windows.fa")
- out_file = os.path.join(rm_dir, f"{asm}_junction_windows.fa.out")
- print(f"\n{asm.upper()}")
- if not os.path.isfile(fa_path):
- print(f" WARNING: window FASTA not found: {fa_path} — skipping")
- continue
- seq_lengths = window_length_from_fasta(fa_path)
- rm_df = parse_repeatmasker_out(out_file)
- summary = summarise_repeats_rm(rm_df, seq_lengths)
- summary["assembly"] = asm
- all_summaries.append(summary)
- print(f" {'Label':<30} {'Total%':>7} {'SINE%':>6} {'LINE%':>6} "
- f"{'DNA%':>6} {'LTR%':>6} {'Simple%':>8} {'Unknown%':>9}")
- for _, row in summary.iterrows():
- print(f" {row['label']:<30} {row['total_repeat_fraction']*100:>7.1f} "
- f"{row['SINE_fraction']*100:>6.1f} {row['LINE_fraction']*100:>6.1f} "
- f"{row['DNA_fraction']*100:>6.1f} {row['LTR_fraction']*100:>6.1f} "
- f"{row['Simple_repeat_fraction']*100:>8.1f} "
- f"{row['Unknown_fraction']*100:>9.1f}")
- combined_rm = pd.concat(all_summaries, ignore_index=True)
- combined_rm["_order"] = combined_rm["label"].map(
- {lbl: i for i, lbl in enumerate(ROW_ORDER_RM)})
- combined_rm = combined_rm.sort_values("_order").drop(columns="_order")
- tsv_rm = f"{OUTPUT_REPEATS}.tsv"
- frac_cols = ["label", "assembly", "seq_len", "total_repeat_fraction"] + [f"{c}_fraction" for c in CLASS_COLOURS_RM]
- combined_rm[frac_cols].to_csv(tsv_rm, sep="\t", index=False, float_format="%.4f")
- print(f"\nSummary TSV saved: {tsv_rm}")
- # %% [markdown]
- # ### 3.5 Plot repeat content
- # %%
- labels_rm = combined_rm["label"].tolist()
- n_rm = len(labels_rm)
- classes = list(CLASS_COLOURS_RM.keys())
- fig_w = 6 #14.0 / 2.54
- fig_h = 4.5 #max(6.0, n_rm * 0.55 + 2.0) / 2.54
- fig3, ax3 = plt.subplots(figsize=(fig_w, fig_h))
- y_positions = np.arange(n_rm)
- bar_height = 0.55
- lefts = np.zeros(n_rm)
- for cls in classes:
- fracs = combined_rm[f"{cls}_fraction"].values * 100
- ax3.barh(y_positions, fracs, left=lefts,
- height=bar_height, color=CLASS_COLOURS_RM[cls],
- label=cls, linewidth=0)
- lefts += fracs
- for i, row in enumerate(combined_rm.itertuples()):
- pct = row.total_repeat_fraction * 100
- ax3.text(lefts[i] + 0.3, i, f"{pct:.1f}%", va="center", ha="left", fontsize=8)
- ax3.set_yticks(y_positions)
- ax3.set_yticklabels([])
- ax3.tick_params(axis="y", length=0)
- first_ctrl_idx = None
- for i, lbl in enumerate(labels_rm):
- meta_rm = LABEL_META_RM.get(lbl, ("mpibr", False))
- asm, is_ctrl = meta_rm
- colour = ASSEMBLY_COLOURS_RM[asm]
- style = "italic" if is_ctrl else "normal"
- prefix = "" if is_ctrl else ""
- ax3.text(-0.5, i, prefix + lbl.replace("_", " "),
- va="center", ha="right", fontsize=8,
- color=colour, style=style)
- if is_ctrl and first_ctrl_idx is None:
- first_ctrl_idx = i
- if first_ctrl_idx is not None:
- ax3.axhline(first_ctrl_idx - 0.5, color="#444444",
- linewidth=0.8, linestyle="--", zorder=5)
- # ax3.text(max(lefts) * 0.5, first_ctrl_idx - 0.55,
- # "breakpoints above | controls below",
- # va="top", ha="center", fontsize=5,
- # color="#444444", style="italic")
- ax3.set_xlabel("Repeat content (%)", fontsize=9)
- x_max = max(lefts) * 1.15
- ax3.set_xlim(-0.5, x_max)
- ax3.spines["top"].set_visible(False)
- ax3.spines["right"].set_visible(False)
- ax3.spines["left"].set_visible(False)
- handles_rm = [mpatches.Patch(color=CLASS_COLOURS_RM[c], label=c) for c in classes]
- plt.tight_layout()
- fig3.subplots_adjust(bottom=0.20)
- fig3.legend(handles=handles_rm, loc="lower center", fontsize=9,
- frameon=False, ncol=4, bbox_to_anchor=(0.5, 0.01))
- plot_rm = f"{OUTPUT_REPEATS}.pdf"
- fig3.savefig(plot_rm, dpi=300, bbox_inches="tight")
- plt.show()
- print(f"Saved: {plot_rm}")
breakpoint_analysis.ipynb at commit efe218e, under MIT · at the source
Overview
- Max Planck Institute for Brain Research Frankfurt am Main Germany
- Radboud University, Donders Institute for Brain, Cognition and Behaviour Nijmegen Netherlands
- Faculty of Biological Sciences, Goethe University Frankfurt am Main Germany
- Department of Neuroscience and Developmental Biology, University of Vienna Vienna Austria
Abstract
Coleoid cephalopods, a subclass of mollusks that includes octopuses, cuttlefish, and squid, exhibit sophisticated biological features, such as dynamic and neurally driven camouflage behavior, inter-individual communication, single-lens camera-like eyes, the largest brains among invertebrates, and a distinctive embryonic development. The common cuttlefish Sepia officinalis has served as a model organism in various research fields, spanning biophysics, neurobiology, behavior, evolution, ecology, and biomechanics. More recently, it has become a model to investigate the neural mechanisms underlying cephalopod camouflage, using quantitative behavioral approaches alongside molecular techniques to characterize the identity, evolution, and development of neuronal cell types. Despite significant interest in this animal, a high-quality, annotated genome of this species is still lacking. To address this, we sequenced and assembled a chromosome-scale genome for S. officinalis. Our assembly spans 5.68 billion base pairs and comprises 1n=
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 40 matches between paragraphs and lines of code.
gitlab.mpcdf.mpg.de/mpibr/laur/cuttlefishomics/soffgenome
efe218e0bf3c8318c5c729cbab94478dbf5b5502, 28 April 2026Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
68 files
- alignment/
starJS_to_bed6.pl , Perl, 42 lines - alignment/
task_countreads.sh , Shell, 46 lines, 1 match - alignment/
task_graphbed.sh , Shell, 45 lines - alignment/
task_minimap.sh , Shell, 81 lines - alignment/
task_minimap_sanger.sh , Shell, 37 lines - alignment/
task_minimap_species.sh , Shell, 44 lines - alignment/
task_mkref_species.sh , Shell, 33 lines - alignment/
task_mkrefs.sh , Shell, 67 lines - alignment/
task_star_count_braker.s , Shell, 94 lines, 1 matchh - alignment/
task_star_dna.sh , Shell, 61 lines - alignment/
task_star_dna_species.sh , Shell, 54 lines - alignment/
task_star_rna.sh , Shell, 89 lines, 1 match - alignment/
task_star_scell.sh , Shell, 93 lines, 1 match - analysis/
gene_families/ , R, 461 linescafe5/ cafe5_top_families_heatm ap.R - analysis/
gene_families/ , Python, 533 lines, 1 matchcafe5/ gene_repeat_overlap.py - analysis/
gene_families/ , R, 507 lines, 3 matchescafe5/ large_families_heatmap.R - analysis/
gene_families/ , Shell, 100 lines, 1 matchcafe5/ run_cafe5.sh - analysis/
gene_families/ , R, 1,354 lines, 3 matchesgene_family_expression_a nalysis.Rmd - analysis/
gene_families/ , R, 70 lines, 1 matchorthofinder/ orthofinder_to_cafe.R - analysis/
genome_alignments/ , Jupyter, 1,598 lines, 6 matchesbreakpoints/ breakpoint_analysis.ipyn b - analysis/
genome_alignments/ , Shell, 198 lines, 2 matchesbreakpoints/ count_hic_trans_backgrou nd.sh - analysis/
genome_alignments/ , Shell, 166 lines, 2 matchesbreakpoints/ extract_hic_trans_pairs. sh - analysis/
genome_alignments/ , Python, 322 lines, 1 matchbreakpoints/ extract_spanning_reads.p y - analysis/
genome_alignments/ , Shell, 47 linesbreakpoints/ generate_scaffold_beds.s h - analysis/
genome_alignments/ , Shell, 129 linesbreakpoints/ run_map_hic_scaffolds.sh - analysis/
genome_alignments/ , Shell, 107 linesbreakpoints/ run_map_hifi_scaffolds.s h - analysis/
genome_alignments/ , Shell, 198 lines, 2 matchesbreakpoints/ run_repeatmasker_junctio ns.sh - analysis/
genome_alignments/ , R, 352 lines, 1 matchwga/ assembly_comparison.R - analysis/
genome_alignments/ , Shell, 65 lineswga/ run_winnowmap.sh - analysis/
rna_seq/ , R, 428 lines, 3 matchesbulkRNA_Deseq2_braker.Rm d - analysis/
rna_seq/ , R, 33 lines, 2 matchesemapper_interproscan_LUT .R - analysis/
rna_seq/ , R, 409 lines, 3 matchesparse_interproscan_go.R - annotation/
stringtieGTF2BED12.pl , Perl, 133 lines - annotation/
task_braker_softmasked.s , Shell, 67 lines, 1 matchh - annotation/
task_repeatmasker.sh , Shell, 38 lines - annotation/
task_repeatmasker_custom , Shell, 79 lines.sh - annotation/
task_repeatmasker_custom , Shell, 77 lines, 1 match_softmask.sh - annotation/
task_repeatmodeler.sh , Shell, 82 lines - annotation/
task_stringtie_long.sh , Shell, 38 lines - annotation/
task_stringtie_merged.sh , Shell, 46 lines - annotation/
task_stringtie_mixed.sh , Shell, 55 lines - annotation/
task_trinity.sh , Shell, 53 lines - archive/
old_pipeline/ , Perl, 56 linesfilterFastqWithWhitelist .pl - archive/
old_pipeline/ , Perl, 103 linesgetVecScreenCategories.p l - archive/
old_pipeline/ , Shell, 427 linespipeline.sh - archive/
old_pipeline/ , Shell, 39 linesrun_busco.sh - archive/
old_pipeline/ , Shell, 50 linesrun_canu.sh - archive/
old_pipeline/ , Shell, 74 linesrun_maphic.sh - archive/
old_pipeline/ , Shell, 70 linesrun_purge.sh - archive/
old_pipeline/ , Shell, 78 linesrun_scaffolding.sh - archive/
old_pipeline/ , Shell, 42 linesrun_sortbam.sh - archive/
utils/ , Perl, 124 linesassemblyStats.pl - assembly/
config/ , Shell, 15 linesbiotools.sh - assembly/
config/ , Shell, 11 linesdata.sh - assembly/
run_busco.sh , Shell, 75 lines, 2 matches - assembly/
run_clean_hic.sh , Shell, 87 lines - assembly/
run_clean_hifi.sh , Shell, 93 lines - assembly/
run_clean_mito.sh , Shell, 56 lines - assembly/
run_filter_hifi.sh , Shell, 86 lines - assembly/
run_hifiasm_mpibr.sh , Shell, 46 lines - assembly/
run_hifiasm_sanger.sh , Shell, 44 lines - assembly/
run_map_hic.sh , Shell, 118 lines - assembly/
run_map_ilmn.sh , Shell, 79 lines - assembly/
run_minimap.sh , Shell, 57 lines - assembly/
run_scaffolding.sh , Shell, 101 lines, 1 match - assembly/
utils/ , Shell, 47 linesvalidators.sh - LICENSE, License, 21 lines
- README.md, Text, 68 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;
- 66 scripts, each with its path and the digest of its content;
- 40 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 genome assembly and raw data can be found at the BioProject PRJNA1091451 on NCBI. Raw sequencing reads are deposited at SRA (study accession SRP570862). The code for the genome assembly and annotation is available at https://
The following datasets were generated:
RenckenS TushevG HainD CiirdaevaE SimakovO LaurentG 2025Sepia officinalis isolate:GLC-03058 (common cuttlefish)NCBI BioProjectPRJNA109145110
RenckenS TushevG HainD CiirdaevaE SimakovO LaurentG 2025Chromosome-scale genome assembly of the European common cuttlefish Sepia officinalisEdmond - the Open Research Data Repository of the Max Planck Society10.17617/
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, 28 September 2026: the first record
Recorded: type, language, journal, volume, pages, dates, 6 authors, 4 keywords, 4 MeSH terms, 1 funder, 183 references, 31 RRIDs.
Cite
This paper
Rencken, S. D., Tushev, G., Hain, D., Ciirdaeva, E., Simakov, O., & Laurent, G. (2026). Chromosome-scale genome assembly of the European common cuttlefish &
BibTeX
@article{rencken2026chro
author = {Rencken, Simone Daniela and Tushev, Georgi and Hain, David and Ciirdaeva, Elena and Simakov, Oleg and Laurent, Gilles},
title = {{Chromosome-scale genome assembly of the European common cuttlefish \&
journal = {eLife},
year = {2026},
month = may,
volume = {14},
pages = {RP107393},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/
url = {https://
pmid = {42206967},
pmcid = {PMC13218726}
}
RIS
TY - JOUR
AU - Rencken, Simone Daniela
AU - Tushev, Georgi
AU - Hain, David
AU - Ciirdaeva, Elena
AU - Simakov, Oleg
AU - Laurent, Gilles
TI - Chromosome-scale genome assembly of the European common cuttlefish &
T2 - eLife
J2 - eLife
PY - 2026
DA - 2026/
VL - 14
SP - RP107393
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": "Chromosome-scale genome assembly of the European common cuttlefish &
"container-title": "eLife",
"author": [
{
"family": "Rencken",
"given": "Simone Daniela"
},
{
"family": "Tushev",
"given": "Georgi"
},
{
"family": "Hain",
"given": "David"
},
{
"family": "Ciirdaeva",
"given": "Elena"
},
{
"family": "Simakov",
"given": "Oleg"
},
{
"family": "Laurent",
"given": "Gilles"
}
],
"container-title-short":
"volume": "14",
"page": "RP107393",
"DOI": "10.7554/
"PMID": "42206967",
"PMCID": "PMC13218726",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
28
]
]
}
}
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/s41586-026-10512-9 [code]
- Astrocyte glucocorticoid receptor signalling restricts neuronal plasticity.Journal: NatureIn common: Subread (featureCounts), STAR, pysam, 15 other tools, cellular / molecular, 4 references
- [2] doi:10.21203/rs.3.rs-9927928/v1 [code]
- Genome-wide and allele-resolved maps of the radial architecture of the mouse genomeJournal: Research Square (preprint)In common: Subread (featureCounts), STAR, pysam, 13 other tools, 4 references
- [3] doi:10.1093/bioinformatics/btag592 [code]
- Network-based stratification of allele-specific expression reveals patient subgroups in Huntington's disease.Journal: Bioinformatics (Oxford, England)In common: Subread (featureCounts), STAR, SAMtools, 13 other tools, 4 references
- [4] doi:10.1038/s42003-026-10402-w [code]
- Temporal orchestration of transcriptional and epigenomic programming underlying maternal embryonic diapause in a cricket model.Journal: Communications biologyIn common: SAMtools, DESeq2, pheatmap, 2 other tools, other, 12 references
- [5] doi:10.1126/sciadv.aed2952 [code]
- Activation of transposable elements is linked to a region- and cell type-specific interferon response in Parkinson's disease.Journal: Science advancesIn common: Subread (featureCounts), STAR, pysam, 12 other tools, cellular / molecular, 3 references
- [6] doi:10.1038/s41586-026-10629-x [code]
- Whole-genome duplication shaped cell-type evolution in the vertebrate brain.Journal: NatureIn common: BEDTools, DESeq2, circlize, 9 other tools, other, cellular / molecular, 5 references
- [7] doi:10.1038/s41467-026-76675-1 [code]
- Long-read proteogenomic atlas of human neuronal differentiation reveals isoform diversity informing neurodevelopmental risk mechanisms.Journal: Nature communicationsIn common: STAR, pysam, BEDTools, 11 other tools, 2 references
- [8] doi:10.1038/s41467-026-71790-5 [code]
- Recurrent DNA break clusters drive replication-stress-induc
ed copy number variants and genome diversification. Journal: Nature communicationsIn common: pysam, BEDTools, SAMtools, 12 other tools, cellular / molecular - [9] doi:10.1186/s13059-026-04177-w [code]
- Genomic sequence evolution underlying human neocortical interareal diversification.Journal: Genome biologyIn common: pysam, BEDTools, SAMtools, 10 other tools, cellular / molecular, 2 references
- [10] 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: pysam, DESeq2, circlize, 11 other tools, cellular / molecular, 2 references
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, 66 scripts, and 40 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:3e83b8f77b701bd6…
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.
