report.typ 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #let cr_colors = (
  2. 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"),
  3. )
  4. #import "@local/svg-emoji:0.1.0": setup-emoji, noto, github
  5. #show: setup-emoji.with(font: noto)
  6. #set page(paper: "a4", fill: cr_colors.light_grey)
  7. #show heading.where(level: 1): it => [
  8. #set align(center)
  9. #set text(fill: cr_colors.dark_blue)
  10. #it.body
  11. #v(18pt)
  12. ]
  13. #show image: set text(font: "FreeSans")
  14. #set text(size: 16pt, font: "Futura", fill: cr_colors.dark_blue)
  15. #let contigs = (
  16. "chr1", "chr2", "chr3", "chr4", "chr5", "chr6", "chr7", "chr8", "chr9", "chr10", "chr11", "chr12", "chr13", "chr14", "chr15", "chr16", "chr17", "chr18", "chr19", "chr20", "chr21", "chr22", "chrX", "chrY",
  17. )
  18. #let parseCustomDate(dateString) = {
  19. let parts = dateString.split("T")
  20. let datePart = parts.at(0).replace("-", "/")
  21. let timePart = parts.at(1).split(":")
  22. let hour = timePart.at(0)
  23. let minute = timePart.at(1)
  24. return datePart + " " + hour + "h" + minute
  25. }
  26. #let formatString(input) = {
  27. let words = input.split("_")
  28. let capitalizedWords = words.map(word => {
  29. if word.len() > 0 {
  30. upper(word.first()) + word.slice(1)
  31. } else {
  32. word
  33. }
  34. })
  35. capitalizedWords.join(" ")
  36. }
  37. #let si-fmt(val, precision: 1, sep: "\u{202F}", binary: false) = {
  38. let factor = if binary { 1024 } else { 1000 }
  39. let gt1_suffixes = ("k", "M", "G", "T", "P", "E", "Z", "Y")
  40. let lt1_suffixes = ("m", "μ", "n", "p", "f", "a", "z", "y")
  41. let scale = ""
  42. let unit = ""
  43. if type(val) == content {
  44. if val.has("text") {
  45. val = val.text
  46. } else if val.has("children") {
  47. val = val.children.map(content => content.text).join()
  48. } else {
  49. panic(val.children.map(content => content.text).join())
  50. }
  51. }
  52. // if val contains a unit, split it off
  53. if type(val) == str {
  54. unit = val.find(regex("(\D+)$"))
  55. val = float(val.split(unit).at(0))
  56. }
  57. if calc.abs(val) > 1 {
  58. for suffix in gt1_suffixes {
  59. if calc.abs(val) < factor {
  60. break
  61. }
  62. val /= factor
  63. scale += " " + suffix
  64. }
  65. } else if val != 0 and calc.abs(val) < 1 {
  66. for suffix in lt1_suffixes {
  67. if calc.abs(val) > 1 {
  68. break
  69. }
  70. val *= factor
  71. scale += " " + suffix
  72. }
  73. }
  74. let formatted = str(calc.round(val, digits: precision))
  75. formatted + sep + scale.split().at(-1, default: "") + unit
  76. }
  77. #let reportCoverage(prefix) = {
  78. image(prefix + "_global.svg", width: 100%)
  79. for contig in contigs {
  80. heading(level: 4, contig)
  81. let path = prefix + "_" + contig
  82. image(path + "_chromosome.svg")
  83. let data = json(path + "_stats.json")
  84. grid(
  85. columns: (1fr, 2fr), gutter: 3pt, align(
  86. left + horizon,
  87. )[
  88. #set text(size: 12pt)
  89. #table(
  90. 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(),
  91. )
  92. ], align(right, image(path + "_distrib.svg", width: 100%)),
  93. )
  94. parbreak()
  95. }
  96. }
  97. #let reportBam(path) = {
  98. let data = json(path)
  99. table(
  100. gutter: 3pt, stroke: none, columns: (auto, 1fr), ..for (key, value) in data {
  101. if key != "cramino" and key != "composition" and key != "path" and key != "modified" {
  102. ([ #formatString(key) ], [ #value ])
  103. } else if key == "modified" {
  104. ([ Modified Date (UTC) ], [ #parseCustomDate(value) ])
  105. } else if key == "composition" {
  106. ([ Run(s) ], [
  107. #for (i, v) in value.enumerate() {
  108. if i > 0 [ \ ]
  109. [#v.at(0).slice(0, 5): #calc.round(v.at(1), digits: 0)%]
  110. }
  111. ])
  112. } else if key == "cramino" {
  113. for (k, v) in value {
  114. if k == "normalized_read_count_per_chromosome" {} else if k != "path" and k != "checksum" and k != "creation_time" and k != "file_name" {
  115. let k = formatString(k)
  116. let v = if type(v) == "integer" { si-fmt(v) } else { v }
  117. ([ #k ], [ #v ])
  118. } else {
  119. ()
  120. }
  121. }.flatten()
  122. } else {
  123. ()
  124. }
  125. }.flatten(),
  126. )
  127. }
  128. #let formatedReadCount(path) = {
  129. let data = json(path)
  130. let data = data.cramino.normalized_read_count_per_chromosome
  131. let res = ()
  132. for contig in contigs {
  133. res.push(data.at(contig))
  134. }
  135. res.push(data.at("chrM"))
  136. return res
  137. }
  138. #let printReadCount(diag_path, mrd_path) = {
  139. let index = 14;
  140. let c = contigs
  141. c.push("chrM")
  142. let diag = formatedReadCount(diag_path)
  143. let mrd = formatedReadCount(mrd_path)
  144. c.insert(0, "")
  145. diag.insert(0, "diag")
  146. mrd.insert(0, "mrd")
  147. let arrays1 = (c.slice(0, index), diag.slice(0, index), mrd.slice(0, index))
  148. table(columns: arrays1.at(0).len(), ..arrays1.map(arr => arr.map(item => [#item])).flatten())
  149. let arrays2 = (c.slice(index), diag.slice(index), mrd.slice(index))
  150. arrays2.at(0).insert(0, "")
  151. arrays2.at(1).insert(0, "diag")
  152. arrays2.at(2).insert(0, "mrd")
  153. table(columns: arrays2.at(0).len(), ..arrays2.map(arr => arr.map(item => [#item])).flatten())
  154. }
  155. #set heading(numbering: "1.")
  156. #heading(level: 1, outlined: false)[Compte Rendu]
  157. #outline(
  158. title: "Table of Contents",
  159. depth: 3
  160. )
  161. #pagebreak()
  162. == Identity
  163. Camara
  164. == Alignement
  165. #grid(
  166. columns: (1fr, 1fr), gutter: 3pt, [
  167. === Diagnostic sample
  168. #set text(size: 11pt)
  169. #reportBam(
  170. "/Turbine-pool/LongReads/report/BECERRA/report/data/BECERRA_diag_hs1_info.json",
  171. )
  172. ], [
  173. === MRD sample
  174. #set text(size: 11pt)
  175. #reportBam(
  176. "/Turbine-pool/LongReads/report/BECERRA/report/data/BECERRA_mrd_hs1_info.json",
  177. )
  178. #set footnote(numbering: n => { " " })
  179. #footnote[Values computed by #link("https://github.com/wdecoster/cramino")[cramino] v0.14.5
  180. ]
  181. ],
  182. )
  183. #pagebreak()
  184. === Normalized read count by chromosome
  185. #[
  186. #set text(size: 10pt)
  187. #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")
  188. ]
  189. === Coverage by chromosome
  190. ==== Proportion at given depth by chromosome
  191. #reportCoverage("/Turbine-pool/LongReads/report/BECERRA/report/data/scan/BECERRA")
  192. #set footnote(numbering: n => { " " })
  193. #footnote[Values computed by Pandora development version]
  194. #pagebreak()
  195. #lorem(150)
  196. #emoji.rocket