OSCR

An optimized translating ribosome affinity purification protocol for low-abundance <i>Drosophila</i> tissues.

Code ↔ Paper

3 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 3 matches · 1 of them tie a paragraph to a whole file without given lines: a weak match, whose lines are not tinted
  1. [1] § STAR★Methods › Method details › Translating Ribosome Affinity Purification (TRAP) › RNA-Seq analysis ↔ multiqc/modules/trim_galore/trim_galore.py, lines 53 to 178 · score 0.60 · Trim Galore, quality trimming, adapter · Report a mismatch
  2. [2] § STAR★Methods › Method details › Microscopy ↔ multiqc/templates/default/assets/js/packages/showdown.min.js, the whole file · a weak match · score 0.59 · microscope, oil, mounted, scissors, plates, medium · Report a mismatch
  3. [3] § STAR★Methods › Method details › Translating Ribosome Affinity Purification (TRAP) › RNA-Seq analysis ↔ multiqc/modules/qualimap/QM_RNASeq.py, lines 232 to 301 · score 0.56 · GTF annotation, RNA seq, Alignments, genome, Genes, Mapped · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 603 lines · 25 KB · GPL-3.0 · 1 match

  1. import json
  2. import logging
  3. from collections import defaultdict
  4. from typing import Any, Dict, List, Optional, Tuple, cast
  5. from multiqc import config
  6. from multiqc.base_module import BaseMultiqcModule, ModuleNoSamplesFound, SampleGroupingConfig
  7. from multiqc.plots import bargraph, linegraph, table
  8. from multiqc.types import ColumnKey, SampleName, SectionAlert
  9. log = logging.getLogger(__name__)
  10. SCHEMA_MAJOR_SUPPORTED = 1
  11. class MultiqcModule(BaseMultiqcModule):
  12. """
  13. [Trim Galore](https://github.com/FelixKrueger/TrimGalore) provides
  14. consistent quality and adapter trimming for next-generation sequencing
  15. data, with special handling for Reduced Representation Bisulfite
  16. Sequencing (RRBS) and small-RNA libraries.
  17. This MultiQC module supports Trim Galore v2.0, which is a Rust
  18. rewrite of the original Perl-based v0.6 that has a new JSON output
  19. file summarising results. The earlier v0.6 versions of Trim Galore
  20. that wrapped Cutadapt are supported with reporting via the Cutadapt module.
  21. The old log format is still produced, but the Cutadapt module search pattern
  22. is configured to skip reports mentioning Trim Galore v2+. If you delete the
  23. JSON but keep the v2 text file, the sample will not be reported by either module.
  24. #### Paired-end sample grouping
  25. R1 and R2 of a paired-end sample are grouped automatically into a single
  26. row in the General Statistics and Pair Validation tables. Click the
  27. expand arrow on a grouped row to see the per-read values. The grouping
  28. is derived from the file list inside each JSON report, so it does not
  29. depend on filename patterns.
  30. To disable automatic grouping and show one row per read everywhere:
  31. ```yaml
  32. trim_galore_config:
  33. auto_group_pairs: false
  34. ```
  35. Auto-grouping can be combined with the global
  36. [`table_sample_merge`](../reports/customisation.md#sample-grouping)
  37. config option to merge further — for example to group lanes of an
  38. already-paired sample.
  39. """
  40. def __init__(self):
  41. super().__init__(
  42. name="Trim Galore",
  43. anchor="trim_galore",
  44. href="https://github.com/FelixKrueger/TrimGalore",
  45. info=(
  46. "Quality and adapter trimming for next-generation sequencing data, "
  47. "with special handling for RRBS libraries."
  48. ),
  49. doi="10.5281/zenodo.5127898",
  50. license="GNU General Public License v3.0",
  51. license_url="https://github.com/FelixKrueger/TrimGalore/blob/master/LICENSE",
  52. )
  53. data_by_sample: Dict[str, Dict[str, Any]] = {}
  54. self._pair_key_to_samples: Dict[Tuple[str, ...], List[str]] = {}
  55. for f in self.find_log_files("trim_galore", filehandles=True):
  56. parsed = self._parse_log(f)
  57. if parsed is None:
  58. continue
  59. s_name, payload, pair_key = parsed
  60. if self.is_ignore_sample(s_name):
  61. continue
  62. if s_name in data_by_sample:
  63. log.debug(f"Duplicate sample name found! Overwriting: {s_name}")
  64. data_by_sample[s_name] = payload
  65. self._pair_key_to_samples.setdefault(pair_key, []).append(s_name)
  66. self.add_data_source(f, s_name=s_name)
  67. self.add_software_version(payload["trim_galore_version"], s_name)
  68. if not data_by_sample:
  69. raise ModuleNoSamplesFound
  70. log.info(f"Found {len(data_by_sample)} reports")
  71. auto_group_pairs = getattr(config, "trim_galore_config", {}).get("auto_group_pairs", True)
  72. self._pair_display_by_key: Dict[Tuple[str, ...], str] = {}
  73. explicit_groups: Optional[Dict[str, List[str]]] = None
  74. if auto_group_pairs:
  75. explicit_groups, self._pair_display_by_key = self._derive_auto_groups(data_by_sample)
  76. self._general_stats_table(data_by_sample, explicit_groups)
  77. self.add_section(
  78. name="Filtered Reads",
  79. anchor="trim_galore_filtered_reads",
  80. description="Number of reads after trimming.",
  81. helptext=(
  82. "The `passing` bucket reflects reads that survived quality trimming, adapter trimming and N-content filtering "
  83. "(i.e. what made it into the trimmed output); the remaining buckets show which filter rejected the rest."
  84. ),
  85. plot=self._filtered_reads_plot(data_by_sample),
  86. )
  87. adapter_plot = self._adapter_length_plot(data_by_sample)
  88. if adapter_plot is not None:
  89. self.add_section(
  90. name="Adapter Length Distribution",
  91. anchor="trim_galore_adapter_lengths",
  92. description="Histogram of adapter match lengths per read.",
  93. helptext=(
  94. "Note that a tall left tail at 1 bp is normal: Trim Galore's default `--stringency 1` accepts "
  95. "single-base overlaps, which represent random hits rather than real adapter contamination."
  96. ),
  97. plot=adapter_plot,
  98. )
  99. pair_validation_plot, pv_dropped = self._pair_validation_plot(data_by_sample)
  100. if pair_validation_plot is not None:
  101. self.add_section(
  102. name="Pair Validation",
  103. anchor="trim_galore_pair_validation",
  104. description="Outcomes of paired-end validation",
  105. helptext=(
  106. "Shows pairs analysed, pairs removed (broken down by reason), and reads left unpaired after a partner "
  107. "was discarded. R1 and R2 of a pair are collapsed into a single row (the data is identical between them)."
  108. ),
  109. plot=pair_validation_plot,
  110. alerts=SectionAlert(
  111. message=(
  112. f"**{len(pv_dropped)} sample{'s' if len(pv_dropped) != 1 else ''}** with less than 0.1% "
  113. "of pairs affected hidden from this table."
  114. ),
  115. affected_samples=pv_dropped,
  116. )
  117. if pv_dropped
  118. else None,
  119. )
  120. poly_plot, poly_dropped = self._poly_trimming_plot(data_by_sample)
  121. if poly_plot is not None:
  122. self.add_section(
  123. name="Poly-A / Poly-G Trimming",
  124. anchor="trim_galore_poly_trimming",
  125. description="Reads and bases removed by Trim Galore's poly-A and poly-G tail trimmers.",
  126. plot=poly_plot,
  127. alerts=SectionAlert(
  128. message=(
  129. f"**{len(poly_dropped)} sample{'s' if len(poly_dropped) != 1 else ''}** with no "
  130. "poly-A/G trimming hidden from this table."
  131. ),
  132. affected_samples=poly_dropped,
  133. )
  134. if poly_dropped
  135. else None,
  136. )
  137. rrbs_plot, rrbs_dropped = self._rrbs_plot(data_by_sample)
  138. if rrbs_plot is not None:
  139. self.add_section(
  140. name="RRBS Trimming",
  141. anchor="trim_galore_rrbs",
  142. description="Reduced Representation Bisulfite Sequencing (RRBS) end-repair trimming counts.",
  143. plot=rrbs_plot,
  144. alerts=SectionAlert(
  145. message=(
  146. f"**{len(rrbs_dropped)} sample{'s' if len(rrbs_dropped) != 1 else ''}** with no "
  147. "RRBS trimming hidden from this table."
  148. ),
  149. affected_samples=rrbs_dropped,
  150. )
  151. if rrbs_dropped
  152. else None,
  153. )
  154. self.write_data_file(_flatten_for_data_file(data_by_sample), "multiqc_trim_galore")
  155. def _parse_log(self, f) -> Optional[Tuple[str, Dict[str, Any], Tuple[str, ...]]]:
  156. try:
  157. payload = json.load(f["f"])
  158. except json.JSONDecodeError as e:
  159. log.warning(f"Could not parse {f['fn']!r}: {e}")
  160. return None
  161. # `tool` check uses .get() because we don't yet know this is a Trim
  162. # Galore JSON — could be a foreign JSON that matched the filename glob.
  163. if payload.get("tool") != "Trim Galore":
  164. log.debug(f"Skipping {f['fn']!r}: tool field is {payload.get('tool')!r}, not Trim Galore")
  165. return None
  166. schema = payload["schema_version"]
  167. major = int(str(schema).split(".")[0])
  168. if major != SCHEMA_MAJOR_SUPPORTED:
  169. log.error(
  170. f"Skipping {f['fn']!r}: schema_version {schema!r} has incompatible major version "
  171. f"(expected major v{SCHEMA_MAJOR_SUPPORTED}). This module needs updating to handle the new schema."
  172. )
  173. return None
  174. # PE reports list both filenames in `input_filenames`; pick the one for
  175. # this read_number so R1 and R2 produce distinct samples. SE reports
  176. # have read_number=1 and a 1-element list, so the same logic works.
  177. # `input_filenames` is byte-identical across R1 and R2 of the same pair,
  178. # so it doubles as a stable pair key.
  179. input_filenames = payload["input_filenames"]
  180. read_number = payload["read_number"]
  181. s_name = self.clean_s_name(input_filenames[read_number - 1], f)
  182. return s_name, payload, tuple(input_filenames)
  183. def _derive_auto_groups(
  184. self,
  185. data_by_sample: Dict[str, Dict[str, Any]],
  186. ) -> Tuple[Dict[str, List[str]], Dict[Tuple[str, ...], str]]:
  187. """Build module-supplied groups from tool-derived pair info.
  188. Returns `(explicit_groups, pair_display_by_key)`. When the user has
  189. `config.table_sample_merge` set, name patterns layer on top: each
  190. auto-group's display name is run through `groups_for_sample` to
  191. merge into a super-group.
  192. """
  193. auto_groups: Dict[str, List[str]] = {}
  194. pair_display_by_key: Dict[Tuple[str, ...], str] = {}
  195. for pair_key, members in self._pair_key_to_samples.items():
  196. sorted_members = sorted(members)
  197. # _clean_fastq_pair needs two names; SE pairs have only one.
  198. if len(sorted_members) >= 2:
  199. display = self._clean_fastq_pair(sorted_members[0], sorted_members[1]) or "_".join(sorted_members)
  200. else:
  201. display = sorted_members[0]
  202. auto_groups[display] = sorted_members
  203. pair_display_by_key[pair_key] = display
  204. if not config.table_sample_merge:
  205. return auto_groups, pair_display_by_key
  206. by_super: Dict[str, List[str]] = defaultdict(list)
  207. for auto_display, samples in auto_groups.items():
  208. super_name, _ = self.groups_for_sample(SampleName(auto_display))
  209. by_super[str(super_name)].extend(samples)
  210. return dict(by_super), pair_display_by_key
  211. def _general_stats_table(
  212. self,
  213. data_by_sample: Dict[str, Dict[str, Any]],
  214. explicit_groups: Optional[Dict[str, List[str]]] = None,
  215. ) -> None:
  216. gen_stats: Dict[str, Dict[ColumnKey, Any]] = {}
  217. for s_name, payload in data_by_sample.items():
  218. rp = payload["read_processing"]
  219. bp = payload["basepair_processing"]
  220. total = rp["total_reads"]
  221. total_bp = bp["total_bp_processed"]
  222. gen_stats[s_name] = {
  223. ColumnKey("tg_total_reads"): total,
  224. ColumnKey("tg_pct_with_adapter"): _safe_pct(rp["reads_with_adapter"], total),
  225. ColumnKey("tg_pct_passing"): _safe_pct(rp["reads_written"], total),
  226. ColumnKey("tg_pct_quality_trimmed"): _safe_pct(bp["quality_trimmed_bp"], total_bp),
  227. ColumnKey("tg_total_bp_written"): bp["total_bp_written"],
  228. }
  229. headers: Dict[str, Dict[str, Any]] = {
  230. "tg_pct_with_adapter": {
  231. "title": "% Adapter",
  232. "description": "% reads where at least one adapter was detected",
  233. "max": 100,
  234. "min": 0,
  235. "suffix": "%",
  236. "scale": "OrRd",
  237. "format": "{:,.1f}",
  238. },
  239. "tg_pct_passing": {
  240. "title": "% Pass",
  241. "description": "% reads passing all filters into trimmed output",
  242. "max": 100,
  243. "min": 0,
  244. "suffix": "%",
  245. "scale": "RdYlGn",
  246. "format": "{:,.1f}",
  247. },
  248. "tg_pct_quality_trimmed": {
  249. "title": "% Q-trim",
  250. "description": "% bases removed by 3'-end quality trimming",
  251. "max": 100,
  252. "min": 0,
  253. "suffix": "%",
  254. "scale": "Blues",
  255. "format": "{:,.1f}",
  256. },
  257. "tg_total_reads": {
  258. "title": "Reads",
  259. "description": "Total reads processed",
  260. "scale": "Greys",
  261. "shared_key": "read_count",
  262. },
  263. "tg_total_bp_written": {
  264. "title": "BP written",
  265. "description": "Total basepairs written to trimmed output",
  266. "scale": "BuGn",
  267. "shared_key": "base_count",
  268. "hidden": True,
  269. },
  270. }
  271. self.general_stats_addcols(
  272. cast(Any, gen_stats),
  273. headers,
  274. group_samples_config=SampleGroupingConfig(
  275. explicit_groups=explicit_groups,
  276. cols_to_sum=[
  277. ColumnKey("tg_total_reads"),
  278. ColumnKey("tg_total_bp_written"),
  279. ],
  280. cols_to_weighted_average=[
  281. (ColumnKey("tg_pct_with_adapter"), ColumnKey("tg_total_reads")),
  282. (ColumnKey("tg_pct_passing"), ColumnKey("tg_total_reads")),
  283. (ColumnKey("tg_pct_quality_trimmed"), ColumnKey("tg_total_bp_written")),
  284. ],
  285. ),
  286. )
  287. def _filtered_reads_plot(self, data_by_sample: Dict[str, Dict[str, Any]]):
  288. bar_data: Dict[str, Dict[str, int]] = {}
  289. for s_name, payload in data_by_sample.items():
  290. rp = payload["read_processing"]
  291. bar_data[s_name] = {
  292. "passing": rp["reads_written"],
  293. "too_short": rp["reads_too_short"],
  294. "too_long": rp["reads_too_long"],
  295. "too_many_n": rp["reads_too_many_n"],
  296. "discarded_untrimmed": rp["reads_discarded_untrimmed"],
  297. }
  298. cats = {
  299. "passing": {"name": "Passed filters"},
  300. "too_short": {"name": "Too short"},
  301. "too_long": {"name": "Too long"},
  302. "too_many_n": {"name": "Too many Ns"},
  303. "discarded_untrimmed": {"name": "Discarded (untrimmed)"},
  304. }
  305. return bargraph.plot(
  306. bar_data,
  307. cats,
  308. {
  309. "id": "trim_galore_filtered_reads_plot",
  310. "title": "Trim Galore: Read Filtering",
  311. "ylab": "Number of reads",
  312. "cpswitch_counts_label": "Number of reads",
  313. },
  314. )
  315. def _adapter_length_plot(self, data_by_sample: Dict[str, Dict[str, Any]]):
  316. line_data: Dict[str, Dict[int, int]] = {}
  317. for s_name, payload in data_by_sample.items():
  318. adapters = payload["adapter_trimming"]
  319. for a in adapters:
  320. key = f"{s_name} ({a['name']})" if len(adapters) > 1 else s_name
  321. line_data[key] = {int(k): v for k, v in a["length_distribution"].items()}
  322. if not line_data:
  323. return None
  324. return linegraph.plot(
  325. line_data,
  326. {
  327. "id": "trim_galore_adapter_length_plot",
  328. "title": "Trim Galore: Adapter Length Distribution",
  329. "xlab": "Adapter overlap length (bp)",
  330. "ylab": "Reads (count)",
  331. "ymin": 0,
  332. "tt_label": "{point.x} bp: {point.y:,} reads",
  333. },
  334. )
  335. def _pair_validation_plot(self, data_by_sample: Dict[str, Dict[str, Any]]):
  336. # pair_validation is identical between R1 and R2 JSONs of the same pair —
  337. # collapse them by the tool-derived pair_key. Skip SE samples (no pv).
  338. all_rows: Dict[str, Dict[str, int]] = {}
  339. for pair_key, members in self._pair_key_to_samples.items():
  340. pv = data_by_sample[members[0]]["pair_validation"]
  341. if not pv: # SE samples — `pair_validation` is null
  342. continue
  343. display = self._pair_display_by_key.get(pair_key, members[0])
  344. all_rows[display] = {
  345. "pairs_analyzed": pv["pairs_analyzed"],
  346. "pairs_removed": pv["pairs_removed"],
  347. "pairs_removed_n": pv["pairs_removed_n"],
  348. "pairs_removed_too_long": pv["pairs_removed_too_long"],
  349. "r1_unpaired": pv["r1_unpaired"],
  350. "r2_unpaired": pv["r2_unpaired"],
  351. }
  352. # `pairs_removed_*` are sub-reasons of `pairs_removed` so are not added
  353. # to the affected-fraction sum here (would double-count).
  354. def _kept(r: Dict[str, int]) -> bool:
  355. return bool(r["pairs_analyzed"]) and (
  356. (r["pairs_removed"] + r["r1_unpaired"] + r["r2_unpaired"]) / r["pairs_analyzed"] > 0.001
  357. )
  358. rows = {n: r for n, r in all_rows.items() if _kept(r)}
  359. dropped = sorted(n for n, r in all_rows.items() if not _kept(r))
  360. if not rows:
  361. return None, dropped
  362. headers: Dict[str, Dict[str, Any]] = {
  363. "pairs_analyzed": {
  364. "title": "Pairs analysed",
  365. "description": "Total read pairs examined by pair validation",
  366. "scale": "Greys",
  367. "shared_key": "read_count",
  368. },
  369. "pairs_removed": {
  370. "title": "Pairs removed",
  371. "description": "Total read pairs removed by pair validation",
  372. "scale": "OrRd",
  373. "shared_key": "read_count",
  374. },
  375. "pairs_removed_n": {
  376. "title": "Pairs removed (N content)",
  377. "description": "Read pairs removed because they exceeded the N-content threshold",
  378. "scale": "OrRd",
  379. "shared_key": "read_count",
  380. },
  381. "pairs_removed_too_long": {
  382. "title": "Pairs removed (too long)",
  383. "description": "Read pairs removed because one or both reads exceeded the maximum length",
  384. "scale": "OrRd",
  385. "shared_key": "read_count",
  386. },
  387. "r1_unpaired": {
  388. "title": "R1 unpaired",
  389. "description": "R1 reads left unpaired after their R2 partner was discarded",
  390. "scale": "Oranges",
  391. "shared_key": "read_count",
  392. },
  393. "r2_unpaired": {
  394. "title": "R2 unpaired",
  395. "description": "R2 reads left unpaired after their R1 partner was discarded",
  396. "scale": "Oranges",
  397. "shared_key": "read_count",
  398. },
  399. }
  400. return (
  401. table.plot(
  402. rows,
  403. cast(Any, headers),
  404. pconfig={
  405. "namespace": self.name,
  406. "id": "trim_galore_pair_validation_table",
  407. "title": "Trim Galore: Pair Validation",
  408. },
  409. ),
  410. dropped,
  411. )
  412. def _poly_trimming_plot(self, data_by_sample: Dict[str, Dict[str, Any]]):
  413. rows: Dict[str, Dict[str, int]] = {}
  414. dropped: List[str] = []
  415. for s_name, payload in data_by_sample.items():
  416. pa = payload["poly_a_trimming"]
  417. pg = payload["poly_g_trimming"]
  418. row = {
  419. "poly_a_reads_trimmed": pa["reads_trimmed"],
  420. "poly_a_bases_removed": pa["bases_removed"],
  421. "poly_g_reads_trimmed": pg["reads_trimmed"],
  422. "poly_g_bases_removed": pg["bases_removed"],
  423. }
  424. if any(row.values()):
  425. rows[s_name] = row
  426. else:
  427. dropped.append(s_name)
  428. if not rows:
  429. return None, sorted(dropped)
  430. headers: Dict[str, Dict[str, Any]] = {
  431. "poly_a_reads_trimmed": {
  432. "title": "Poly-A reads trimmed",
  433. "description": "Reads with a poly-A tail trimmed",
  434. "scale": "Purples",
  435. "shared_key": "read_count",
  436. },
  437. "poly_a_bases_removed": {
  438. "title": "Poly-A bp removed",
  439. "description": "Bases removed by poly-A trimming",
  440. "scale": "Purples",
  441. "shared_key": "base_count",
  442. },
  443. "poly_g_reads_trimmed": {
  444. "title": "Poly-G reads trimmed",
  445. "description": "Reads with a poly-G tail trimmed",
  446. "scale": "Greens",
  447. "shared_key": "read_count",
  448. },
  449. "poly_g_bases_removed": {
  450. "title": "Poly-G bp removed",
  451. "description": "Bases removed by poly-G trimming",
  452. "scale": "Greens",
  453. "shared_key": "base_count",
  454. },
  455. }
  456. return (
  457. table.plot(
  458. rows,
  459. cast(Any, headers),
  460. pconfig={
  461. "namespace": self.name,
  462. "id": "trim_galore_poly_trimming_table",
  463. "title": "Trim Galore: Poly-A / Poly-G Trimming",
  464. },
  465. ),
  466. sorted(dropped),
  467. )
  468. def _rrbs_plot(self, data_by_sample: Dict[str, Dict[str, Any]]):
  469. rows: Dict[str, Dict[str, int]] = {}
  470. dropped: List[str] = []
  471. for s_name, payload in data_by_sample.items():
  472. rr = payload["rrbs"]
  473. row = {
  474. "rrbs_trimmed_3prime": rr["trimmed_3prime"],
  475. "rrbs_trimmed_5prime": rr["trimmed_5prime"],
  476. "rrbs_r2_clipped_5prime": rr["r2_clipped_5prime"],
  477. }
  478. if any(row.values()):
  479. rows[s_name] = row
  480. else:
  481. dropped.append(s_name)
  482. if not rows:
  483. return None, sorted(dropped)
  484. headers: Dict[str, Dict[str, Any]] = {
  485. "rrbs_trimmed_3prime": {
  486. "title": "3' trimmed",
  487. "description": "Reads trimmed at the 3' end for RRBS end-repair",
  488. "scale": "Blues",
  489. "shared_key": "read_count",
  490. },
  491. "rrbs_trimmed_5prime": {
  492. "title": "5' trimmed",
  493. "description": "Reads trimmed at the 5' end for RRBS end-repair",
  494. "scale": "Blues",
  495. "shared_key": "read_count",
  496. },
  497. "rrbs_r2_clipped_5prime": {
  498. "title": "R2 5' clipped",
  499. "description": "R2 reads with the 5' end clipped (directional RRBS)",
  500. "scale": "Blues",
  501. "shared_key": "read_count",
  502. },
  503. }
  504. return (
  505. table.plot(
  506. rows,
  507. cast(Any, headers),
  508. pconfig={
  509. "namespace": self.name,
  510. "id": "trim_galore_rrbs_table",
  511. "title": "Trim Galore: RRBS Trimming",
  512. },
  513. ),
  514. sorted(dropped),
  515. )
  516. def _flatten_for_data_file(data_by_sample: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
  517. flat: Dict[str, Dict[str, Any]] = {}
  518. for s_name, p in data_by_sample.items():
  519. rp = p["read_processing"]
  520. bp = p["basepair_processing"]
  521. pa = p["poly_a_trimming"]
  522. pg = p["poly_g_trimming"]
  523. rr = p["rrbs"]
  524. # `pair_validation` is explicitly null for SE samples — keep `or {}`
  525. # so the .get() calls below produce None for the SE pair columns.
  526. pv = p["pair_validation"] or {}
  527. flat[s_name] = {
  528. "trim_galore_version": p["trim_galore_version"],
  529. "mode": p["mode"],
  530. "total_reads": rp["total_reads"],
  531. "reads_with_adapter": rp["reads_with_adapter"],
  532. "reads_written": rp["reads_written"],
  533. "reads_too_short": rp["reads_too_short"],
  534. "reads_too_long": rp["reads_too_long"],
  535. "reads_too_many_n": rp["reads_too_many_n"],
  536. "reads_discarded_untrimmed": rp["reads_discarded_untrimmed"],
  537. "total_bp_processed": bp["total_bp_processed"],
  538. "total_bp_written": bp["total_bp_written"],
  539. "quality_trimmed_bp": bp["quality_trimmed_bp"],
  540. "pairs_analyzed": pv.get("pairs_analyzed"),
  541. "pairs_removed": pv.get("pairs_removed"),
  542. "pairs_removed_n": pv.get("pairs_removed_n"),
  543. "pairs_removed_too_long": pv.get("pairs_removed_too_long"),
  544. "r1_unpaired": pv.get("r1_unpaired"),
  545. "r2_unpaired": pv.get("r2_unpaired"),
  546. "poly_a_reads_trimmed": pa["reads_trimmed"],
  547. "poly_a_bases_removed": pa["bases_removed"],
  548. "poly_g_reads_trimmed": pg["reads_trimmed"],
  549. "poly_g_bases_removed": pg["bases_removed"],
  550. "rrbs_trimmed_3prime": rr["trimmed_3prime"],
  551. "rrbs_trimmed_5prime": rr["trimmed_5prime"],
  552. "rrbs_r2_clipped_5prime": rr["r2_clipped_5prime"],
  553. }
  554. return flat
  555. def _safe_pct(part: int, total: int) -> float:
  556. if not total:
  557. return 0.0
  558. return 100.0 * part / total

trim_galore.py at commit dc565eb, under GPL-3.0 · at the source

Overview

Authors: Leonor Miller-Fleming1, Wing Hei Au1, Alexander J Whitworth1
  1. MRC Mitochondrial Biology Unit, University of Cambridge, Cambridge Biomedical Campus, Cambridge CB2 0XY, UK
Institutions: University of Cambridge (United Kingdom); MRC Mitochondrial Biology Unit (United Kingdom)
Journal: iScience, volume 29, issue 5, article 115530
Dates: received 9 October 2025; accepted 26 March 2026; published online 30 March 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.isci.2026.115530 · PMID 42028032 · PMCID PMC13099348 · OpenAlex W7143345098
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: drosophila (organism), cellular / molecular (subfield)
Methods: Statistics, Evoked potentials
Keywords: genetics, molecular biology, neuroscience
Topic: Toxin Mechanisms and Immunotoxins (Immunology, Immunology and Microbiology), according to OpenAlex
Funding: UK Research and Innovation Medical Research Council (MC_UU_0028/6, MR/V003933/1); Motor Neurone Disease Association (Whitworth/Apr17/857-79); Medical Research Center, Hamad Medical Corporation; European Research Council (309742)
Citations: not cited yet (Europe PMC); 42 references in the paper
Research resources: Monoclonal mouse anti-FLAG M2 antibody RRID:AB_262044, RRID:AB_95543, D. melanogaster: UAS-GFP RRID:BDSC_1522, D. melanogaster: OK371-GAL4 RRID:BDSC_26160, D. melanogaster: UAS-mCD8-GFP RRID:BDSC_5137, D. melanogaster: UAS-NLS-RFP RRID:BDSC_8547, RRID:BDSC_90315, D. melanogaster: UAS-Flag-TEV-GFP-RpL10 RRID:FlyBase_FBal0319196

Abstract

Localized protein translation enables spatially restricted cellular dynamics, particularly in neurons, where specific mRNAs are translated in axons and dendrites far from the cell body. Translating ribosome affinity purification (TRAP) has been used to study axonal translation in rodents and cell-type-specific translation in Drosophila, but existing protocols are not optimized for axons, where material is extremely limited. Here, we present a highly sensitive TRAP protocol for isolating ribosome-bound mRNAs from low-input samples, enabling recovery of axonal mRNAs from Drosophila larval and adult (leg) motor neurons. RNA-seq identified axonally translated transcripts, including mRNAs encoding ribosomal and mitochondrial proteins, similar to those reported in axons of other species, indicating conservation of axonal translation in Drosophila. This low-input method enables analysis of local translation with Drosophila genetics across developmental stages, genetic backgrounds, and disease models, and can be adapted for rare genotypes, other tissues and model systems requiring high sensitivity.

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 3 matches between paragraphs and lines of code.

MultiQC/MultiQC

License: GPL-3.0
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: dc565eb9eef7b22a289ab7eec78e5efb6bf297c3, 28 September 2026
Languages: Python (647), JavaScript (75), Shell (1)
Size: 1,215 files, 723 scripts
Software Heritage: archived
Found in: the resources table
Holds: README, license file, CITATION.cff, environment (Dockerfile, flake.nix, pyproject.toml, setup.py), tests, continuous integration, documentation
Tools: NumPy (14 files), Plotly (8 files), Pillow (2 files)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
725 files

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;
  • 723 scripts, each with its path and the digest of its content;
  • 3 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

Datasets cited

Data and code availability

RNA-seq datasets have been deposited at GEO (GEO: GSE313171 (https://ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE313171)).

This paper does not report original code and data analysis methods are described in the STAR Methods.

Any additional information required to re-analyse the data reported in this study is available from the lead contact upon request.

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, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 3 authors, 3 keywords, 4 funders, 39 references, 8 RRIDs.

Cite

This paper

Miller-Fleming, L., Au, W. H., & Whitworth, A. J. (2026). An optimized translating ribosome affinity purification protocol for low-abundance <i>Drosophila</i> tissues. iScience, 29(5), 115530. https://doi.org/10.1016/j.isci.2026.115530

BibTeX

@article{millerfleming2026optimized,
author = {Miller-Fleming, Leonor and Au, Wing Hei and Whitworth, Alexander J},
title = {{An optimized translating ribosome affinity purification protocol for low-abundance \<i\>Drosophila\</i\> tissues}},
journal = {iScience},
year = {2026},
month = mar,
volume = {29},
number = {5},
pages = {115530},
publisher = {Elsevier},
issn = {2589-0042},
doi = {10.1016/j.isci.2026.115530},
url = {https://doi.org/10.1016/j.isci.2026.115530},
pmid = {42028032},
pmcid = {PMC13099348}
}

RIS

TY - JOUR
AU - Miller-Fleming, Leonor
AU - Au, Wing Hei
AU - Whitworth, Alexander J
TI - An optimized translating ribosome affinity purification protocol for low-abundance <i>Drosophila</i> tissues
T2 - iScience
J2 - iScience
PY - 2026
DA - 2026/03/30
VL - 29
IS - 5
SP - 115530
SN - 2589-0042
PB - Elsevier
DO - 10.1016/j.isci.2026.115530
UR - https://doi.org/10.1016/j.isci.2026.115530
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.isci.2026.115530",
"type": "article-journal",
"title": "An optimized translating ribosome affinity purification protocol for low-abundance <i>Drosophila</i> tissues",
"container-title": "iScience",
"author": [
{
"family": "Miller-Fleming",
"given": "Leonor"
},
{
"family": "Au",
"given": "Wing Hei"
},
{
"family": "Whitworth",
"given": "Alexander J"
}
],
"container-title-short": "iScience",
"volume": "29",
"issue": "5",
"page": "115530",
"DOI": "10.1016/j.isci.2026.115530",
"PMID": "42028032",
"PMCID": "PMC13099348",
"ISSN": "2589-0042",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.isci.2026.115530",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
30
]
]
}
}

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.26508/lsa.202503571
Reticulon-1 synthesis controls outgrowth and microtubule dynamics in injured cortical axons.
Journal: Life science alliance
In common: cellular / molecular, 5 references
[2] doi:10.1261/rna.080954.126 [code]
Neuronal subtype-specific ribosomal protein mRNA expression.
Journal: RNA (New York, N.Y.)
In common: NumPy, cellular / molecular, 4 references
[3] doi:10.26508/lsa.202603630 [code]
Locally synthesized glycyl aminoacyl-tRNA synthetase is important for local translation in neurons.
Journal: Life science alliance
In common: NumPy, cellular / molecular, 4 references
[4] doi:10.1038/s41593-024-01619-1
Messenger RNA transport on lysosomal vesicles maintains axonal mitochondrial homeostasis and prevents axonal degeneration.
Journal: Nature neuroscience
In common: cellular / molecular, 5 references
[5] doi:10.1038/s41593-025-02062-6
Muscle-derived miR-126 regulates TDP-43 axonal local synthesis and NMJ integrity in ALS models.
Journal: Nature neuroscience
In common: cellular / molecular, 5 references
[6] doi:10.1038/s41467-023-40142-y [code]
Glial Draper signaling triggers cross-neuron plasticity in bystander neurons after neuronal cell death in Drosophila.
Journal: Nature communications
In common: NumPy, drosophila, cellular / molecular, 3 references
[7] doi:10.1093/narmme/ugaf005 [code]
Neuromuscular dysfunction in patient-derived FUS<sup>R244RR</sup>-ALS iPSC model via axonal downregulation of neuromuscular junction proteins.
Journal: NAR molecular medicine
In common: Pillow, NumPy, 3 references
[8] doi:10.1073/pnas.2502424122 [code]
Neuronal processes contain the essential components for the late steps of ribosome biogenesis.
Journal: Proceedings of the National Academy of Sciences of the United States of America
In common: NumPy, cellular / molecular, 4 references
[9] doi:10.1098/rsob.250101 [code]
Unravelling axonal transcriptional landscapes: insights from induced pluripotent stem cell-derived cortical neurons and implications for motor neuron degeneration.
Journal: Open biology
In common: cellular / molecular, 4 references
[10] doi:10.1186/s12929-024-01061-2
CPEB2-activated axonal translation of VGLUT2 mRNA promotes glutamatergic transmission and presynaptic plasticity.
Journal: Journal of biomedical science
In common: cellular / molecular, 4 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.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1016/j.isci.2026.115530.svg)](https://openscicode.org/badge/doi/10.1016/j.isci.2026.115530)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1016/j.isci.2026.115530.svg
   :target: https://openscicode.org/badge/doi/10.1016/j.isci.2026.115530
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1016/j.isci.2026.115530"><img src="https://openscicode.org/badge/doi/10.1016/j.isci.2026.115530.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.

Request its removal

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

Discussion, reproductions, activity

Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

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