When something is wrong: the conditions this package raises

Description

Every failure that comes back from the engine is raised as a classed condition, so a script can tell one kind of problem from another instead of matching on the message. Until now that was true and undocumented, which made it true and useless.

What the class vector looks like

Three or four entries, outermost first:

platypus_config_error    the specific kind
platypus_error           every failure from this package, which is the one to catch
error                    an ordinary R error
condition

So tryCatch(…, platypus_error = ) catches everything this package raises and nothing else, and a specific class narrows it. platypus_error appears once even when the engine sends no kind at all.

What to catch

platypus_error, unless there is a reason not to. The specific kinds come from the engine, so the list below belongs to the version this package pins, and a later engine may add one. Catching the base class is the thing that keeps working.

Two are worth naming on purpose:

platypus_config_error
A specification that does not describe a runnable experiment. This one carries data: conditionCall() aside, the condition has a problems data frame with one row per problem - where, problem and, when it is short enough to be useful, got - and a source naming the YAML file if it came from one. The engine reports every problem it found rather than the first, so the frame is what makes "three things are wrong" readable.
platypus_training_stopped
Not a defect. A callback ended the run early, which is what it was asked to do, and anything scoring the result afterwards should treat it as a finished run rather than a failure.

The kinds the pinned engine can send

One class per kind, prefixed. tests/testthat/test-conditions.R holds this list to what the engine actually defines, so a kind added upstream fails here rather than arriving undocumented.

platypus_config_error            a specification that cannot be run
platypus_engine_error            the engine could not do what the specification asked
platypus_model_error             the architecture could not be built as described
platypus_data_error              the dataset on disk is not what the specification says
platypus_image_error             an image could not be read, or is not the shape claimed
platypus_mask_error              a mask does not match its colormap
platypus_channel_error           one channel per file, and the files do not agree
platypus_volume_error            a volume, its orientation or its spacing
platypus_dicom_error             a DICOM file or series
platypus_augmentation_error      a transform that cannot do what was asked of it
platypus_detection_error         detection metrics
platypus_detection_data_error    annotations, boxes or classes
platypus_instance_error          a separation target, or decoding one into objects
platypus_instance_data_error     the per-object mask files a case has, or has not
platypus_weights_error           weights that do not belong to this model
platypus_training_stopped        a callback ended the run early
platypus_unknown_case            a case or model name that is not in this run

platypus_unknown_case is the one with no class behind it in the engine: the R bridge raises it itself, because asking for a case that does not exist is a question the bridge can answer without troubling Python.

Why R has more of these than Python does

The engine exports three exception classes - PlatypusError, ConfigError, WeightsError - and raises the other twelve internally, so a Python caller catches PlatypusError and inspects it. R derives its class from the kind the engine sends with every failure, so all of them arrive as catchable names. That is a wider surface on this side, and deliberately so: R has no exception hierarchy to lean on, and inherits() is cheaper than parsing a message.

See Also

platypus_status(), for when the problem is that the engine never started.

Examples

library("rplatypus")

# A specification with two problems, caught and read as data rather than as text.
spec <- platypus_spec(
  data = segmentation_data("train", "valid", colormap = binary_colormap),
  models = list(u_net("m", input_shape = c(64, 64), loss = "focaal"))
)

tryCatch(
  platypus_fit(spec),
  platypus_config_error = function(e) {
    print(e$problems)        # one row per problem: where, problem, got
    invisible(NULL)
  },
  platypus_error = function(e) {
    message("something else went wrong: ", conditionMessage(e))
  }
)