library("rplatypus")
masks <- predict(fit, "unet") # an array, on the model's grid
masks <- predict(fit, "unet", space = "source") # a list, on each scan's own grid
# The two worst cases, without holding the other 198.
cases <- evaluate_cases(fit, split = "validation")
worst <- cases$case[order(cases$dice)][1:2]
two <- predict(fit, split = "validation", cases = worst)
# Or walk the split, keeping one number per image and none of the masks.
coverage <- predict(fit, split = "validation", each = function(case, mask) mean(mask > 1))
# `space = "source"` returns a list and not an array, which is the API being
# honest: scans differ in size, a stacked array needs one shape, and resizing
# them to match is how a mask ends up describing anatomy it was not computed from.Predict masks
Description
Predict masks
Usage
## S3 method for class 'platypus_fit'
predict(
object,
model = NULL,
split = "test",
type = c("class", "probability", "channels"),
space = c("model", "source"),
cases = NULL,
each = NULL,
decode = NULL,
...
)
Arguments
object
|
A platypus_fit().
|
model
|
Which model, by the name given in the specification. Defaults to the first one trained. |
split
|
Which data to predict on. |
type
|
On an instance fit, regions - overlapping classes - “class” is refused rather than computed. The channels are independent binary questions and do not compete, so the most likely one is not an answer: a pixel may be in three classes and naming one of them drops the rest with nothing in the result to say so. Ask for “probability” and write it with save_class_masks().
|
space
|
“model” or “source”; see above. Note that the two return different shapes of thing, an array and a list, because they are different things.
|
cases
|
Case names to predict, as evaluate_cases() reports them. With this the result is a named list of just those, in the order asked for. A name that is not in the split is an error rather than a missing entry.
|
each
|
A function called as each(case, mask) once per image, instead of the predictions being returned together. Whatever it returns is collected under the case’s name; return NULL from it and nothing accumulates. This is the form that does not need the split to fit in memory.
|
decode
|
Which decoding turns the predicted channels into objects, by the name used in the model’s decode list. Unset takes the model’s first, which is the engine’s own default rather than one restated here. Instance models only; for anything else it is refused, because a semantic or detection fit decodes nothing.
|
…
|
Unused. |
Value
With space = “model”: for “class”, an integer array of image x height x width, classes numbered from 1 to match the colormap; for “probability”, the same with a trailing class axis. Tiled models return images at their original size, reassembled.
With space = “source”: a list of such arrays, one per scan, each on that scan’s grid.
On an instance fit: a list of integer label maps named by case, objects numbered from 1 and 0 for background, one entry per image. A list rather than an array for the reason the detection one is a list, and named rather than ordered because a list paired against case names by position is one dropped batch away from naming the wrong picture. Draw them with overlay_instances() and measure them with instance_sizes().
Which grid the answer is on
space = “model” returns everything on the grid the model worked at, stacked into one array. That is what training saw, and it is the only form that can be a single array, because a stack needs one shape.
space = “source” maps each prediction back onto the grid of the scan it was computed from - undoing the resampling and the crop - and therefore returns a list, one mask per scan. This is what you want for anything that has to meet the original data: laying a mask over the scan in a viewer, writing it beside the scan with save_volumes(), or measuring it in millilitres of the scan’s own voxels. Scans differ in size, so the list is not a limitation being worked around - resizing them to a common shape is exactly how a mask ends up describing anatomy it was not computed from.
Two things are lost mapping back, and neither can be recovered. Interpolation softens a boundary, so the mask returns slightly smoother than the model drew it. And where reading cropped anatomy away, the mask comes back padded with background - which means not examined, not nothing there. Give the model an input_shape that covers the anatomy if that distinction matters.
One at a time, when the split will not fit
The default returns everything together, which for a tiled run is the whole split at full resolution: 200 fundus photographs at 2048 x 2048 is about 6.7 GB before anything is drawn, and the engine’s own predict was killed by exactly that. Two arguments avoid it.
cases names the ones wanted. each hands them over one at a time and keeps only what the function gives back. They compose: cases says which, each says what to do with them.
The saving is in memory and not in time. The engine’s stream is sequential, so asking for two cases out of two hundred still runs the model over two hundred - it simply never holds more than one answer at once.