metrics¶
Compare a predicted image with a reference image and write the scores to a JSON file. Use it to evaluate a segmentation (Dice, Jaccard, Hausdorff distance and related overlap scores) or a reconstruction or denoising result (squared error, peak signal-to-noise ratio, and so on).
Description¶
metrics has two families of scores:
- label metrics treat the images as label maps, in which every voxel holds an integer class. They are computed for each label separately, by asking how well the voxels predicted as that label coincide with the voxels that carry that label in the reference;
- image metrics treat the images as continuous intensities and measure the voxel-wise difference between them.
You choose the metrics with -m, and a single call can mix both families. The
groups labels and images stand for every label metric and every image metric,
respectively, and can be combined with each other and with individual metrics, as
in -m labels mse. The data are loaded once per family.
Inputs¶
--pred (-p) is the image to evaluate and --ref (-r) is the ground truth.
Each can be given as several files. Every file is read as a volume with up to
three spatial dimensions and, optionally, a channel dimension: a 3D file has one
channel, and a 4D file has one channel per volume along its fourth dimension. The
channels of all the files you list are stacked, in order, to form one multi-channel
image. The stacked prediction and reference must have exactly the same shape.
The command compares shapes only. It does not check that the orientation
matrices agree, so make sure that both images are on the same voxel grid; the
orientation matrix of the reference is used only to convert millimetres to
voxels for the SSIM window.
For label metrics, the data are read as stored (not rescaled) and converted to integers. For image metrics they are read as floating-point values, with the scaling stored in the file applied. A call that asks for both families therefore reads each image twice, once in each way.
Output¶
The scores are written to a JSON file. Its name is built from the output
template, which by default is {dir}{sep}{base}.metrics.json; the placeholders
refer to the first file given to --pred (see Options). The file contains an object with one
entry per requested metric. Each entry is itself an object that maps either a
label (label metrics) or a channel number starting at 0 (image metrics) to the
score, and that carries one more entry for every averaging mode you asked for
with -s. The keys are strings, since JSON has no numeric keys.
When you give several -p/-r pairs, each holding a single channel, the
image metrics are keyed by the base name of each prediction (for example pred
and pred2) instead of the channel number. The micro and macro entries keep
their names. This lets --append keep the results of different pairs apart, as
long as the base names are distinct. For example,
writes seg.metrics.json with a content of the form
{
"dice": {
"1": 0.94,
"2": 0.87,
"macro": 0.905
},
"jaccard": {
"1": 0.89,
"2": 0.77,
"macro": 0.83
}
}
If the output file already exists, it is overwritten, unless you pass --append:
the new scores are then merged into the existing file, metric by metric. Entries
of a metric that are not recomputed (other labels, or averages you did not ask
for this time) are kept, and entries that are recomputed are replaced. Metrics that
the file does not contain yet are added, and the file is created if it does not
exist.
The values above only illustrate the layout. Ratios that are undefined, such as
a precision for a label that the prediction never uses, are written as NaN.
If anything goes wrong, for example a shape mismatch between the prediction and
the reference, metrics prints a single [ERROR] ... line to the standard error
stream, without a Python traceback, and exits with status 1 (see
Errors). The message is the text of the underlying
exception, so it can be terse.
Label metrics¶
For each label \(\ell\) that occurs in the reference image, the prediction and the reference are reduced to two binary masks, "is \(\ell\)" or "is not \(\ell\)", and the voxels of the whole image (all channels together) are counted:
- TP (true positives): labelled \(\ell\) in both images;
- FP (false positives): labelled \(\ell\) in the prediction only;
- FN (false negatives): labelled \(\ell\) in the reference only;
- TN (true negatives): not labelled \(\ell\) in either.
Let \(P = TP + FN\) be the number of voxels of the reference with label \(\ell\) and
\(N = TN + FP\) the number of the other voxels. Only labels present in the
reference are scored; a label that appears only in the prediction is ignored.
In particular, spurious labels in the prediction (false-positive labels) are
never scored, so they do not lower the macro average. The hausdorff
metrics behave differently, see below.
The labels listed with -x are never scored and never count as foreground
(by default, label 0, which usually stands for the background).
Excluding every label of the reference
If -x removes all the labels of the reference, there is nothing left to
score. Without -s, each label metric is written as an empty object.
With -s macro, micro or weighted, the average would divide by zero:
metrics stops with [ERROR] division by zero and exit status 1, and
writes no file.
| Metric | Name | Definition |
|---|---|---|
dice |
Dice coefficient (F1 score) | \(2\,TP / (2\,TP + FP + FN)\) |
jaccard |
Jaccard index (intersection over union) | \(TP / (TP + FP + FN)\) |
accuracy |
Accuracy | \((TP + TN) / (P + N)\) |
ppv |
Positive predictive value (precision) | \(TP / (TP + FP)\) |
npv |
Negative predictive value | \(TN / (TN + FN)\) |
fdr |
False discovery rate | \(FP / (TP + FP)\) |
tpr |
True positive rate (sensitivity, recall) | \(TP / P\) |
fpr |
False positive rate | \(FP / N\) |
fnr |
False negative rate (miss rate) | \(FN / P\) |
tnr |
True negative rate (specificity) | \(TN / N\) |
plr |
Positive likelihood ratio | \(\text{TPR} / \text{FPR}\) |
nlr |
Negative likelihood ratio | \(\text{FNR} / \text{TNR}\) |
hausdorff |
Hausdorff distance | See below |
hausdorff95 |
Hausdorff distance, 95th percentile | See below |
Hausdorff distance¶
For a label \(\ell\), hausdorff is the largest distance, over all voxels that the
prediction assigns to \(\ell\), to the closest voxel that the reference assigns to
\(\ell\). It is therefore a directed distance, from prediction to reference, and
not the symmetric Hausdorff distance \(\max(d(P, R), d(R, P))\): a part of the
reference that the prediction misses does not increase it. To obtain the
symmetric distance, run metrics a second time with -p and -r swapped and a
different -o, and keep the larger of the two values for each label.
hausdorff95 replaces the maximum with the 95th percentile of these distances,
which is much less sensitive to a few stray voxels. Distances are Euclidean and
expressed in voxels; the voxel size is not applied.
Unlike the overlap metrics, which use the labels of the reference, hausdorff
and hausdorff95 are computed for the union of the labels of the reference and
of the prediction (minus --exclude). A label that is missing from either image
has no defined distance and is reported as inf (Infinity in the JSON output).
Such labels are left out of the macro, weighted and micro averages, with a
warning that names them (issued only when -s is given); if no label remains,
the averages are inf. In particular, a channel whose reference lacks a label
that its prediction has yields inf for that label.
With several -p/-r pairs, or 4D files, distances are computed within each
channel, and the value of a label pools the distances of all channels.
Image metrics¶
Image metrics are computed for each channel separately, from the difference between prediction and reference, then optionally averaged.
| Metric | Value computed for each channel |
|---|---|
mse |
Mean squared error, the mean of \((\text{pred} - \text{ref})^2\). |
rmse |
Root mean squared error, the square root of mse. |
mrse |
Mean of the absolute difference, \(\text{mean}\,\lvert \text{pred} - \text{ref} \rvert\). The built-in help calls it "mean root squared error". |
medae |
Median absolute error (MedAE), the median of the absolute difference, \(\text{median}\,\lvert \text{pred} - \text{ref} \rvert\), as in scikit-learn's median_absolute_error. It is the median error between the two images, not the deviation from their own median. The former name mad is still accepted as a deprecated alias: it triggers a warning and the scores are written under medae. |
psnr |
Peak signal-to-noise ratio in decibels, \(10 \log_{10}(L^2 / \text{mse})\), where \(L\) is the width of the dynamic range. |
ssim |
Structural similarity index. See below. |
Dynamic range¶
psnr and ssim need the dynamic range of the data, whose width is
\(L = \text{max} - \text{min}\). It is set with --range [[MIN=0] MAX]: a single
number is taken as MAX, with MIN equal to 0. Without --range, the range
is that of the reference, from its minimum to its maximum. It is measured
in each channel separately, or over all channels together for the micro score.
SSIM¶
The structural similarity index compares, in a local window, the mean (\(\mu\)), variance (\(\sigma^2\)) and covariance (\(\sigma_{xy}\)) of the prediction \(x\) and the reference \(y\) (Wang et al., 2004):
The score is the mean of the local indices over the image. --ssim-fwhm sets
the size of the window, as one value, or one per axis, optionally followed by
a unit, vox (voxels, the default) or mm (millimetres, converted to voxels
with the voxel size of the reference). Three variants exist:
- by default, a sliding square window whose side is the window size, rounded up to an integer number of voxels (3 by default). The statistics are computed where the window fits entirely inside the image;
- with
--ssim-gauss, a Gaussian window whose full width at half maximum is the window size; - with
--ssim-fwhm full, global statistics: the whole image is a single window.
Averaging¶
Without -s, the output contains one score per label or channel. -s adds a
summary score to every metric. It can be given several modes at once.
| Mode | Meaning |
|---|---|
macro |
The unweighted mean of the per-label (or per-channel) scores. Every label counts equally. |
micro |
The score recomputed after pooling the counts (or voxels) of all labels or channels. Large structures dominate. |
weighted |
The mean of the per-label scores, weighted by the number of reference voxels of each label (\(P\)). Available for label metrics, including the Hausdorff distances. |
The micro score of a label metric uses the sums of TP, TN, FP and FN over the
labels. For hausdorff, it is the maximum (or percentile) of the distances
pooled over the labels that are present in both images. For image metrics, micro of mse is the mean over all
voxels of all channels, and of medae the median over all of them; for mrse it
is the mean, over voxels, of the Euclidean norm of the difference vector across
channels, which is a different quantity from the per-channel mean of absolute
errors.
weighted has no meaning for image metrics. When label and image metrics are
requested together, -s weighted applies to the label metrics only: image metrics
keep their micro and macro scores and get no weighted entry. If only image
metrics are requested, metrics warns that -s weighted has no effect and runs
normally.
Arguments¶
metrics takes no positional arguments. Everything is given through options.
Options¶
Inputs and outputs¶
| Option | Value | Description | Default |
|---|---|---|---|
-p, --pred |
FILE... |
Predicted image(s). Files are stacked as channels. See above. | required |
-r, --ref |
FILE... |
Reference image(s), with the same total shape as the prediction. | required |
-m, --metrics |
METRIC... |
Metrics to compute: dice, jaccard, accuracy, ppv, npv, fdr, tpr, fpr, fnr, tnr, plr, nlr, hausdorff, hausdorff95 (label metrics, or labels for all of them); mse, rmse, mrse, medae, psnr, ssim (image metrics, or images for all of them). The two families can be mixed. See above. |
required |
-s, --average |
AVERAGE... |
Summary scores to add: micro, macro, weighted. weighted applies to label metrics only and is ignored, with a warning, if no label metric is requested. See above. |
none |
-x, --exclude |
LABEL... |
Labels that are never scored as foreground. Use none to exclude nothing. Label metrics only. |
0 |
-o, --output |
FILE |
Name or template of the JSON file. Only one value is accepted. | {dir}{sep}{base}.metrics.json |
-a, --append |
[BOOL] |
Merge the scores, metric by metric, into the output file if it exists, instead of overwriting it. See above. | off |
The output template accepts {dir} (directory of the first prediction, . if none),
{base} (its name without extension; .nii.gz counts as one extension), {ext}
(its extension) and {sep} (the path separator). See
Output filenames.
Image metric parameters¶
| Option | Value | Description | Default |
|---|---|---|---|
--range |
[[MIN=0] MAX] |
Dynamic range of the data, used by psnr and ssim. A single value is taken as MAX, with MIN equal to 0. See above. |
min and max of the reference |
--ssim-fwhm |
FWHM... [UNIT] or full |
Window size for ssim, one or more numbers and an optional unit, vox (default) or mm; or full for global statistics. See above. |
3 |
--ssim-gauss |
[BOOL] |
Use a Gaussian window with the given FWHM for ssim, instead of a sliding square window. |
off |
Hardware¶
| Option | Value | Description | Default |
|---|---|---|---|
--cpu |
[N] |
Compute on the CPU. N is the number of threads; 0 (the default) keeps the PyTorch default. |
CPU |
--gpu |
[ID] |
Compute on a GPU; ID is the device index. Falls back to the CPU, with a warning, if CUDA is not available. |
See Hardware for the general behaviour of device options.
Known issues¶
- Only the first prediction file names the output, so a call with several prediction files writes a single JSON file. Label metrics pool all the pairs into one score per label, so there is no per-file entry to tell them apart.
Examples¶
Compute the Dice coefficient of each label of a segmentation against a manual
reference. The scores go to seg.metrics.json, and label 0 is skipped:
Add the Jaccard index and the 95th-percentile Hausdorff distance, with the macro and micro averages:
Treat both 0 and 4 as labels that should not be scored:
Score every label, including the background, and choose the output file:
nitorch metrics -p seg.nii.gz -r manual_seg.nii.gz -m dice tpr ppv -x none \
-o results/seg_scores.json
Evaluate a denoised image against a clean one, with intensities in a range of 0 to 1000:
Evaluate a two-contrast result, giving each contrast its own file, and add the mean over the contrasts:
nitorch metrics -p pred_T1w.nii.gz pred_T2w.nii.gz \
-r ref_T1w.nii.gz ref_T2w.nii.gz \
-m mse mrse -s macro
Compute SSIM with a Gaussian window of 5 mm FWHM, and with global statistics, using the range of the reference:
nitorch metrics -p denoised.nii.gz -r clean.nii.gz -m ssim --ssim-fwhm 5 mm --ssim-gauss
nitorch metrics -p denoised.nii.gz -r clean.nii.gz -m ssim --ssim-fwhm full
Compute all the label metrics, then add an image metric to the same file:
nitorch metrics -p seg.nii.gz -r manual_seg.nii.gz -m labels -s macro -o scores.json
nitorch metrics -p seg.nii.gz -r manual_seg.nii.gz -m mse -a -o scores.json