OSCR

Recurrent DNA break clusters drive replication-stress-induced copy number variants and genome diversification.

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. [1] § Methods › Alignment, QC, and CNV calling ↔ src/coral.h, lines 500–584 · score 0.60 · CNV calling, mapping quality, ploidy, Somatic, depth, genotyped
  2. [2] § Methods › Alignment, QC, and CNV calling ↔ workflow/scripts/haplotagging_scripts/haplotagTable.R, lines 16–63 · score 0.57 · findOverlaps, GRanges, exported, BED, Overlapping, filtered
  3. [3] § Methods › Demultiplexing and alignment ↔ transloc_pipeline_v3/bin/TranslocPipeline.pl, lines 1078–1147 · score 0.52 · TranslocWrapper, Bowtie2, pipeline, adapters, prey, bait

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

C/C++ header · 933 lines · 35 KB · BSD-3-Clause · 1 match

  1. #ifndef CORAL_H
  2. #define CORAL_H
  3. #include <limits>
  4. #include <iomanip>
  5. #include <boost/icl/split_interval_map.hpp>
  6. #include <boost/dynamic_bitset.hpp>
  7. #include <boost/unordered_map.hpp>
  8. #include <boost/date_time/posix_time/posix_time.hpp>
  9. #include <boost/date_time/gregorian/gregorian.hpp>
  10. #include <boost/math/special_functions/round.hpp>
  11. #include <htslib/sam.h>
  12. #include <htslib/faidx.h>
  13. #include "bed.h"
  14. #include "scan.h"
  15. #include "gcbias.h"
  16. #include "cnv.h"
  17. #include "version.h"
  18. namespace torali
  19. {
  20. struct CountDNAConfig {
  21. bool basecov;
  22. bool somatic;
  23. bool adaptive;
  24. bool hasStatsFile;
  25. bool hasScanFile;
  26. bool noScanWindowSelection;
  27. bool regionalGc;
  28. bool hasSegFile;
  29. bool hasGenoFile;
  30. bool hasExcludeFile;
  31. uint32_t nchr;
  32. uint32_t minClip;
  33. uint32_t minRefSep;
  34. uint32_t minBpSupport;
  35. float cnMergeTol;
  36. float penalty;
  37. uint32_t meanisize;
  38. uint32_t window_size;
  39. uint32_t window_offset;
  40. uint32_t scanWindow;
  41. uint32_t minChrLen;
  42. uint32_t minCnvSize;
  43. uint32_t targetReads;
  44. uint32_t minSegWin;
  45. double targetExpCov;
  46. uint16_t minQual;
  47. uint16_t mapqUniq;
  48. uint16_t mad;
  49. float ploidy;
  50. float ctrlPloidy;
  51. float expectedCN;
  52. float purity;
  53. float exclgc;
  54. float uniqueToTotalCovRatio;
  55. float fracWindow;
  56. float fragmentUnique;
  57. int32_t cnvMinQual;
  58. float cnvDelConfirm;
  59. float cnvDelRatio;
  60. std::string sampleName;
  61. boost::filesystem::path segfile;
  62. boost::filesystem::path genofile;
  63. boost::filesystem::path outfile;
  64. boost::filesystem::path covfile;
  65. boost::filesystem::path genome;
  66. boost::filesystem::path statsFile;
  67. boost::filesystem::path bamFile;
  68. boost::filesystem::path scanFile;
  69. boost::filesystem::path exclude;
  70. std::set<int32_t> refIdx;
  71. };
  72. struct CovWin {
  73. uint32_t start;
  74. uint32_t end;
  75. uint32_t winlen;
  76. double covsum;
  77. double expcov;
  78. double ucov;
  79. double tcov;
  80. double aall;
  81. double eall;
  82. bool valid;
  83. CovWin(uint32_t const s, uint32_t const e, uint32_t const w, double const cs, double const ec, double const uc, double const tc, double const aa, double const ea, bool vld) : start(s), end(e), winlen(w), covsum(cs), expcov(ec), ucov(uc), tcov(tc), aall(aa), eall(ea), valid(vld) {}
  84. };
  85. struct CountDNAConfigLib {
  86. uint16_t madCutoff;
  87. uint16_t madNormalCutoff;
  88. boost::filesystem::path genome;
  89. std::vector<boost::filesystem::path> files;
  90. };
  91. template<typename TConfig>
  92. inline int32_t
  93. bamCount(TConfig const& c, LibraryInfo const& li, std::vector<GcBias> const& gcbias, std::pair<uint32_t, uint32_t> const& gcbound, std::vector<double> const& regcorr, uint32_t const regWin) {
  94. // Load bam file
  95. samFile* samfile = sam_open(c.bamFile.string().c_str(), "r");
  96. hts_set_fai_filename(samfile, c.genome.string().c_str());
  97. hts_idx_t* idx = sam_index_load(samfile, c.bamFile.string().c_str());
  98. bam_hdr_t* hdr = sam_hdr_read(samfile);
  99. // Parse BAM file
  100. boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
  101. std::cerr << '[' << boost::posix_time::to_simple_string(now) << "] " << "Count fragments" << std::endl;
  102. // Open output files
  103. boost::iostreams::filtering_ostream dataOut;
  104. if (!c.covfile.empty()) {
  105. dataOut.push(boost::iostreams::gzip_compressor());
  106. dataOut.push(boost::iostreams::file_sink(c.covfile.string(), std::ios_base::out | std::ios_base::binary));
  107. dataOut << "chr\tstart\tend\t" << c.sampleName << "_uniqfrac\t" << c.sampleName << "_logR\t" << c.sampleName << "_CN" << std::endl;
  108. }
  109. // CNVs
  110. std::vector<CNV> cnvs;
  111. if (c.hasGenoFile) parseVcfCNV(c, hdr, cnvs);
  112. // Iterate chromosomes
  113. faidx_t* faiRef = fai_load(c.genome.string().c_str());
  114. for(int32_t refIndex=0; refIndex < (int32_t) hdr->n_targets; ++refIndex) {
  115. if ((!c.hasGenoFile) && (chrNoData(c, refIndex, idx))) continue;
  116. // Haploid chromosome?
  117. float chrCtrlPloidy = c.ctrlPloidy;
  118. float chrPloidy = c.ploidy;
  119. if (c.refIdx.find(refIndex) != c.refIdx.end()) {
  120. chrCtrlPloidy = c.ctrlPloidy - 1;
  121. chrPloidy = c.ploidy - 1;
  122. }
  123. // Reference sequence
  124. std::string tname(hdr->target_name[refIndex]);
  125. int32_t seqlen = faidx_seq_len(faiRef, tname.c_str());
  126. if (seqlen == - 1) continue;
  127. else seqlen = -1;
  128. char* ref = faidx_fetch_seq(faiRef, tname.c_str(), 0, faidx_seq_len(faiRef, tname.c_str()), &seqlen);
  129. if (ref == NULL) continue;
  130. // Get GC content
  131. std::vector<uint16_t> uniqContent(hdr->target_len[refIndex], 0);
  132. std::vector<uint16_t> gcContent(hdr->target_len[refIndex], 0);
  133. {
  134. // GC map
  135. typedef boost::dynamic_bitset<> TBitSet;
  136. TBitSet gcref(hdr->target_len[refIndex], false);
  137. for(uint32_t i = 0; i < hdr->target_len[refIndex]; ++i) {
  138. if ((ref[i] == 'c') || (ref[i] == 'C') || (ref[i] == 'g') || (ref[i] == 'G')) gcref[i] = 1;
  139. }
  140. // Sum across fragment
  141. int32_t halfwin = (int32_t) (c.meanisize / 2);
  142. int32_t gcsum = 0;
  143. for(int32_t pos = halfwin; pos < (int32_t) hdr->target_len[refIndex] - halfwin; ++pos) {
  144. if (pos == halfwin) {
  145. for(int32_t i = pos - halfwin; i<=pos+halfwin; ++i) gcsum += gcref[i];
  146. } else {
  147. gcsum -= gcref[pos - halfwin - 1];
  148. gcsum += gcref[pos + halfwin];
  149. }
  150. gcContent[pos] = gcsum;
  151. }
  152. }
  153. // Broad window for replication wave
  154. std::vector<float> tileFac;
  155. if ((!regcorr.empty()) && (regWin > 0)) {
  156. uint32_t ntile = hdr->target_len[refIndex] / regWin + 1;
  157. tileFac.resize(ntile, 1.0);
  158. for(uint32_t t = 0; t < ntile; ++t) {
  159. uint32_t s = t * regWin;
  160. uint32_t e = std::min((uint32_t) hdr->target_len[refIndex], s + regWin);
  161. double gcnum = 0;
  162. uint32_t winlen = 0;
  163. for(uint32_t pos = s; pos < e; ++pos) {
  164. if ((gcContent[pos] > gcbound.first) && (gcContent[pos] < gcbound.second)) { gcnum += gcContent[pos]; ++winlen; }
  165. }
  166. if (winlen > 0) tileFac[t] = (float) regCorrFactor(regcorr, (gcnum / (double) winlen) / (double) c.meanisize);
  167. }
  168. }
  169. // Coverage track
  170. typedef uint16_t TCount;
  171. uint32_t maxCoverage = std::numeric_limits<TCount>::max();
  172. typedef std::vector<TCount> TCoverage;
  173. TCoverage cov(hdr->target_len[refIndex], 0);
  174. TCoverage covUniq(hdr->target_len[refIndex], 0);
  175. TCoverage covAll(hdr->target_len[refIndex], 0);
  176. TCoverage covTot;
  177. if (!c.basecov) covTot.resize(hdr->target_len[refIndex], 0);
  178. TCoverage& covMap = (!c.basecov) ? covTot : cov;
  179. // Split-read breakpoints
  180. std::vector<int32_t> clips;
  181. {
  182. // Mate map
  183. typedef boost::unordered_map<std::size_t, bool> TMateMap;
  184. TMateMap mateMap;
  185. // Count reads
  186. hts_itr_t* iter = sam_itr_queryi(idx, refIndex, 0, hdr->target_len[refIndex]);
  187. bam1_t* rec = bam_init1();
  188. int32_t lastAlignedPos = 0;
  189. std::set<std::size_t> lastAlignedPosReads;
  190. while (sam_itr_next(samfile, iter, rec) >= 0) {
  191. if (rec->core.flag & (BAM_FQCFAIL | BAM_FDUP | BAM_FUNMAP | BAM_FSECONDARY | BAM_FSUPPLEMENTARY)) continue;
  192. if ((rec->core.flag & BAM_FPAIRED) && ((rec->core.flag & BAM_FMUNMAP) || (rec->core.tid != rec->core.mtid))) continue;
  193. // Base coverage
  194. addBaseCoverage3(rec, covAll, covMap, covUniq, c.minQual, c.mapqUniq, hdr->target_len[refIndex], maxCoverage);
  195. // Filter by quality
  196. if (rec->core.qual < c.minQual) continue;
  197. // Collect split-read breakpoints
  198. if (rec->core.qual >= c.mapqUniq) addSplitReadBreakpoints(rec, c.minClip, c.minRefSep, hdr->target_len[refIndex], clips);
  199. // Base-level counting done
  200. if (c.basecov) continue;
  201. // Fragment coverage
  202. int32_t midPoint = rec->core.pos + halfAlignmentLength(rec);
  203. if (rec->core.flag & BAM_FPAIRED) {
  204. std::size_t seed = hash_sr(rec);
  205. // Clean-up the read store for identical alignment positions
  206. if (rec->core.pos > lastAlignedPos) {
  207. lastAlignedPosReads.clear();
  208. lastAlignedPos = rec->core.pos;
  209. }
  210. if (_firstPairObs(rec, lastAlignedPosReads)) {
  211. // First read
  212. lastAlignedPosReads.insert(seed);
  213. std::size_t hv = hash_pair(rec);
  214. mateMap[hv] = true;
  215. continue;
  216. } else {
  217. // Second read
  218. std::size_t hv = hash_pair_mate(rec);
  219. auto itMM = mateMap.find(hv);
  220. if ((itMM == mateMap.end()) || (!itMM->second)) continue; // Mate discarded
  221. mateMap.erase(itMM);
  222. }
  223. // update midpoint
  224. int32_t isize = (rec->core.pos + alignmentLength(rec)) - rec->core.mpos;
  225. if ((li.minNormalISize < isize) && (isize < li.maxNormalISize)) midPoint = rec->core.mpos + (int32_t) (isize/2);
  226. }
  227. // Count fragment
  228. if ((midPoint >= 0) && (midPoint < (int32_t) hdr->target_len[refIndex]) && (cov[midPoint] < maxCoverage - 1)) ++cov[midPoint];
  229. }
  230. // Clean-up
  231. bam_destroy1(rec);
  232. hts_itr_destroy(iter);
  233. }
  234. // Callable positions: unique if mostly high-MAPQ reads
  235. for(uint32_t pos = 0; pos < hdr->target_len[refIndex]; ++pos) {
  236. bool u;
  237. if (covMap[pos] == 0) u = ((ref[pos] != 'N') && (ref[pos] != 'n'));
  238. else u = (2 * (uint32_t) covUniq[pos] >= (uint32_t) covMap[pos]);
  239. uniqContent[pos] = (u ? (uint16_t) c.meanisize : 0);
  240. }
  241. // hom-del or unmappable?
  242. uint32_t maxHomDel = 1000000;
  243. uint32_t rstart = 0;
  244. while (rstart < hdr->target_len[refIndex]) {
  245. if (covMap[rstart] == 0) {
  246. uint32_t rend = rstart;
  247. while ((rend < hdr->target_len[refIndex]) && (covMap[rend] == 0)) ++rend;
  248. bool leftOK = (rstart > 0) && (uniqContent[rstart - 1] > 0);
  249. bool rightOK = (rend < hdr->target_len[refIndex]) && (uniqContent[rend] > 0);
  250. if ((!leftOK) || (!rightOK) || (rend - rstart > maxHomDel)) {
  251. for(uint32_t k = rstart; k < rend; ++k) uniqContent[k] = 0;
  252. }
  253. rstart = rend;
  254. } else ++rstart;
  255. }
  256. // Genome-wide read-depth windows
  257. DepthTrack const dt = uniqueTrack();
  258. std::vector<CovWin> wins;
  259. if (c.adaptive) {
  260. double covsum = 0;
  261. double expraw = 0;
  262. double expcor = 0;
  263. double ucov = 0;
  264. double tcov = 0;
  265. double aall = 0;
  266. double eall = 0;
  267. uint32_t winlen = 0;
  268. uint32_t start = 0;
  269. for(uint32_t pos = 0; pos < hdr->target_len[refIndex]; ++pos) {
  270. ucov += covUniq[pos];
  271. tcov += covMap[pos];
  272. if ((gcContent[pos] > gcbound.first) && (gcContent[pos] < gcbound.second)) {
  273. aall += covAll[pos];
  274. eall += gcbias[gcContent[pos]].coverageTotal;
  275. }
  276. if (_posUsed(dt, c, gcContent, uniqContent, gcbound, pos)) {
  277. double e1 = _expCov(dt, gcbias, gcContent[pos]);
  278. covsum += cov[pos];
  279. expraw += e1;
  280. expcor += e1 * (tileFac.empty() ? 1.0 : (double) tileFac[pos / regWin]);
  281. ++winlen;
  282. if (expraw >= c.targetExpCov) {
  283. wins.push_back(CovWin(start, pos + 1, winlen, covsum, expcor, ucov, tcov, aall, eall, true));
  284. covsum = 0;
  285. expraw = 0;
  286. expcor = 0;
  287. ucov = 0;
  288. tcov = 0;
  289. aall = 0;
  290. eall = 0;
  291. winlen = 0;
  292. start = pos + 1;
  293. }
  294. }
  295. }
  296. } else {
  297. // Fixed windows (non-overlapping)
  298. for(uint32_t start = 0; start < hdr->target_len[refIndex]; start = start + c.window_offset) {
  299. if (start + c.window_size < hdr->target_len[refIndex]) {
  300. double covsum = 0;
  301. double expcov = 0;
  302. double ucov = 0;
  303. double tcov = 0;
  304. double aall = 0;
  305. double eall = 0;
  306. uint32_t winlen = 0;
  307. for(uint32_t pos = start; pos < start + c.window_size; ++pos) {
  308. ucov += covUniq[pos];
  309. tcov += covMap[pos];
  310. if ((gcContent[pos] > gcbound.first) && (gcContent[pos] < gcbound.second)) {
  311. aall += covAll[pos];
  312. eall += gcbias[gcContent[pos]].coverageTotal;
  313. }
  314. if (_posUsed(dt, c, gcContent, uniqContent, gcbound, pos)) {
  315. covsum += cov[pos];
  316. expcov += _expCov(dt, gcbias, gcContent[pos]) * (tileFac.empty() ? 1.0 : (double) tileFac[pos / regWin]);
  317. ++winlen;
  318. }
  319. }
  320. bool valid = (winlen >= c.fracWindow * c.window_size);
  321. wins.push_back(CovWin(start, start + c.window_size, winlen, covsum, expcov, ucov, tcov, aall, eall, valid));
  322. }
  323. }
  324. }
  325. // Separate true hom. dels from unmappable
  326. uint32_t nw = wins.size();
  327. std::vector<bool> naFlag(nw, false);
  328. std::vector<bool> suspect(nw, false);
  329. std::vector<bool> strong(nw, false);
  330. double lowFrac = 0.1; // close to CN0
  331. double flankFrac = 0.5; // clear no CN0
  332. uint32_t maxHomDelWin = 1000000; // max size for hom. DEL
  333. for(uint32_t i = 0; i < nw; ++i) {
  334. if ((!wins[i].valid) || (wins[i].expcov <= 0)) {
  335. naFlag[i] = true;
  336. continue;
  337. }
  338. double r = wins[i].covsum / wins[i].expcov;
  339. suspect[i] = (r < lowFrac);
  340. strong[i] = (r >= flankFrac);
  341. }
  342. for(uint32_t i = 0; i < nw; ) {
  343. if (naFlag[i] || (!suspect[i])) {
  344. ++i;
  345. continue;
  346. }
  347. uint32_t a = i;
  348. uint32_t b = i;
  349. while ((b + 1 < nw) && (!naFlag[b+1]) && suspect[b+1]) ++b;
  350. uint32_t runBp = wins[b].end - wins[a].start;
  351. bool leftStrong = (a > 0) && (!naFlag[a-1]) && strong[a-1];
  352. bool rightStrong = (b + 1 < nw) && (!naFlag[b+1]) && strong[b+1];
  353. bool keepDel = leftStrong && rightStrong && (runBp <= maxHomDelWin);
  354. if (!keepDel) {
  355. for(uint32_t k = a; k <= b; ++k) naFlag[k] = true;
  356. }
  357. i = b + 1;
  358. }
  359. // Total-depth deletion signal
  360. double delRatio = c.cnvDelRatio;
  361. // Flag non-unique windows
  362. bool uniqGate = c.basecov;
  363. if (uniqGate) {
  364. for(uint32_t i = 0; i < nw; ++i) {
  365. if (naFlag[i]) continue;
  366. double rTot = (wins[i].eall > 0) ? (wins[i].aall / wins[i].eall) : 1.0;
  367. if (rTot < delRatio) continue;
  368. if ((wins[i].tcov > 0) && (wins[i].ucov <= c.uniqueToTotalCovRatio * wins[i].tcov)) naFlag[i] = true;
  369. }
  370. }
  371. // Flag low callable windows
  372. if ((c.adaptive) && (nw > 4)) {
  373. std::vector<int32_t> spans(nw);
  374. for(uint32_t i = 0; i < nw; ++i) spans[i] = (int32_t) (wins[i].end - wins[i].start);
  375. std::vector<int32_t> tmp(spans);
  376. std::sort(tmp.begin(), tmp.end());
  377. int32_t medSpan = tmp[tmp.size() / 2];
  378. for(uint32_t i = 0; i < nw; ++i) tmp[i] = std::abs(spans[i] - medSpan);
  379. std::sort(tmp.begin(), tmp.end());
  380. int32_t madSpan = tmp[tmp.size() / 2];
  381. int32_t maxSpan = std::max(medSpan + (int32_t) c.mad * madSpan, 2 * medSpan);
  382. for(uint32_t i = 0; i < nw; ++i) {
  383. if (spans[i] > maxSpan) {
  384. double rTot = (wins[i].eall > 0) ? (wins[i].aall / wins[i].eall) : 1.0;
  385. if (rTot >= delRatio) naFlag[i] = true;
  386. }
  387. }
  388. }
  389. // Exclude NA windows
  390. std::vector<std::pair<int32_t, int32_t> > naiv;
  391. for(uint32_t i = 0; i < nw; ++i) {
  392. if (naFlag[i]) {
  393. for(uint32_t k = wins[i].start; k < wins[i].end; ++k) uniqContent[k] = 0;
  394. int32_t nas = (int32_t) wins[i].start;
  395. int32_t nae = (int32_t) wins[i].end;
  396. if ((!naiv.empty()) && (naiv.back().second >= nas)) naiv.back().second = std::max(naiv.back().second, nae);
  397. else naiv.push_back(std::make_pair(nas, nae));
  398. }
  399. }
  400. // Split-read breakpoints
  401. std::vector<SVBreakpoint> chrbp;
  402. collectBreakpoints(c, gcbound, gcContent, uniqContent, gcbias, cov, hdr, refIndex, clips, chrbp);
  403. // CNV discovery
  404. if (!c.hasGenoFile) segmentRD(c, gcbound, gcContent, uniqContent, gcbias, tileFac, regWin, cov, hdr, refIndex, chrbp, uniqueTrack(), naiv, cnvs);
  405. // CNV genotyping
  406. genotypeCNVs(c, gcbound, gcContent, uniqContent, gcbias, tileFac, regWin, cov, covUniq, covMap, covAll, ref, hdr, refIndex, cnvs);
  407. if (ref != NULL) free(ref);
  408. // Write windows
  409. if (!c.covfile.empty()) {
  410. std::string chrn(hdr->target_name[refIndex]);
  411. for(uint32_t i = 0; i < nw; ++i) {
  412. double uniqFrac;
  413. if (uniqGate) uniqFrac = (wins[i].tcov > 0) ? (wins[i].ucov / wins[i].tcov) : -1.0;
  414. else uniqFrac = (wins[i].end > wins[i].start) ? ((double) wins[i].winlen / (double) (wins[i].end - wins[i].start)) : -1.0;
  415. if (naFlag[i]) {
  416. dataOut << chrn << "\t" << wins[i].start << "\t" << wins[i].end << "\t" << uniqFrac << "\tNA\tNA" << std::endl;
  417. } else {
  418. double cn = chrPloidy;
  419. double logR = 0;
  420. if (wins[i].expcov > 0) {
  421. cn = (c.expectedCN * wins[i].covsum / wins[i].expcov - chrCtrlPloidy * (1 - c.purity)) / c.purity;
  422. logR = std::log2((wins[i].covsum + 1.0) / (wins[i].expcov + 1.0));
  423. }
  424. dataOut << chrn << "\t" << wins[i].start << "\t" << wins[i].end << "\t" << uniqFrac << "\t" << logR << "\t" << cn << std::endl;
  425. }
  426. }
  427. }
  428. }
  429. // Sort CNVs
  430. sort(cnvs.begin(), cnvs.end());
  431. // Merge CNVs
  432. if (!c.hasGenoFile) mergeAdjacentSameCN(cnvs, c.cnMergeTol);
  433. // Exclude regions
  434. if (c.hasExcludeFile) {
  435. typedef boost::icl::interval_set<uint32_t> TChrIntervals;
  436. typedef TChrIntervals::interval_type TIVal;
  437. std::vector<TChrIntervals> validRegions;
  438. if (_parseExcludeIntervals(c, hdr, validRegions)) {
  439. std::vector<CNV> keep;
  440. keep.reserve(cnvs.size());
  441. for(uint32_t i = 0; i < cnvs.size(); ++i) {
  442. int32_t s = cnvs[i].start;
  443. int32_t e = cnvs[i].end;
  444. if ((e <= s) || (cnvs[i].chr < 0) || (cnvs[i].chr >= (int32_t) validRegions.size())) { keep.push_back(cnvs[i]); continue; }
  445. TChrIntervals ci;
  446. ci.insert(TIVal::right_open((uint32_t) s, (uint32_t) e));
  447. TChrIntervals inter = ci & validRegions[cnvs[i].chr];
  448. uint32_t validbp = 0;
  449. for(TChrIntervals::const_iterator it = inter.begin(); it != inter.end(); ++it) validbp += (it->upper() - it->lower());
  450. if ((double) validbp >= 0.5 * (double) (e - s)) keep.push_back(cnvs[i]);
  451. }
  452. cnvs.swap(keep);
  453. }
  454. }
  455. // Genotype CNVs
  456. cnvVCF(c, cnvs);
  457. // clean-up
  458. fai_destroy(faiRef);
  459. bam_hdr_destroy(hdr);
  460. hts_idx_destroy(idx);
  461. sam_close(samfile);
  462. if (!c.covfile.empty()) {
  463. dataOut.pop();
  464. dataOut.pop();
  465. }
  466. return 0;
  467. }
  468. int coral(int argc, char **argv) {
  469. CountDNAConfig c;
  470. std::string haploidChr;
  471. std::string mode;
  472. // Parameter
  473. boost::program_options::options_description generic("Generic options");
  474. generic.add_options()
  475. ("help,?", "show help message")
  476. ("genome,g", boost::program_options::value<boost::filesystem::path>(&c.genome), "genome file")
  477. ("exclude,x", boost::program_options::value<boost::filesystem::path>(&c.exclude), "file with regions to exclude")
  478. ("quality,q", boost::program_options::value<uint16_t>(&c.minQual)->default_value(10), "min. mapping quality")
  479. ("outfile,o", boost::program_options::value<boost::filesystem::path>(&c.outfile), "BCF output file")
  480. ("covfile,c", boost::program_options::value<boost::filesystem::path>(&c.covfile), "gzipped coverage file")
  481. ("segmentation,u", boost::program_options::value<boost::filesystem::path>(&c.segfile), "segmentation BED output file")
  482. ;
  483. boost::program_options::options_description cnv("CNV calling");
  484. cnv.add_options()
  485. ("cnv-size,z", boost::program_options::value<uint32_t>(&c.minCnvSize)->default_value(1000), "min. CNV size")
  486. ("vcffile,v", boost::program_options::value<boost::filesystem::path>(&c.genofile), "input CNV BCF file for re-genotyping")
  487. ("minclip", boost::program_options::value<uint32_t>(&c.minClip)->default_value(25), "min. clipping length")
  488. ("minrefsep", boost::program_options::value<uint32_t>(&c.minRefSep)->default_value(30), "min. reference separation")
  489. ("min-bp-support", boost::program_options::value<uint32_t>(&c.minBpSupport)->default_value(3), "min. split-read support")
  490. ("penalty", boost::program_options::value<float>(&c.penalty)->default_value(3), "segmentation penalty")
  491. ("cnv-merge", boost::program_options::value<float>(&c.cnMergeTol)->default_value(0.25), "min. log2 ratio to separate CNVs")
  492. ("cnv-qual", boost::program_options::value<int32_t>(&c.cnvMinQual)->default_value(5), "min. quality for PASS")
  493. ;
  494. boost::program_options::options_description cancer("Ploidy/purity correction");
  495. cancer.add_options()
  496. ("mode,m", boost::program_options::value<std::string>(&mode)->default_value("germline"), "CNV calling mode [germline|somatic]")
  497. ("ploidy,y", boost::program_options::value<float>(&c.ploidy)->default_value(2), "sample ploidy")
  498. ("purity,p", boost::program_options::value<float>(&c.purity)->default_value(1), "sample purity [0.1, 1]")
  499. ("ctrl-ploidy", boost::program_options::value<float>(&c.ctrlPloidy)->default_value(2), "control ploidy")
  500. ("haploid-chr", boost::program_options::value<std::string>(&haploidChr), "haploid chromosomes, e.g. chrX,chrY")
  501. ;
  502. boost::program_options::options_description window("Read-depth windows");
  503. window.add_options()
  504. ("window,w", boost::program_options::value<uint32_t>(&c.window_size)->default_value(0), "window size in bp (0: automatic)")
  505. ("fraction-unique", boost::program_options::value<float>(&c.uniqueToTotalCovRatio)->default_value(0.8), "uniqueness filter [0,1]")
  506. ("cnv-del-confirm", boost::program_options::value<float>(&c.cnvDelConfirm)->default_value(1.5), "max. total-depth CN for DEL")
  507. ("cnv-del-ratio", boost::program_options::value<float>(&c.cnvDelRatio)->default_value(0.75), "total-depth ratio")
  508. ("basecov", "force base-level counting")
  509. ("fragmentcov", "force fragment-level counting")
  510. ("no-regional-gc", "disable broad GC correction")
  511. ;
  512. boost::program_options::options_description hidden("Hidden options");
  513. hidden.add_options()
  514. ("input-file", boost::program_options::value<boost::filesystem::path>(&c.bamFile), "input BAM/CRAM file")
  515. ("fragment", boost::program_options::value<float>(&c.fragmentUnique)->default_value(0.97), "min. fragment uniqueness [0,1]")
  516. ("statsfile", boost::program_options::value<boost::filesystem::path>(&c.statsFile), "gzipped stats output file (optional)")
  517. ("window-offset", boost::program_options::value<uint32_t>(&c.window_offset)->default_value(0), "window offset (0: window size)")
  518. ("fraction-window", boost::program_options::value<float>(&c.fracWindow)->default_value(0.25), "min. callable window fraction [0,1]")
  519. ("mapq-uniq", boost::program_options::value<uint16_t>(&c.mapqUniq)->default_value(20), "min. MAPQ for a uniquely-placed read")
  520. ("target-reads", boost::program_options::value<uint32_t>(&c.targetReads)->default_value(150), "target reads/window")
  521. ("min-windows", boost::program_options::value<uint32_t>(&c.minSegWin)->default_value(2), "min. windows per segment")
  522. ("scan-window", boost::program_options::value<uint32_t>(&c.scanWindow)->default_value(10000), "GC scanning window size")
  523. ("scan-regions", boost::program_options::value<boost::filesystem::path>(&c.scanFile), "GC scanning regions in BED format")
  524. ("mad-cutoff", boost::program_options::value<uint16_t>(&c.mad)->default_value(3), "median + 3 * mad count cutoff")
  525. ("percentile", boost::program_options::value<float>(&c.exclgc)->default_value(0.0005), "excl. extreme GC fraction")
  526. ("no-window-selection", "no scan window selection")
  527. ;
  528. boost::program_options::positional_options_description pos_args;
  529. pos_args.add("input-file", -1);
  530. // Set the visibility
  531. boost::program_options::options_description cmdline_options;
  532. cmdline_options.add(generic).add(cnv).add(cancer).add(window).add(hidden);
  533. boost::program_options::options_description visible_options;
  534. visible_options.add(generic).add(cnv).add(cancer).add(window);
  535. // Parse command-line
  536. boost::program_options::variables_map vm;
  537. boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(cmdline_options).positional(pos_args).run(), vm);
  538. boost::program_options::notify(vm);
  539. // Check command line arguments
  540. if ((vm.count("help")) || (!vm.count("input-file")) || (!vm.count("genome"))) {
  541. std::cerr << std::endl;
  542. std::cerr << "Usage: delly " << argv[0] << " [OPTIONS] -g <genome.fa> <aligned.bam>" << std::endl;
  543. std::cerr << visible_options << "\n";
  544. return 1;
  545. }
  546. // Show cmd
  547. boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
  548. std::cerr << '[' << boost::posix_time::to_simple_string(now) << "] ";
  549. std::cerr << "delly ";
  550. for(int i=0; i<argc; ++i) { std::cerr << argv[i] << ' '; }
  551. std::cerr << std::endl;
  552. // Stats file
  553. if (vm.count("statsfile")) c.hasStatsFile = true;
  554. else c.hasStatsFile = false;
  555. // Scan regions
  556. if (vm.count("scan-regions")) c.hasScanFile = true;
  557. else c.hasScanFile = false;
  558. // Exclude regions
  559. if (vm.count("exclude")) c.hasExcludeFile = true;
  560. else c.hasExcludeFile = false;
  561. // Scan window selection
  562. if (vm.count("no-window-selection")) c.noScanWindowSelection = true;
  563. else c.noScanWindowSelection = false;
  564. c.regionalGc = (!vm.count("no-regional-gc"));
  565. // Adaptive windows
  566. c.adaptive = false;
  567. c.targetExpCov = 0;
  568. if (c.window_size == 0) c.adaptive = true;
  569. if (c.targetReads == 0) c.targetReads = 150;
  570. // Calling mode
  571. c.somatic = (mode == "somatic");
  572. if (c.somatic) {
  573. // More sensitive segmentation
  574. if (vm["penalty"].defaulted()) c.penalty = 1.25;
  575. if (vm["cnv-merge"].defaulted()) c.cnMergeTol = 0;
  576. }
  577. // Purity
  578. if (c.purity > 1) c.purity = 1;
  579. if (c.purity < 0.1) c.purity = 0.1;
  580. // Expected ploidy
  581. c.expectedCN = c.purity * c.ploidy + (1.0 - c.purity) * c.ctrlPloidy;
  582. // Segmentation
  583. if (vm.count("segmentation")) c.hasSegFile = true;
  584. else c.hasSegFile = false;
  585. // Window offset
  586. if ((c.window_offset == 0) || (c.window_offset > c.window_size)) c.window_offset = c.window_size;
  587. // Check input VCF file (CNV genotyping)
  588. if (vm.count("vcffile")) {
  589. if (!(boost::filesystem::exists(c.genofile) && boost::filesystem::is_regular_file(c.genofile) && boost::filesystem::file_size(c.genofile))) {
  590. std::cerr << "Input VCF/BCF file is missing: " << c.genofile.string() << std::endl;
  591. return 1;
  592. }
  593. htsFile* ifile = bcf_open(c.genofile.string().c_str(), "r");
  594. if (ifile == NULL) {
  595. std::cerr << "Fail to open file " << c.genofile.string() << std::endl;
  596. return 1;
  597. }
  598. bcf_hdr_t* hdr = bcf_hdr_read(ifile);
  599. if (hdr == NULL) {
  600. std::cerr << "Fail to open index file " << c.genofile.string() << std::endl;
  601. return 1;
  602. }
  603. bcf_hdr_destroy(hdr);
  604. bcf_close(ifile);
  605. c.hasGenoFile = true;
  606. } else c.hasGenoFile = false;
  607. // Check outfile
  608. if (!vm.count("outfile")) c.outfile = "-";
  609. else {
  610. if (c.outfile.string() != "-") {
  611. if (!_outfileValid(c.outfile)) return 1;
  612. }
  613. }
  614. // Check bam file
  615. LibraryInfo li;
  616. bool pairedLib = false;
  617. if (!(boost::filesystem::exists(c.bamFile) && boost::filesystem::is_regular_file(c.bamFile) && boost::filesystem::file_size(c.bamFile))) {
  618. std::cerr << "Alignment file is missing: " << c.bamFile.string() << std::endl;
  619. return 1;
  620. } else {
  621. // Get scan regions
  622. typedef boost::icl::interval_set<uint32_t> TChrIntervals;
  623. typedef typename TChrIntervals::interval_type TIVal;
  624. typedef std::vector<TChrIntervals> TRegionsGenome;
  625. TRegionsGenome scanRegions;
  626. // Open BAM file
  627. samFile* samfile = sam_open(c.bamFile.string().c_str(), "r");
  628. if (samfile == NULL) {
  629. std::cerr << "Fail to open file " << c.bamFile.string() << std::endl;
  630. return 1;
  631. }
  632. hts_idx_t* idx = sam_index_load(samfile, c.bamFile.string().c_str());
  633. if (idx == NULL) {
  634. if (bam_index_build(c.bamFile.string().c_str(), 0) != 0) {
  635. std::cerr << "Fail to open index for " << c.bamFile.string() << std::endl;
  636. return 1;
  637. }
  638. }
  639. bam_hdr_t* hdr = sam_hdr_read(samfile);
  640. if (hdr == NULL) {
  641. std::cerr << "Fail to open header for " << c.bamFile.string() << std::endl;
  642. return 1;
  643. }
  644. c.nchr = hdr->n_targets;
  645. c.minChrLen = setMinChrLen(hdr, 0.95);
  646. std::string sampleName = "unknown";
  647. getSMTag(std::string(hdr->text), c.bamFile.stem().string(), sampleName);
  648. c.sampleName = sampleName;
  649. // Check special chromosomes
  650. if (haploidChr.size()) _selectedRefIndices(c, hdr, haploidChr);
  651. // Check matching chromosome names
  652. faidx_t* faiRef = fai_load(c.genome.string().c_str());
  653. uint32_t refFound = 0;
  654. for(int32_t refIndex=0; refIndex < hdr->n_targets; ++refIndex) {
  655. std::string tname(hdr->target_name[refIndex]);
  656. if (faidx_has_seq(faiRef, tname.c_str())) ++refFound;
  657. else {
  658. std::cerr << "Warning: BAM chromosome " << tname << " not present in reference genome!" << std::endl;
  659. }
  660. }
  661. fai_destroy(faiRef);
  662. if (!refFound) {
  663. std::cerr << "Reference genome chromosome naming disagrees with BAM file!" << std::endl;
  664. return 1;
  665. }
  666. // Estimate library params
  667. if (c.hasScanFile) {
  668. if (!_parseBedIntervals(c.scanFile.string(), c.hasScanFile, hdr, scanRegions)) {
  669. std::cerr << "Warning: Couldn't parse BED intervals. Do the chromosome names match?" << std::endl;
  670. return 1;
  671. }
  672. } else {
  673. scanRegions.resize(hdr->n_targets);
  674. for (int32_t refIndex = 0; refIndex < hdr->n_targets; ++refIndex) {
  675. scanRegions[refIndex].insert(TIVal::right_open(0, hdr->target_len[refIndex]));
  676. }
  677. }
  678. typedef std::vector<LibraryInfo> TSampleLibrary;
  679. TSampleLibrary sampleLib(1, LibraryInfo());
  680. CountDNAConfigLib dellyConf;
  681. dellyConf.genome = c.genome;
  682. dellyConf.files.push_back(c.bamFile);
  683. dellyConf.madCutoff = 9;
  684. dellyConf.madNormalCutoff = c.mad;
  685. getLibraryParams(dellyConf, scanRegions, sampleLib);
  686. li = sampleLib[0];
  687. pairedLib = (li.median > 0);
  688. if (!li.median) {
  689. li.median = 250;
  690. li.mad = 15;
  691. li.minNormalISize = 0;
  692. li.maxNormalISize = 400;
  693. }
  694. c.meanisize = ((int32_t) (li.median / 2)) * 2 + 1;
  695. // Coverage-aware GC scan window
  696. bool autoScanWin = (!vm.count("scan-window")) || (vm["scan-window"].defaulted());
  697. if ((autoScanWin) && (idx != NULL)) {
  698. uint64_t totalMapped = 0;
  699. uint64_t genomeLen = 0;
  700. for(int32_t refIndex = 0; refIndex < hdr->n_targets; ++refIndex) {
  701. uint64_t mapped = 0;
  702. uint64_t unmapped = 0;
  703. hts_idx_get_stat(idx, refIndex, &mapped, &unmapped);
  704. if (mapped > 0) {
  705. totalMapped += mapped;
  706. genomeLen += hdr->target_len[refIndex];
  707. }
  708. }
  709. if (pairedLib) totalMapped /= 2; // fragments
  710. if ((totalMapped > 0) && (genomeLen > 0)) {
  711. double fragPerBp = (double) totalMapped / (double) genomeLen;
  712. uint32_t targetScanReads = 30;
  713. uint32_t autoScan = (uint32_t) (targetScanReads / fragPerBp);
  714. if (autoScan < c.scanWindow) autoScan = c.scanWindow;
  715. uint32_t maxScan = 1000000;
  716. if (autoScan > maxScan) autoScan = maxScan;
  717. if (autoScan > c.scanWindow) c.scanWindow = autoScan;
  718. }
  719. }
  720. // Clean-up
  721. bam_hdr_destroy(hdr);
  722. hts_idx_destroy(idx);
  723. sam_close(samfile);
  724. }
  725. // Counting model
  726. if (vm.count("basecov")) c.basecov = true;
  727. else if (vm.count("fragmentcov")) c.basecov = false;
  728. else c.basecov = ((!pairedLib) && (li.rs >= 500));
  729. if ((c.basecov) && (!vm.count("cnv-del-confirm"))) c.cnvDelConfirm = std::numeric_limits<float>::max();
  730. // GC bias estimation
  731. typedef std::pair<uint32_t, uint32_t> TGCBound;
  732. TGCBound gcbound;
  733. std::vector<GcBias> gcbias(c.meanisize + 1, GcBias());
  734. typedef std::vector<ScanWindow> TWindowCounts;
  735. typedef std::vector<TWindowCounts> TGenomicWindowCounts;
  736. TGenomicWindowCounts scanCounts(c.nchr, TWindowCounts());
  737. {
  738. // Scan genomic windows
  739. scan(c, li, scanCounts);
  740. // Check coverage
  741. {
  742. std::vector<uint32_t> sampleScanVec;
  743. for(uint32_t i = 0; i < scanCounts.size(); ++i) {
  744. for(uint32_t j = 0; j < scanCounts[i].size(); ++j) {
  745. sampleScanVec.push_back(scanCounts[i][j].cov);
  746. }
  747. if (sampleScanVec.size() > 1000000) break;
  748. }
  749. std::sort(sampleScanVec.begin(), sampleScanVec.end());
  750. if (sampleScanVec.empty()) {
  751. std::cerr << "Not enough windows!" << std::endl;
  752. return 1;
  753. }
  754. if (sampleScanVec[sampleScanVec.size()/2] < 5) {
  755. std::cerr << "Coverage in the GC scan window is too low." << std::endl;
  756. return 1;
  757. }
  758. }
  759. // Select stable windows
  760. selectWindows(c, scanCounts);
  761. // Estimate GC bias
  762. gcBias(c, scanCounts, li, gcbias, gcbound);
  763. // Statistics output
  764. if (c.hasStatsFile) {
  765. // Open stats file
  766. boost::iostreams::filtering_ostream statsOut;
  767. statsOut.push(boost::iostreams::gzip_compressor());
  768. statsOut.push(boost::iostreams::file_sink(c.statsFile.string(), std::ios_base::out | std::ios_base::binary));
  769. // Library Info
  770. statsOut << "LP\t" << li.rs << ',' << li.median << ',' << li.mad << ',' << li.minNormalISize << ',' << li.maxNormalISize << std::endl;
  771. // Scan window summry
  772. samFile* samfile = sam_open(c.bamFile.string().c_str(), "r");
  773. bam_hdr_t* hdr = sam_hdr_read(samfile);
  774. statsOut << "SW\tchrom\tstart\tend\tselected\tcoverage\tuniqcov" << std::endl;
  775. for(uint32_t refIndex = 0; refIndex < (uint32_t) hdr->n_targets; ++refIndex) {
  776. for(uint32_t i = 0; i < scanCounts[refIndex].size(); ++i) {
  777. statsOut << "SW\t" << hdr->target_name[refIndex] << '\t' << scanCounts[refIndex][i].start << '\t' << scanCounts[refIndex][i].end << '\t' << scanCounts[refIndex][i].select << '\t' << scanCounts[refIndex][i].cov << '\t' << scanCounts[refIndex][i].uniqcov << std::endl;
  778. }
  779. }
  780. bam_hdr_destroy(hdr);
  781. sam_close(samfile);
  782. // GC bias summary
  783. statsOut << "GC\tgcsum\tsample\treference\tpercentileSample\tpercentileReference\tfractionSample\tfractionReference\tobsexp\tmeancoverage\tmeancoverageTotal\treferenceTotal" << std::endl;
  784. for(uint32_t i = 0; i < gcbias.size(); ++i) statsOut << "GC\t" << i << "\t" << gcbias[i].sample << "\t" << gcbias[i].reference << "\t" << gcbias[i].percentileSample << "\t" << gcbias[i].percentileReference << "\t" << gcbias[i].fractionSample << "\t" << gcbias[i].fractionReference << "\t" << gcbias[i].obsexp << "\t" << gcbias[i].coverage << "\t" << gcbias[i].coverageTotal << "\t" << gcbias[i].referenceTotal << std::endl;
  785. statsOut << "BoundsGC\t" << gcbound.first << "," << gcbound.second << std::endl;
  786. statsOut.pop();
  787. statsOut.pop();
  788. }
  789. }
  790. // Coverage-aware window size
  791. uint32_t effWin = (c.window_size > 0) ? c.window_size : 50000;
  792. if (c.adaptive) {
  793. // Mean expected coverage at CN2 over the callable GC range
  794. double covMean = 0;
  795. uint64_t refCnt = 0;
  796. for(uint32_t i = gcbound.first + 1; i < gcbound.second; ++i) {
  797. covMean += gcbias[i].coverage * (double) gcbias[i].reference;
  798. refCnt += gcbias[i].reference;
  799. }
  800. if (refCnt) covMean /= (double) refCnt;
  801. if (covMean <= 0) {
  802. // Fixed windows
  803. c.adaptive = false;
  804. c.window_size = 10000;
  805. c.window_offset = c.window_size;
  806. } else {
  807. double readLen = (li.rs > 0) ? (double) li.rs : (double) c.meanisize;
  808. double molPerBp = c.basecov ? (covMean / readLen) : covMean;
  809. if (molPerBp <= 0) molPerBp = 1e-9;
  810. double winBp = (double) c.targetReads / molPerBp;
  811. double minWin = std::max(100.0, 4.0 * readLen);
  812. double maxWin = 2000000.0;
  813. if (winBp < minWin) winBp = minWin;
  814. if (winBp > maxWin) winBp = maxWin;
  815. c.targetExpCov = covMean * winBp;
  816. effWin = (uint32_t) winBp;
  817. double effReads = molPerBp * winBp;
  818. double covDepth = c.basecov ? covMean : (covMean * readLen);
  819. now = boost::posix_time::second_clock::local_time();
  820. std::cerr << '[' << boost::posix_time::to_simple_string(now) << "] " << "Auto window size: " << (uint32_t) winBp << " bp, " << (uint32_t) effReads << " reads/window (" << (c.basecov ? "base-level" : "fragment") << ", coverage " << std::fixed << std::setprecision(2) << covDepth << std::defaultfloat << "x)" << std::endl;
  821. }
  822. }
  823. // Regional GC correction
  824. std::vector<double> regcorr;
  825. uint32_t regWin = std::max((uint32_t) 50000, effWin);
  826. if (c.regionalGc) estimateRegionalGc(c, gcbound, gcbias, scanCounts, regWin, regcorr);
  827. // Count reads
  828. if (bamCount(c, li, gcbias, gcbound, regcorr, regWin)) {
  829. std::cerr << "Read counting error!" << std::endl;
  830. return 1;
  831. }
  832. // Done
  833. now = boost::posix_time::second_clock::local_time();
  834. std::cerr << '[' << boost::posix_time::to_simple_string(now) << "] " << "Done." << std::endl;
  835. return 0;
  836. }
  837. }
  838. #endif

coral.h at commit c6474a2, under BSD-3-Clause · at the source

Overview

Authors: Lorenzo Corazzi1,2, Alex Ing1, Eva Benito3, Marco Raffaele Cosenza3, Patrick Hasenfeld3, Thomas Weber4, Anna J M Marx1,2, Vivien S Ionasz1, Nathan Trausch1,2, Sarah Benedetto5, Giulia Di Muzio1,2, Boyu Ding1,6, Jana Berlanda1,2, Marco Giaisi1, Nina Claudino5, Thomas Höfer5, Jan O Korbel3,7, Pei-Chi Wei1,2
  1. Brain Mosaicism and Tumorigenesis Laboratory, German Cancer Research Center, Heidelberg, Germany
  2. Faculty of Bioscience, Ruprecht-Karl-University of Heidelberg, Heidelberg, Germany
  3. European Molecular Biology Laboratory (EMBL), Genome Biology Unit, Heidelberg, Germany
  4. Data Science Centre, European Molecular Biology Laboratory (EMBL), Heidelberg, Germany
  5. Division of Theoretical Systems Biology, German Cancer Research Center, Heidelberg, Germany
  6. Faculty of Medicine, Ruprecht-Karl-University of Heidelberg, Heidelberg, Germany
  7. Bridging Research Division on Mechanisms of Genomic Variation and Data Science, German Cancer Research Center, Heidelberg, Germany
Journal: Nature communications, volume 17, issue 1, article 3627
Dates: received 20 August 2025; accepted 25 March 2026; published online 20 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-71790-5 · PMID 42009664 · PMCID PMC13096501 · OpenAlex W7154939394
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), mouse (organism), cellular / molecular (subfield)
Methods: Statistics, Preprocessing
Keywords: Genomic instability, Genomics, DNA damage and repair, DNA replication
MeSH: DNA Breaks*, DNA Copy Number Variations*, DNA Replication*, Animals, CRISPR-Cas Systems, DNA Polymerase theta, DNA Repair, DNA-Directed DNA Polymerase, Genome, Mice, Neural Stem Cells, Whole Genome Sequencing (* major topic)
Topic: Genomic variations and chromosomal abnormalities (Genetics, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: European Research Council (101098056, 949990); Helmholtz Association (YIP-DKFZ)
Citations: cited by 3 papers (Europe PMC); 63 references in the paper

Abstract

Copy number variants (CNVs) are strongly implicated in neurological and psychiatric disorders and brain cancer, yet the process by which replication stress generates CNVs—and why some recur while others remain rare—remains poorly understood. Here, we show that recurrent DNA-break clusters (RDCs) act as common initiating lesions that drive both recurrent and non-recurrent CNVs. In murine neural progenitor cells subjected to chemically induced replication stress, bulk whole-genome sequencing identifies recurrent CNVs enriched at late-replicating RDCs within actively transcribed genes. Single-cell genome sequencing further uncovers frequent, non-recurrent CNVs associated with RDCs that arise during the transition from early to late DNA replication. These CNVs represent stable, heritable structural variants with breakpoints consistently enriched at RDCs. CRISPR/Cas9-mediated transcriptional suppression abolishes both RDC formation and CNV generation, establishing RDC-associated breaks as a shared upstream source. Mechanistically, CNV formation depends on DNA repair context: CNVs are Pol θ-dependent in NHEJ-deficient cells but arise independently of Pol θ in NHEJ-proficient cells. Together, these findings define RDCs as central drivers of replication-stress-induced genome diversification.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 3 matches between paragraphs and lines of code.

DKFZ-ODCF/AlignmentAndQCWorkflows

License: MIT
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 2701b22ad5391ce53747345fff80b87f521cc60f, 9 May 2023
Languages: Shell (29), Java (25), Python (22), Perl (12), R (9)
Size: 148 files, 97 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (resources/analysisTools/qcPipeline/environments/conda.yml), tests, documentation
Not found: CITATION.cff, continuous integration
Tools: pysam (7 files)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
99 files

CL-CHEN-Lab/RepliSeq

License: GPL-3.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 359c213d846061eba0a688412f203dfb05369bf1, 6 September 2021
Languages: R (13)
Size: 64 files, 13 scripts
Software Heritage: not archived
Found in: the text, “Two-fraction replication sequencing (Repli-Seq)”
Holds: README, license file, environment (DESCRIPTION), documentation, 1 notebook
Not found: CITATION.cff, tests, continuous integration
Tools: tidyverse (3 files)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
15 files

brainbreaks/GROseq

License: none: the authors keep all their rights
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 724b09bd564e0cb2ea41a6855ec055c86dd35e11, 19 March 2024
Languages: Python (11)
Size: 18 files, 11 scripts
Software Heritage: not archived
Found in: the text, “Global run-on sequencing (GRO-seq)”
Holds: README, environment (Dockerfile)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (2 files), pandas (2 files), BEDTools (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
12 files

dellytools/delly

License: BSD-3-Clause
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: c6474a25a125982a85829749ab7c14c63eaf17a2, 24 September 2026
Languages: C/C++ (35), R (3), C++ (2), Python (1)
Size: 76 files, 41 scripts
Software Heritage: archived
Found in: “Code availability”
Holds: README, license file, environment (Dockerfile, Dockerfile.staticbuild, pyproject.toml, singularity/delly.def), continuous integration
Not found: CITATION.cff, tests, documentation
Tools: ggplot2 (3 files), reshape2 (2 files)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
43 files

friendsofstrandseq/mosaicatcher-pipeline

License: MIT
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 180b6591078f2a1bd28b2cdd757c6ee31c6299fc, 26 August 2026
Languages: R (111), Python (106), Shell (29), Jupyter (19), Perl (8)
Size: 517 files, 273 scripts
Software Heritage: archived
Found in: “Code availability”
Holds: README, license file, environment (.gitpod.Dockerfile, pixi.lock, pixi.toml, github-actions-runner/Dockerfile, github-actions-runner/Dockerfile-1.8.3-T2T.dockerfile, github-actions-runner/Dockerfile-1.8.3.dockerfile, github-actions-runner/Dockerfile-1.8.4.dockerfile, github-actions-runner/Dockerfile-1.8.5.dockerfile, github-actions-runner/Dockerfile-1.8.6.dockerfile, github-actions-runner/Dockerfile-1.9.0.dockerfile, github-actions-runner/Dockerfile-1.9.1.dockerfile, github-actions-runner/Dockerfile-2.0.0.dockerfile), continuous integration, documentation, 19 notebooks
Not found: CITATION.cff, tests
Tools: pandas (80 files), data.table (36 files), tidyverse (32 files), ggplot2 (26 files), NumPy (24 files), reshape2 (14 files), pysam (10 files), Matplotlib (9 files), SAMtools (7 files), pheatmap (6 files), TensorFlow (6 files), ggpubr (5 files), seaborn (5 files), cowplot (3 files), Keras (3 files), Plotly (3 files), SciPy (3 files), Snakemake (3 files), BCFtools (2 files), BEDTools (2 files), DESeq2 (2 files), UMAP (2 files), ComplexHeatmap (1 file), edgeR (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
275 files

Zenodo 10843397

License: CC-BY-4.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: tidyverse (13 files), ggplot2 (7 files), data.table (6 files), circlize (2 files), reshape2 (2 files), SAMtools (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers (HTTP 200)
  • 29 September 2026: the link answers (HTTP 200)
35 files
At the source:

Zenodo 10838367

License: CC-BY-4.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (2 files), pandas (2 files), BEDTools (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers (HTTP 200)
  • 29 September 2026: the link answers (HTTP 200)
12 files
At the source:

Zenodo 10838365

License: CC-BY-4.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: tidyverse (4 files), reshape2 (3 files)
Availability: 1 check, the latest on 29 September 2026: the link answers (HTTP 200)
  • 29 September 2026: the link answers (HTTP 200)
5 files
At the source:

Code availability

In this manuscript, three singularity containers were applied for HTGTS (10.5281/zenodo.10843397), GRO-seq (10.5281/zenodo.10838367), and Repli-seq (10.5281/zenodo.10838365) analyses. In addition, an automated AlignmentAndQC workflow (https://github.com/DKFZ-ODCF/AlignmentAndQCWorkflows/) was applied to WGS analysis. Bowtie 2 2.5.0 used for LAM-HTGTS, Repli-seq, and GRO-seq. Samtools 1.7 was used for LAM-HTGTS, and 1.8 and above was used for Repli-seq and GRO-seq. These pipelines and software dependencies were containerized using Docker, ensuring reproducible and consistent version control across analyses. Delly 1.2.6 is a publicly available package (https://github.com/dellytools/delly). MosaicCatcher V2 (https://github.com/friendsofstrandseq/mosaicatcher-pipeline) and Strandtools (https://git.embl.org/cosenza/strandtools) are publicly available.

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 8 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 484 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 Availability Statement

The whole genome sequencing data generated in this study have been deposited in the European Genome Archive database under accession code PRJEB95922 (https://www.ebi.ac.uk/ena/browser/view/PRJEB95922). The two-fraction Repli-Seq and LAM-HTGTS generated in this study have been deposited in NCBI’s Gene Expression Omnibus and are accessible through GEO Series accession number GSE305347 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE305347). The GRO-seq data generated in this study have been deposited in NCBI’s GEO database under accession code GSE305346 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE305346). Processed data were aligned to mm10 genome build. The Strand-seq data have been deposited in the European Genome Archive database under accession code PRJEB105360 [https://www.ebi.ac.uk/ena/browser/view/ PRJEB105360]. We retrieved high-resolution Repli-Seq data generated in this study from GSE257765 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE257765) (related to Fig. 2), LAM-HTGTS data from GSE233842 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE233842) [https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc= GSE233842 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE233842)] (related to Fig. 2) and GSE74356 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE74356) (wild-type Chr12 data, Fig. 6; re-aligned to mm10) for replotting. Source data are provided with this paper.

In this manuscript, three singularity containers were applied for HTGTS (10.5281/zenodo.10843397), GRO-seq (10.5281/zenodo.10838367), and Repli-seq (10.5281/zenodo.10838365) analyses. In addition, an automated AlignmentAndQC workflow (https://github.com/DKFZ-ODCF/AlignmentAndQCWorkflows/) was applied to WGS analysis. Bowtie 2 2.5.0 used for LAM-HTGTS, Repli-seq, and GRO-seq. Samtools 1.7 was used for LAM-HTGTS, and 1.8 and above was used for Repli-seq and GRO-seq. These pipelines and software dependencies were containerized using Docker, ensuring reproducible and consistent version control across analyses. Delly 1.2.6 is a publicly available package (https://github.com/dellytools/delly). MosaicCatcher V2 (https://github.com/friendsofstrandseq/mosaicatcher-pipeline) and Strandtools (https://git.embl.org/cosenza/strandtools) are publicly available.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 29 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 18 authors, 4 keywords, 12 MeSH terms, 2 funders, 62 references.

Cite

This paper

Corazzi, L., Ing, A., Benito, E., Cosenza, M. R., Hasenfeld, P., Weber, T., Marx, A. J. M., Ionasz, V. S., Trausch, N., Benedetto, S., Di Muzio, G., Ding, B., Berlanda, J., Giaisi, M., Claudino, N., Höfer, T., Korbel, J. O., & Wei, P.-C. (2026). Recurrent DNA break clusters drive replication-stress-induced copy number variants and genome diversification. Nature communications, 17(1), 3627. https://doi.org/10.1038/s41467-026-71790-5

BibTeX

@article{corazzi2026recurrent,
author = {Corazzi, Lorenzo and Ing, Alex and Benito, Eva and Cosenza, Marco Raffaele and Hasenfeld, Patrick and Weber, Thomas and Marx, Anna J M and Ionasz, Vivien S and Trausch, Nathan and Benedetto, Sarah and Di Muzio, Giulia and Ding, Boyu and Berlanda, Jana and Giaisi, Marco and Claudino, Nina and Höfer, Thomas and Korbel, Jan O and Wei, Pei-Chi},
title = {{Recurrent DNA break clusters drive replication-stress-induced copy number variants and genome diversification}},
journal = {Nature communications},
year = {2026},
month = apr,
volume = {17},
number = {1},
pages = {3627},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-71790-5},
url = {https://doi.org/10.1038/s41467-026-71790-5},
pmid = {42009664},
pmcid = {PMC13096501}
}

RIS

TY - JOUR
AU - Corazzi, Lorenzo
AU - Ing, Alex
AU - Benito, Eva
AU - Cosenza, Marco Raffaele
AU - Hasenfeld, Patrick
AU - Weber, Thomas
AU - Marx, Anna J M
AU - Ionasz, Vivien S
AU - Trausch, Nathan
AU - Benedetto, Sarah
AU - Di Muzio, Giulia
AU - Ding, Boyu
AU - Berlanda, Jana
AU - Giaisi, Marco
AU - Claudino, Nina
AU - Höfer, Thomas
AU - Korbel, Jan O
AU - Wei, Pei-Chi
TI - Recurrent DNA break clusters drive replication-stress-induced copy number variants and genome diversification
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/04/20
VL - 17
IS - 1
SP - 3627
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-71790-5
UR - https://doi.org/10.1038/s41467-026-71790-5
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-71790-5",
"type": "article-journal",
"title": "Recurrent DNA break clusters drive replication-stress-induced copy number variants and genome diversification",
"container-title": "Nature communications",
"author": [
{
"family": "Corazzi",
"given": "Lorenzo"
},
{
"family": "Ing",
"given": "Alex"
},
{
"family": "Benito",
"given": "Eva"
},
{
"family": "Cosenza",
"given": "Marco Raffaele"
},
{
"family": "Hasenfeld",
"given": "Patrick"
},
{
"family": "Weber",
"given": "Thomas"
},
{
"family": "Marx",
"given": "Anna J M"
},
{
"family": "Ionasz",
"given": "Vivien S"
},
{
"family": "Trausch",
"given": "Nathan"
},
{
"family": "Benedetto",
"given": "Sarah"
},
{
"family": "Di Muzio",
"given": "Giulia"
},
{
"family": "Ding",
"given": "Boyu"
},
{
"family": "Berlanda",
"given": "Jana"
},
{
"family": "Giaisi",
"given": "Marco"
},
{
"family": "Claudino",
"given": "Nina"
},
{
"family": "Höfer",
"given": "Thomas"
},
{
"family": "Korbel",
"given": "Jan O"
},
{
"family": "Wei",
"given": "Pei-Chi"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "3627",
"DOI": "10.1038/s41467-026-71790-5",
"PMID": "42009664",
"PMCID": "PMC13096501",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-71790-5",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
20
]
]
}
}

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/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: BCFtools, SAMtools, edgeR, 18 other tools, mouse, cellular / molecular
[2] doi:10.1186/s13059-026-04177-w [code]
Genomic sequence evolution underlying human neocortical interareal diversification.
Journal: Genome biology
In common: Snakemake, pysam, BEDTools, 17 other tools, genetics / omics, mouse, cellular / molecular
[3] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: pysam, edgeR, Keras, 18 other tools, genetics / omics, cellular / molecular
[4] 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: BCFtools, SAMtools, edgeR, 17 other tools, genetics / omics
[5] doi:10.1016/j.celrep.2026.117073 [code]
Single-cell epigenomics uncovers heterochromatin instability and transcription factor dysfunction during mouse brain aging.
Journal: Cell reports
In common: Snakemake, pysam, BEDTools, 15 other tools, genetics / omics, mouse, cellular / molecular
[6] doi:10.1038/s41586-026-10512-9 [code]
Astrocyte glucocorticoid receptor signalling restricts neuronal plasticity.
Journal: Nature
In common: pysam, BEDTools, SAMtools, 15 other tools, mouse, cellular / molecular
[7] doi:10.1038/s41586-026-10612-6 [code]
Acquired genetic and cell-state changes in IDH-mutant glioma progression.
Journal: Nature
In common: Snakemake, BCFtools, pysam, 13 other tools, cellular / molecular
[8] 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 communications
In common: pysam, BEDTools, SAMtools, 15 other tools, genetics / omics
[9] doi:10.3389/fnmol.2026.1844705 [code]
Risperidone regulates the expression of schizophrenia-related genes in the forebrain of adult male mice.
Journal: Frontiers in molecular neuroscience
In common: pysam, BEDTools, SAMtools, 14 other tools, genetics / omics, mouse, cellular / molecular
[10] doi:10.1038/s41467-026-72598-z [code]
Functional impact of genetic background on variable expressivity in neurodevelopmental disorders.
Journal: Nature communications
In common: BCFtools, BEDTools, SAMtools, 12 other tools, 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.

Request its removal

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

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

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