#import "template.typ": *
#import "@preview/proteograph:0.2.2": *

// sudo apt install fonts-paratype

#show: doc => conf(
  authors: (
    (
      name: "Olivier Langella",
    ),
  ),
  toc: true,
  lang: "en",
  font: "PT Sans",
  date: "14/02/2025",
  years: (2024, 2025),
  version: "3.0",
  "MassChroQ library documentation",
  doc,
)
#let mcq = "MassChroQ"

#set text(lang: "en")
#set heading(numbering: "1.1")

#show figure.where(kind: "attribute"): set align(start)

#show ref: it => {
  show link: it => {
    show text: body => {
      set text(fill: navy)
      smallcaps(body)
    }
    it
  }
  let el = it.element
  if el.func() == figure and el.kind == "attribute" {
    link(el.location(), el.supplement)
  } else {
    link(el.location(), it)
  }
}

= Introduction

#mcq is designed to extract ion current of a list of peptide and measure the area under the curve on a single run.

= Input format

#code-block(read("input.json"), "json", title: "input file format")

== JSON input root file structure
#attribute(parent: "root", tag: "project_parameters", nature: "object", required: false)[
  Controlled vocabulary summarising important parameters of the project.
]
#attribute(parent: "root", tag: "masschroq_methods", nature: "object", required: true)[
  This object contains all required parameters for the #mcq engine, see the @masschroq_methods section
]
#attribute(parent: "root", tag: "identification_data", nature: "object", required: true)[]
#attribute(parent: "root", tag: "actions", nature: "object", required: true)[]

= #mcq methods <masschroq_methods>

#code-block(read("json/methods.json"), "json", title: "masschroq_methods JSON object")
#attribute(parent: "masschroq_methods", tag: "alignment_method", nature: "object", required: true)[
  Describes how to align retention time between MS runs, see @alignment_method
]
#attribute(parent: "masschroq_methods", tag: "quantification_method", nature: "object", required: true)[
  Describes how #mcq extracts ion current, detects peaks and then quantify ion intensities, see @quantification_method
]

== Alignment method <alignment_method>

#code-block(read("json/alignment.json"), "json", title: "alignment_method JSON object")

== Quantification method <quantification_method>

#code-block(read("json/quantification.json"), "json", title: "quantification_method JSON object")

#attribute(parent: "quantification_method", tag: "match_between_run", nature: "boolean", required: true)[
  Boolean, true if a match between run is required, false otherwise
]
#attribute(parent: "quantification_method", tag: "isotope_minimum_ratio", nature: "float", required: true)[
  Ratio between 0 and 1 (not included): the ratio of minimum intensity of the whole theoretical isotopic pattern that must be reached. If the value is 0, then only the most theoretically intense isotope will be extracted and quantified. If the value is 0.8, it selects isotopes from the most intense to the least intense and stops when 80% of the whole theoretical intensity is reached.
]

#attribute(parent: "quantification_method", tag: "extraction", nature: "object", required: true)[
  Describes how the ion current is extracted at a given m/z and retention time.
]
#attribute(parent: "quantification_method", tag: "prefilter", nature: "object", required: true)[
  Signal process to apply on ion exctracted current before the peak detection. Can be empty.
]
#attribute(parent: "quantification_method", tag: "detection", nature: "object", required: true)[
  Peak detection method (see @peak_detection_method) to use. This is important to separate real peaks from artefacts and to accurately determine peak boundaries.
]

=== Peak detection method <peak_detection_method>

#code-block(read("json/peak_detection.json"), "json", title: "quantification_method JSON object")




#let xic = json("data/onexicpeak.json")
#xic.insert("title", "ion current")
#let maxmin = json("data/onexicpeak_maxmin.json")
#maxmin.insert("title", "maxmin curve")
#let minmax = json("data/onexicpeak_minmax.json")
#minmax.insert("title", "minmax curve")
#let th_max = (x: (3750, 3810), y: (500000, 500000))
//#xic2.insert("peak-begin",data_json.found_list_first_pass.first().xics.at(2).peak.rt.first())
//#xic2.insert("peak-end",data_json.found_list_first_pass.first().xics.at(2).peak.rt.last())
//
#figure(placement: auto, caption: "peak detection using minmax and maxmin filters", [
  #xic-plot(height: 8cm, width: 12cm, title: "peak detection", xic, maxmin, minmax, th_max)
])<peak-detection-min-max-curve>

The default detection method called "zivy" differantiates real MS1 peaks from noise by performing a morphology algorithm#footnote([https://en.wikipedia.org/wiki/Mathematical_morphology]).
As shown in @peak-detection-min-max-curve, it uses two curves:
- "minmax" is the closing curve #footnote([https://en.wikipedia.org/wiki/Closing_(morphology)])
- "maxmin" is the opening curve #footnote([https://en.wikipedia.org/wiki/Opening_(morphology)])

"minmax"@attribute-detection-minmax is used to determine the boundary of the peaks : where it starts and where it ends and to validate the peak with @attribute-detection-threshold_on_max.

"maxmin"@attribute-detection-maxmin is used to validate the peak as a true MS1 peak using @attribute-detection-threshold_on_min.

A peak is considered only when the @attribute-detection-threshold_on_max is reached on "minmax" *and* @attribute-detection-threshold_on_min is reached on "maxmin".


#attribute(parent: "detection", tag: "type", nature: "string", required: true)[
  The detection method to use. Only "zivy" is available in #mcq 3.
]

#attribute(parent: "detection", tag: "meanfilter", nature: "integer", required: true)[
  Applies a mean filter on the signal before processing the minmax and maxmin process. The mean is computed in a window of width :
  - $"meanfilter" dot 2$ for odd values
  - $("meanfilter" dot 2) + 1$ for even values.

  The peak area is always computed on the original signal, not the signal after the mean filter. But keep in mind that this filter must be used only on very noisy signals, with extremely high random variations.

  Do not use a value more than 1, and if the signal is stable, prefer a 0 value (no mean filter at all).
]


#attribute(parent: "detection", tag: "minmax", nature: "integer", required: true)[
  Computes the "closing" signal to fit the upper shape of the peak, filling random noise (see @peak-detection-min-max-curve). It uses a window of width :
  - $"minmax" dot 2$ for odd values
  - $("minmax" dot 2) + 1$ for even values.
]
#attribute(parent: "detection", tag: "maxmin", nature: "integer", required: true)[
  Computes the "opening" signal to fit the lower shape (from below) of the peak, filling random noise (see @peak-detection-min-max-curve). It uses a window of width :
  - $"maxmin" dot 2$ for odd values
  - $("maxmin" dot 2) + 1$ for even values.
]
#attribute(parent: "detection", tag: "threshold_on_max", nature: "integer", required: true)[
  Intensity threshold of the @attribute-detection-maxmin.
]
#attribute(parent: "detection", tag: "threshold_on_min", nature: "integer", required: true)[
  Intensity threshold of the @attribute-detection-minmax.
]

#code-block(
  "peptide proformat, charge, retention time
SLTNDWEDHLAVK, 2, 853.78
SLTNDWEDHLAVK, 3, 853.78
",
  "csv",
  title: "CSV input file format",
)

= Output format

Wouldn't be nice to try #link("https://en.wikipedia.org/wiki/Comparison_of_data-serialization_formats")[CBOR] ?
CBOR is supported in #link("https://doc.qt.io/qt-6/cbor.html")[QT6]

It could be something liket that :
#let code = read("output.json")
#code-block(code, "json", title: "output file format")

== QrDataBlock element

QrDataBlock stands for *quantification run data block* it handles quantification data for a single MSrun.

#let code = read("qr_data_block.json")
#code-block(code, "json", title: "qr_data_block element format")


#let code = read("output.json")
#code-block(code, "json", title: "output file format")



= MSrun retention time alignment

#code-block(
  "std::shared_ptr<pappso::MsRunRetentionTime<QString>> & mcql::MsRunPeptideList::buildMsRunRetentionTimeSp(const mcql::AlignmentMethodSp &alignment_method)",
  "cpp",
  title: "MS run retention time function",
)


= Peak quality code

/ aa: best quality : many MS2 fragmentation event, only one peak directly detected
/ zaa: same as aa, but this charge state was not directly observed in MS2 fragmentation events in this MSrun
/ a: good quality, single MS2 fragmentation event, one peak detected
/ za: same as a, but this charge state was not directly observed in MS2 fragmentation events in this MSrun
/ ab: many MS2 fragmentation event, but more than one peak detected, the greater peak (area) is chosen, it is obviously fragmented... perhaps a hint to check for peak detection parameters
/ zab: same as ab, but this charge state was not directly observed in MS2 fragmentation events in this MSrun
/ b: peak obtained by "match between run" on the mean aligned observed retention times in MS2 fragmentation events *and* also matching with the retention time given by other detected and quantifified MS1 apex peaks in other MS runs
/ c: peak obtained by "match between run" only on the mean of aligned observed retention times in MS2 fragmentation events
/ d: peak obtained by "match between run" only matching with the retention time given by other detected and quantifified MS1 apex peaks in other MS runs

/ missed: no peak detected, no quantification


= Match between run difference with legacy MassChroQ

The match between run process behaviour is slightly different between legacy MassChroQ and MassChroQ3. The new process is more conservative as it will not try to find different peptide charge state if the peptide was observed for an MSrun in an other charge state.

An important difference is that MassChroQ3 reports missed peaks : this lead to a natural data cleaning when used with MCQR. Indeed, if the most theoretical abundant isotope is not found in *any* msruns, then it will be discarded.
This lead to less quantified peptides but with a better quality.
indentation
