#let cr_colors = ( dark_grey: rgb("#333333"), beige: rgb("#fdf0d5"), light_grey: rgb("#eeeeee"), dark_red: rgb("#780000"), red: rgb("#c1121f"), blue: rgb("#669bbc"), dark_blue: rgb("#003049"), ) #import "@local/svg-emoji:0.1.0": setup-emoji, noto, github #show: setup-emoji.with(font: noto) #set page(paper: "a4", fill: cr_colors.light_grey) #show heading.where(level: 1): it => [ #set align(center) #set text(fill: cr_colors.dark_blue) #it.body #v(18pt) ] #show image: set text(font: "FreeSans") #set text(size: 16pt, font: "Futura", fill: cr_colors.dark_blue) #let contigs = ( "chr1", "chr2", "chr3", "chr4", "chr5", "chr6", "chr7", "chr8", "chr9", "chr10", "chr11", "chr12", "chr13", "chr14", "chr15", "chr16", "chr17", "chr18", "chr19", "chr20", "chr21", "chr22", "chrX", "chrY", ) #let parseCustomDate(dateString) = { let parts = dateString.split("T") let datePart = parts.at(0).replace("-", "/") let timePart = parts.at(1).split(":") let hour = timePart.at(0) let minute = timePart.at(1) return datePart + " " + hour + "h" + minute } #let formatString(input) = { let words = input.split("_") let capitalizedWords = words.map(word => { if word.len() > 0 { upper(word.first()) + word.slice(1) } else { word } }) capitalizedWords.join(" ") } #let si-fmt(val, precision: 1, sep: "\u{202F}", binary: false) = { let factor = if binary { 1024 } else { 1000 } let gt1_suffixes = ("k", "M", "G", "T", "P", "E", "Z", "Y") let lt1_suffixes = ("m", "μ", "n", "p", "f", "a", "z", "y") let scale = "" let unit = "" if type(val) == content { if val.has("text") { val = val.text } else if val.has("children") { val = val.children.map(content => content.text).join() } else { panic(val.children.map(content => content.text).join()) } } // if val contains a unit, split it off if type(val) == str { unit = val.find(regex("(\D+)$")) val = float(val.split(unit).at(0)) } if calc.abs(val) > 1 { for suffix in gt1_suffixes { if calc.abs(val) < factor { break } val /= factor scale += " " + suffix } } else if val != 0 and calc.abs(val) < 1 { for suffix in lt1_suffixes { if calc.abs(val) > 1 { break } val *= factor scale += " " + suffix } } let formatted = str(calc.round(val, digits: precision)) formatted + sep + scale.split().at(-1, default: "") + unit } #let reportCoverage(prefix) = { image(prefix + "_global.svg", width: 100%) for contig in contigs { heading(level: 4, contig) let path = prefix + "_" + contig image(path + "_chromosome.svg") let data = json(path + "_stats.json") grid( columns: (1fr, 2fr), gutter: 3pt, align( left + horizon, )[ #set text(size: 12pt) #table( stroke: none, columns: (auto, 1fr), gutter: 3pt, [Mean], [#calc.round(data.mean, digits: 2)], [Standard dev.], [#calc.round(data.std_dev, digits: 2)], ..data.breaks_values.map(r => ([#r.at(0)], [#calc.round(r.at(1) * 100, digits: 1)%])).flatten(), ) ], align(right, image(path + "_distrib.svg", width: 100%)), ) parbreak() } } #let reportBam(path) = { let data = json(path) table( gutter: 3pt, stroke: none, columns: (auto, 1fr), ..for (key, value) in data { if key != "cramino" and key != "composition" and key != "path" and key != "modified" { ([ #formatString(key) ], [ #value ]) } else if key == "modified" { ([ Modified Date (UTC) ], [ #parseCustomDate(value) ]) } else if key == "composition" { ([ Run(s) ], [ #for (i, v) in value.enumerate() { if i > 0 [ \ ] [#v.at(0).slice(0, 5): #calc.round(v.at(1), digits: 0)%] } ]) } else if key == "cramino" { for (k, v) in value { if k == "normalized_read_count_per_chromosome" {} else if k != "path" and k != "checksum" and k != "creation_time" and k != "file_name" { let k = formatString(k) let v = if type(v) == "integer" { si-fmt(v) } else { v } ([ #k ], [ #v ]) } else { () } }.flatten() } else { () } }.flatten(), ) } #let formatedReadCount(path) = { let data = json(path) let data = data.cramino.normalized_read_count_per_chromosome let res = () for contig in contigs { res.push(data.at(contig)) } res.push(data.at("chrM")) return res } #let printReadCount(diag_path, mrd_path) = { let index = 14; let c = contigs c.push("chrM") let diag = formatedReadCount(diag_path) let mrd = formatedReadCount(mrd_path) c.insert(0, "") diag.insert(0, "diag") mrd.insert(0, "mrd") let arrays1 = (c.slice(0, index), diag.slice(0, index), mrd.slice(0, index)) table(columns: arrays1.at(0).len(), ..arrays1.map(arr => arr.map(item => [#item])).flatten()) let arrays2 = (c.slice(index), diag.slice(index), mrd.slice(index)) arrays2.at(0).insert(0, "") arrays2.at(1).insert(0, "diag") arrays2.at(2).insert(0, "mrd") table(columns: arrays2.at(0).len(), ..arrays2.map(arr => arr.map(item => [#item])).flatten()) } #set heading(numbering: "1.") #heading(level: 1, outlined: false)[Compte Rendu] #outline( title: "Table of Contents", depth: 3 ) #pagebreak() == Identity Camara == Alignement #grid( columns: (1fr, 1fr), gutter: 3pt, [ === Diagnostic sample #set text(size: 11pt) #reportBam( "/Turbine-pool/LongReads/report/BECERRA/report/data/BECERRA_diag_hs1_info.json", ) ], [ === MRD sample #set text(size: 11pt) #reportBam( "/Turbine-pool/LongReads/report/BECERRA/report/data/BECERRA_mrd_hs1_info.json", ) #set footnote(numbering: n => { " " }) #footnote[Values computed by #link("https://github.com/wdecoster/cramino")[cramino] v0.14.5 ] ], ) #pagebreak() === Normalized read count by chromosome #[ #set text(size: 10pt) #printReadCount("/Turbine-pool/LongReads/report/BECERRA/report/data/BECERRA_diag_hs1_info.json", "/Turbine-pool/LongReads/report/BECERRA/report/data/BECERRA_mrd_hs1_info.json") ] === Coverage by chromosome ==== Proportion at given depth by chromosome #reportCoverage("/Turbine-pool/LongReads/report/BECERRA/report/data/scan/BECERRA") #set footnote(numbering: n => { " " }) #footnote[Values computed by Pandora development version] #pagebreak() #lorem(150) ❌ #emoji.rocket