diff --git a/astro.config.mjs b/astro.config.mjs index c10cc14..d658380 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -3,6 +3,9 @@ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import Icons from 'unplugin-icons/vite'; import tailwindcss from '@tailwindcss/vite'; +import remarkMath from 'remark-math'; +import rehypeDocument from 'rehype-document'; +import rehypeKatex from 'rehype-katex'; // https://astro.build/config export default defineConfig({ @@ -78,6 +81,7 @@ export default defineConfig({ { label: 'Advanced Tutorials', items: [ + { label: 'Conventions', slug: 'advanced-tutorials/conventions' }, { label: 'BIDS Input', slug: 'advanced-tutorials/bidsinput' }, { label: 'BIDS Output', slug: 'advanced-tutorials/bidsoutput' }, { label: 'MultiQC', slug: 'advanced-tutorials/multiqc' }, @@ -114,6 +118,19 @@ export default defineConfig({ ], }) ], + markdown: { + remarkPlugins: [remarkMath], + rehypePlugins: [ + [ + rehypeDocument, + { + // Get the latest one from: . + css: 'https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css' + }, + ], + rehypeKatex + ], + }, vite: { plugins: [ Icons({ compiler: 'astro' }), diff --git a/package.json b/package.json index 25aeaf2..7adf16c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "astro": "^5.4.3", "astro-matomo": "^1.8.1", "pnpm": "^10.6.2", + "rehype-document": "^7.0.3", + "rehype-katex": "^7.0.1", + "remark-math": "^6.0.0", "sharp": "^0.32.6", "tailwindcss": "^4.0.13", "uninstall": "^0.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd9374a..d6723e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,15 @@ importers: pnpm: specifier: ^10.6.2 version: 10.6.2 + rehype-document: + specifier: ^7.0.3 + version: 7.0.3 + rehype-katex: + specifier: ^7.0.1 + version: 7.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 sharp: specifier: ^0.32.6 version: 0.32.6 @@ -747,6 +756,9 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/katex@0.16.7': + resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/mdast@3.0.15': resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} @@ -1049,6 +1061,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + common-ancestor-path@1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} @@ -1294,6 +1310,12 @@ packages: hast-util-format@1.1.0: resolution: {integrity: sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==} + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + hast-util-from-html@2.0.3: resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} @@ -1360,6 +1382,9 @@ packages: hastscript@6.0.0: resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} + hastscript@8.0.0: + resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} @@ -1494,6 +1519,10 @@ packages: engines: {node: '>=6'} hasBin: true + katex@0.16.22: + resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} + hasBin: true + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -1648,6 +1677,9 @@ packages: mdast-util-gfm@3.1.0: resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} + mdast-util-mdx-expression@2.0.1: resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} @@ -1705,6 +1737,9 @@ packages: micromark-extension-gfm@3.0.0: resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} + micromark-extension-mdx-expression@3.0.0: resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==} @@ -2031,12 +2066,18 @@ packages: resolution: {integrity: sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==} hasBin: true + rehype-document@7.0.3: + resolution: {integrity: sha512-g5zq6i2FwWVBVdyVi0Jw/5MRvsHj3wuJCn+QeyOjm29QBpTG4r1iUElyH9GhfWx5fB27ZEApA53RdAiYGBb4zQ==} + rehype-expressive-code@0.40.2: resolution: {integrity: sha512-+kn+AMGCrGzvtH8Q5lC6Y5lnmTV/r33fdmi5QU/IH1KPHKobKr5UnLwJuqHv5jBTSN/0v2wLDS7RTM73FVzqmQ==} rehype-format@5.0.1: resolution: {integrity: sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==} + rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + rehype-parse@9.0.1: resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} @@ -2061,6 +2102,9 @@ packages: remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} + remark-mdx@1.6.22: resolution: {integrity: sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==} @@ -3368,6 +3412,8 @@ snapshots: '@types/js-yaml@4.0.9': {} + '@types/katex@0.16.7': {} + '@types/mdast@3.0.15': dependencies: '@types/unist': 2.0.11 @@ -3759,6 +3805,8 @@ snapshots: commander@2.20.3: optional: true + commander@8.3.0: {} + common-ancestor-path@1.0.1: {} confbox@0.1.8: {} @@ -4010,6 +4058,19 @@ snapshots: html-whitespace-sensitive-tag-names: 3.0.1 unist-util-visit-parents: 6.0.1 + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + hast-util-from-html@2.0.3: dependencies: '@types/hast': 3.0.4 @@ -4216,6 +4277,14 @@ snapshots: property-information: 5.6.0 space-separated-tokens: 1.1.5 + hastscript@8.0.0: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + hastscript@9.0.1: dependencies: '@types/hast': 3.0.4 @@ -4316,6 +4385,10 @@ snapshots: json5@2.2.3: {} + katex@0.16.22: + dependencies: + commander: 8.3.0 + kleur@3.0.3: {} kleur@4.1.5: {} @@ -4516,6 +4589,18 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + mdast-util-mdx-expression@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 @@ -4698,6 +4783,16 @@ snapshots: micromark-util-combine-extensions: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.7 + devlop: 1.1.0 + katex: 0.16.22 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + micromark-extension-mdx-expression@3.0.0: dependencies: '@types/estree': 1.0.6 @@ -5181,6 +5276,13 @@ snapshots: dependencies: jsesc: 0.5.0 + rehype-document@7.0.3: + dependencies: + '@types/hast': 3.0.4 + hastscript: 8.0.0 + unified: 11.0.5 + vfile: 6.0.3 + rehype-expressive-code@0.40.2: dependencies: expressive-code: 0.40.2 @@ -5190,6 +5292,16 @@ snapshots: '@types/hast': 3.0.4 hast-util-format: 1.1.0 + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/katex': 0.16.7 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.22 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.3 + rehype-parse@9.0.1: dependencies: '@types/hast': 3.0.4 @@ -5245,6 +5357,15 @@ snapshots: transitivePeerDependencies: - supports-color + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + remark-mdx@1.6.22: dependencies: '@babel/core': 7.12.9 diff --git a/src/content/docs/advanced-tutorials/conventions.mdx b/src/content/docs/advanced-tutorials/conventions.mdx new file mode 100644 index 0000000..0c04f70 --- /dev/null +++ b/src/content/docs/advanced-tutorials/conventions.mdx @@ -0,0 +1,203 @@ +--- +title: Conventions +description: Any and all conventions. +--- + +import { Steps } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { FileTree } from '@astrojs/starlight/components'; + +## Spatial transformations + +### Format + +The common file format for anything that implies transformations is the [ITK format](https://github.com/ANTsX/ANTsPy/wiki/ANTs-transform-concepts-and-file-formats) : + +| Transformation type | format | +|------------------------------------------------|---------| +| linear transformations (e.g. rigid and affine) | .mat | +| non-linear warps and deformation fields | .nii.gz | + +:::note +Modules can output their **format of preference** still, but must provide a **conversion module** if they decide to do so. Take a look at the +[current conversion module](api/modules/registration/convert), which already supports most common formats. +::: + +### Order of operations + +To guarantee perfect interaction between all components of its library, nf-neuro imposes **linear algebra order of operations (from right to left)** +when applying transformations. + +:::note +Modules that use **few transforms** (e.g. only an affine to a template) can **bypass** this but are encouraged to +implement **parsing of the sequence of transforms** in order to provide exact registration universally. +::: + +#### Subworkflows and pipelines + +To define a **sequence of transformations**, use either **a list** or a **glob pattern** to a list of transformation files + + + +Consider the following sequence of transformations : + +```math +moving \xrightarrow[T_1]{} \bullet \xrightarrow[T_2]{} \bullet \xrightarrow[T_3]{} fixed +``` + +and 3 channels, one for each transformation. Use the following to output a channel in the right order : + +```groovy +// All channels are joined on meta, the basic required +// metadata defining a subject, minimally [id: id] +ch_transformations = ch_T1 + .join(ch_T2) + .join(ch_T3) + .map{ meta, t1, t2, t3 -> [ meta, [ t3, t2, t1 ] ] } +``` + + + +Consider the following sequence of transformations : + +```math +moving \xrightarrow[T_1]{} \bullet \xrightarrow[T_2]{} \bullet \xrightarrow[T_3]{} fixed +``` + +corresponding to the following example files : + + +- sub-007 + - transforms + - transformX.mat + - transformY.nii.gz + - transformZ.mat + + +Using the `Channel.fromFilePairs` filesystem parser, which implements alphabetical ordering +by default, you obtain the correct oredering using : + +```groovy +ch_transforms = Channel + .fromFilePairs("**/transforms/transform*.{mat,nii.gz}", size: -1) + { file -> file.parent.parent.name } // This defines the ID for the file, here subject sub-007 + .map{ id, t1, t2, t3 -> [ [id: id], [ t3, t2, t1 ] ] } // [id: id] is the basic required metadata defining a subject +``` + + + +:::tip +Here, we use semantics when joining the channel (apply `ch_T1`, then `ch_T2` and finally `ch_T3`) and +switched to **linear algebra format at the end**. Only because it makes it more readable. Note that +**nextflow code is compiled, so focus on readability**. +::: + +#### Modules that perform registration + +Registration modules **must** output their transforms in lists following **linear algebra ordering**. +Additionally, for **compatibility with legacy modules**, they also **must** provide individual outputs +for an affine and a deformation field (and their inverses). + +The following output channels **are required** : + + + + +| Channel name | Transformation | +|----------------------------------|----------------------------------------------------------------------------------------------------------------------------------| +| **image_transform** | Transformation from moving space to fixed space | +| **inverse_image_transform** | Transformation from fixed space to moving space | +| **tractogram_transform** | Reversed and inversed transformation from moving space to fixed space
(usually the same as the **inverse_image_transform**) | +| **inverse_tractogram_transform** | Reversed and inversed transformation from fixed space to moving space
(usually the same as the **image_transform**) | + +Transformation files **must** be collected in one single [path](https://www.nextflow.io/docs/latest/process.html#multiple-output-files) +variable with a [glob pattern](https://docs.oracle.com/javase/tutorial/essential/io/fileOps.html#glob), using the **arity** parameter to +cast it as a **list of files** (see example below). + +```groovy +output: + tuple val(meta), path("*__transform*.{mat,nii.gz}", arity: '1..*') , emit: image_transform + tuple val(meta), path("*__inverse_transform*.{mat,nii.gz}", arity: '1..*'), emit: inverse_image_transform + tuple val(meta), path("*__inverse_transform*.{mat,nii.gz}", arity: '1..*'), emit: tractogram_transform + tuple val(meta), path("*__transform*.{mat,nii.gz}", arity: '1..*') , emit: inverse_tractogram_transform +script: + def prefix = meta.id + """ + # Moving space + touch ${prefix}__transformZ.mat + touch ${prefix}__transformY.mat + touch ${prefix}__transformX.nii.gz + # Fixed space + touch ${prefix}__inverse_transformZ.nii.gz + touch ${prefix}__inverse_transformY.mat + touch ${prefix}__inverse_transformX.mat + # Moving space + """ +``` +
+ + + +| Channel name | Transformation | +|--------------------|---------------------------------------------------------| +| **affine** | Linear transformation from moving space to fixed space | +| **warp** | Non-linear deformation from moving space to fixed space | +| **inverse_affine** | Linear transformation from fixed space to moving space | +| **inverse_warp** | Non-linear deformation from fixed space to moving space | + +For compatibility with modules that don't support parsing the transformation sequences, you **must** +provide **single file transformations between fixed and moving spaces**. There are many options here, +**rely on your judgment and knowledge** of the module you are implementing. Here are some examples : + + +
  • Use a **transformation composition tool**, like [ComposeMultiTransform](https://manpages.debian.org/stretch/ants/ComposeMultiTransform.1.en.html) +from [ANTs](https://github.com/ANTsX/ANTs).
  • +
  • Use the transformations files that brings the moving image closest to the fixed image.
  • + + +
    +
    + +--- + +#### Modules that apply transformations + +All modules that use transformations and need them provided in input **should** expect a chain of transformations +in the form of **a list** and ordered as stated above. If they only use parts of it, they should parse through it to select +the transformations to apply. Define the **input variable** using a `path` and the **arity** parameter in order to ensure +it is interpreted as a list in the script section of the module : + +```groovy +input: + tuple val(meta), ..., path(transformations, arity: '1..*') +... +script: +""" +for transform in $transformations +do + echo "I'm doing good work using transformation : \$transform" +done +""" +``` + +:::note +If you consider you cannot parse through the transformation chain, you can still implement your inputs using **single-file transforms**. +However, don't wait for the PR to get in [contact with the nf-neuro team](https://github.com/scilus/nf-neuro/issues) as we can help you +evaluate the use-case and potentially implement the parsing. +::: + +### Filenames convention + +nf-neuro aligns with the [BIDS specification](https://bids-specification.readthedocs.io/en/stable/) for management and to ensure observability +of files generated by its modules. BIDS is not complete yet and doesn't define a convention for **spatial transformation derivatives**. Refer +below for a custom convention to apply in your modules : + +| Position | Keyword | Type | Value | Mandatory | Description | +|----------|---------|---------|-----------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| first | index | Integer | > 0 | false | Position of the transformation in the sequence, following **linear algebra ordering**
    (transform `N` comes first, `0` comes last). | +| any | from | String | any | true | Identifier for the **source space**. | +| any | to | String | any | true | Identifier for the **target space**. | +| any | mode | String | image
    points | true | Kind of transformation. Either for **image** (voxel grids) or **points** (tractogram, surface, streamline). | +| any | space | String | ras
    lps
    ... | true | Orientation of image referential, as a triplet of **(l)eft/(r)ight**, **(a)nterior/(p)osterior** and **(i)nferior/(s)uperior**. Alteratively, use **orig** to indicate the **fixed image** defines this space. | +| any | unit | String | vox
    mm | true | Specifies if transformation acts on **millimetric** (mm) image space or **unitless** (vox) image space. | +| last | desc | String | affine
    warp | true | Type of transformation. Use **affine** to encompass any linear transformation, and **warp** for deformation and displacement fields. | \ No newline at end of file