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 | 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 148x 2x 2x 2x 2x | /**
* Per-cell contact QC and cell-cycle phasing, as a descriptor.
*
* The one task in the catalogue whose unit is a **cell** rather than a bin, a
* sequence or a feature, and it exists because the `.scool` interaction is built
* around a decision nothing in the app was helping anybody make. A container
* opens as its Cells table, the reader ticks some, and the ticked ones are
* summed (§3.8). What the table can rank them by is `nnz` and `sum` — *what the
* writer recorded*, which is a statement about the sequencing and not about the
* contacts. Depth is also the least interesting axis a single-cell Hi-C library
* varies along: cells differ mostly by **where in the cell cycle they were
* fixed**, and a pseudo-bulk summed across phases smears exactly the structure
* somebody opened the file to see.
*
* ## The measurement, and whose it is
*
* Nagano et al. (2017)'s, and it is two numbers per cell: the share of its
* contacts at a *near* separation and the share at a *mitotic* one. Plotted
* against each other the cells fall on a horseshoe, and position along it is
* position in the cycle — post-M cells carry an enormous mitotic band and almost
* no near signal, G1 the reverse, and S and G2 in between. Nothing about it is
* an inference this app makes; it is a histogram of separations, per cell, with
* the edges the paper used.
*
* ## Why the edges are parameters and not constants
*
* 23 kb, 2 Mb and 12 Mb are the paper's, and the paper is about mouse. A yeast
* chromosome is 1.5 Mb at its longest, so the mitotic band on a yeast `.scool` is
* empty by construction and the phasing plot is a column of points on the axis —
* which is a true picture of a measurement that does not apply rather than a
* result. So the edges are typed in base pairs, the defaults are the paper's,
* and the run says when the assembly cannot reach the band it was asked about.
*
* ## Why these three stayed in base pairs when the matrix settings moved to bins
*
* §18.2 asks every matrix parameter in **bins**, because a reader setting one is
* choosing a number of the file's own cells and the runner was dividing base
* pairs by the bin size to get there. Neither half of that is true here. These
* three are never converted to bins by anything: the histogram measures a
* contact's separation as `|bin2 − bin1| × binsize` and compares it in base
* pairs, so the arithmetic runs the *other* way; and the numbers are not a
* reader's choice of grid but a **citation** — a reader types what Nagano et al.
* printed, and in bins the same band would be a different number on every
* `.scool` and unrecognisable on all of them. They are also what the phasing
* plot's own axes and the table's own column heads say, and a plot's axes stay
* in base pairs (§26.2). So: base pairs, deliberately, and the one exception in
* the group.
*
* ## What it does not do
*
* It does not **cluster** the cells and it does not assign a phase label. The
* bands are a measurement with a citation; a cut through the horseshoe is a
* method with choices, and §18.9's rule about a half-good caller inside a viewer
* applies to a phase call exactly as it applies to a loop call. What the reader
* gets is the scatter, the table, and the ability to tick a region of it — the
* judgement stays theirs, which is also what they would be doing in the paper's
* own figure.
*/
import type { AnalysisContext, AnalysisTask, CostEstimate, ParamSpec, Params } from '../types'
import { READY } from '../types'
import { BALANCE_PARAM } from './shared'
/** The paper's separation edges, in base pairs. */
export const NEAR_FROM = 23_000
export const NEAR_TO = 2_000_000
export const MITOTIC_TO = 12_000_000
const PARAMS: readonly ParamSpec[] = [
{
kind: 'choice',
key: 'cells',
label: 'cells',
default: 'all',
options: [
{
value: 'all',
label: 'every cell in the file',
hint: 'What the choice is made from, so this is the answer that helps make it.',
},
{ value: 'selected', label: 'the ticked ones only' },
],
},
{
kind: 'number',
key: 'nearFrom',
label: 'near band, from',
unit: 'bp',
hint: 'Below this a contact is mostly self-ligation and unligated ends rather than structure.',
default: NEAR_FROM,
min: 0,
integer: true,
section: 'Bands',
},
{
kind: 'number',
key: 'nearTo',
label: 'near band, to',
unit: 'bp',
default: NEAR_TO,
min: 1,
integer: true,
section: 'Bands',
},
{
kind: 'number',
key: 'mitoticTo',
label: 'mitotic band, to',
unit: 'bp',
hint: 'The near band’s upper edge is this band’s lower one; beyond it is the far band.',
default: MITOTIC_TO,
min: 1,
integer: true,
section: 'Bands',
},
BALANCE_PARAM,
]
function estimate(context: AnalysisContext, params: Params): CostEstimate {
const cells = context.cells ?? 0
if (cells === 0) {
return {
class: 'free',
requests: 0,
bytes: 0,
note: 'Nothing to read.',
refusal: 'This file has no cells.',
}
}
const counted = params.cells === 'selected' ? (context.activeCells ?? cells) : cells
return {
class: 'scan',
requests: null,
bytes: null,
note:
`One pass over each of ${counted.toLocaleString()} cells’ own pixels — the same reads a ` +
'pseudo-bulk of them costs, with one accumulator per cell instead of one shared.',
}
}
export const CELL_QC_TASK: AnalysisTask = {
id: 'cellqc',
title: 'Per-cell contact QC',
blurb: 'What each cell’s contacts look like, and where in the cycle that puts it.',
keywords: ['single cell', 'scool', 'cell cycle', 'quality'],
group: '2d',
venue: 'analysis',
version: 1,
// Absent rather than waiting on every other container, which is §8.5's rule
// applied exactly: a `.cool` has no cells and nothing the reader does here
// gives it any.
availability: (context) =>
(context.cells ?? 0) > 0 ? READY : { state: 'absent' },
params: PARAMS,
outputs: ['table', 'curve'],
estimate,
}
|