Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | 2x 2x 49x 49x 49x 49x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x | /**
* The four metagenome tasks, as descriptors.
*
* Together in one module rather than four, because they are one idea seen from
* four distances — the file, the MAG, the contig, the pair — and because three
* of them are priced identically: one walk over the pixel table. Splitting them
* across four files would put four copies of the same estimate in four places.
*
* They are the answer to §18.3's problem: annotations are absent on a
* `.metacool` (§8.5), so every task that consumes a track or a feature set is
* absent there too — and without these the Analysis panel on this app's primary
* format would offer P(s), coverage, insulation and 4C and nothing that knows
* what a MAG is.
*/
import { READY, waitingFor } from '../types'
import type {
AnalysisContext,
AnalysisTask,
CostEstimate,
TaskAvailability,
} from '../types'
import { BALANCE_PARAM } from './shared'
/**
* Every one of these needs a genome layer, and a metacool is the only thing with
* one — so on a cooler they are **absent** and stay absent however many files
* the reader drops, which is what §18.1's doctrine is actually about.
*
* With *nothing* open the answer is different: a metagenome is one file away, so
* the row is listed and says so. The distinction is not "can it run now" but
* "can what the reader does next make it run".
*/
function metagenomeOnly(context: AnalysisContext): TaskAvailability {
if (!context.hasMap) return waitingFor('Open a metagenome (.metacool with a genome layer).')
return context.hasGenomes && (context.genomes?.length ?? 0) > 1 ? READY : { state: 'absent' }
}
function scanCost(note: string): CostEstimate {
return {
class: 'scan',
requests: null,
bytes: null,
note,
}
}
export const MAG_QC_TASK: AnalysisTask = {
id: 'magqc',
dev: 'metacool',
title: 'MAG contact QC',
blurb: 'Per genome: how much contact it has, and how much of it stays inside.',
keywords: ['metagenome', 'genomes', 'binning'],
group: 'metagenome',
venue: 'analysis',
version: 1,
availability: metagenomeOnly,
params: [
BALANCE_PARAM,
{
kind: 'toggle',
key: 'network',
label: 'inter-MAG matrix',
hint: 'Costs nGenomes² doubles held while it runs; needed by the network task.',
default: true,
},
],
outputs: ['table', 'matrix'],
estimate: (context) =>
scanCost(
`Walks the whole pixel table once, sorting every contact by the genomes of its two ends ` +
`across ${(context.genomes?.length ?? 0).toLocaleString()} MAGs.`,
),
}
export const MAG_NETWORK_TASK: AnalysisTask = {
id: 'magnetwork',
dev: 'metacool',
title: 'Inter-MAG network',
blurb: 'Which MAGs touch which, as a ranked edge list.',
keywords: ['metagenome', 'host', 'phage', 'plasmid'],
group: 'metagenome',
venue: 'analysis',
version: 1,
// A genuine composite: the edge list *is* the QC pass's genome-by-genome
// matrix, ranked and normalised. Declaring the dependency means the runner
// reuses a cached QC result whose parameters and framing match rather than
// walking the pixel table a second time for numbers it already has.
requires: ['magqc'],
availability: metagenomeOnly,
params: [
{
kind: 'choice',
key: 'normalise',
label: 'edge weight',
default: 'coverage',
options: [
{ value: 'raw', label: 'raw contacts' },
{ value: 'bins', label: 'per bin pair', hint: 'Divided by the two MAGs’ bin counts.' },
{
value: 'coverage',
label: 'per coverage',
hint:
'Divided by the geometric mean of the two MAGs’ intra-MAG contacts, so a deep pair ' +
'and a shallow one rank on one scale.',
},
],
},
{
kind: 'number',
key: 'minWeight',
label: 'least edge worth listing',
default: 0,
min: 0,
},
{
kind: 'number',
key: 'maxEdges',
label: 'edges to list',
default: 500,
min: 10,
integer: true,
},
],
outputs: ['table', 'matrix'],
estimate: () => ({
class: 'free',
requests: 0,
bytes: 0,
note: 'Built from the MAG QC pass, which runs first and is reused if it has already run.',
}),
}
export const MAG_PURITY_TASK: AnalysisTask = {
id: 'magpurity',
dev: 'metacool',
title: 'MAG purity and contamination',
blurb: 'Per contig: how much of its contact leaves its MAG, and where it goes.',
keywords: ['metagenome', 'contamination', 'binning'],
group: 'metagenome',
venue: 'analysis',
version: 1,
availability: metagenomeOnly,
params: [
BALANCE_PARAM,
{
kind: 'number',
key: 'minContacts',
label: 'least contact before judging a contig',
hint: 'A contig with almost no contact has no evidence either way.',
default: 10,
min: 0,
},
{
kind: 'number',
key: 'flagAbove',
label: 'flag above this outside fraction',
hint: 'A contig sending more than this share of its contact elsewhere is called out.',
default: 0.5,
min: 0,
max: 1,
step: 0.05,
slider: true,
},
],
outputs: ['table', 'intervals', 'profile'],
estimate: (context) =>
scanCost(
`Walks the whole pixel table once, sorting every contact by the contigs of its two ends ` +
`across ${context.contigs.length.toLocaleString()} sequences.`,
),
}
export const MAG_SPLIT_TASK: AnalysisTask = {
id: 'magsplit',
dev: 'metacool',
title: 'Split-MAG detection',
blurb: 'MAGs whose own contigs fall into two blocks that barely touch.',
keywords: ['metagenome', 'chimera', 'binning'],
group: 'metagenome',
venue: 'analysis',
version: 1,
availability: metagenomeOnly,
params: [
BALANCE_PARAM,
{
kind: 'number',
key: 'minContigs',
label: 'least contigs before judging a MAG',
hint: 'A two-contig MAG splits into two trivially and says nothing.',
default: 4,
min: 2,
integer: true,
},
{
kind: 'number',
key: 'flagBelow',
label: 'flag below this cross-block ratio',
hint: 'How much of a MAG’s contact crosses the split, against what stays inside a block.',
default: 0.1,
min: 0,
max: 1,
step: 0.01,
slider: true,
},
],
outputs: ['table', 'intervals'],
estimate: (context) => {
const genomes = context.genomes?.length ?? 0
return {
class: 'scan',
requests: genomes * 4,
bytes: null,
note:
`One contig-level square per MAG, ${genomes.toLocaleString()} of them, each a bounded ` +
'read.',
}
},
}
|