Creating the R Script

With our Python script completed, we now need to create the R script that it will execute.

This R script is responsible for receiving CSV data and method arguments from Python, and writing a plot as image data to STDOUT.

Our final R script will look like this.

# nolint start: commented_code_linter.
library("pythonipc");

# This method takes in a set of coordinates from a dataframe
# and draws those coordinates as line segments.
# For example, the letter "L" would be stored like this.
# letter_segments <- data.frame(
#  letter = c("L", "L"),
#  x = c(0.0, 0.0),
#  y = c(0.0, 0.0),
#  xend = c(0.0, 0.6),
#  yend = c(1.0, 0.0)
# )
draw_lines_from_dataframe <- function(df, spacing = 1, line_width = 1,
                                      color = "black",
                                      background_color = "white") {
  par(bg = background_color);
  plot(NULL, xlim = c(0, 7), ylim = c(0, 1.1), asp = 1, axes = FALSE, xlab = "",
       ylab = "");

  unique_letters <- unique(df$letter);
  for (i in seq_along(unique_letters)) {
    current_letter <- unique_letters[i];
    letter_data <- subset(df, df$letter == current_letter);
    segments(
      letter_data$x + (i - 1) * spacing, letter_data$y,
      letter_data$xend + (i - 1) * spacing, letter_data$yend,
      lwd = line_width,
      col = color
    );
  }
}

on.exit(dev.off(), add = TRUE);

invisible(pdf(NULL));

# get the output format and data FDs
with(as.list(parse.cli.args()), {
  invisible(output.type.func(output.device));
})

# read the method parameters for `draw_lines_from_dataframe`
parameter_args <- read.method.parameters();
# read the line segment data for `draw_lines_from_dataframe`
letter_segments <- read.dataframe();

# apply the line segment dataframe and other parameters
# to the `draw_lines_from_dataframe` method
do.call(draw_lines_from_dataframe, c(list(letter_segments), parameter_args));

# nolint end

Using pythonipc

Ligare contains an R package named pythonipc. This package contains utility methods for working with R script executions performed by RProcessStepBuilder.

We will use pythonipc to execute an R script from through Python, which will draw line segments to a plot and then return a PNG.

Let’s start with some initial output device configuration, and reading the commandline options specified by Python.

First, we prevent the default PDF output from occurring. We also tell R to clean up output devices when the script exits for any reason.

on.exit(dev.off(), add = TRUE);
invisible(pdf(NULL));

Next, we read in the commandline arguments.

library("pythonipc");

with(as.list(parse.cli.args()), {
   invisible(output.type.func(output.device));
})

pythonipc contains a method named parse.cli.args.

This method returns commandline argument values. Here, we use output.type.func and output.device. RProcessStepBuilder creates these for us, although we configured output.type.func using with_args(["--output-type=png"]). The value here is the R output device function matching the commandline value - in our case, it is png.

output.device is the file descriptor that R will write image data to. By default, this is "/dev/stdout", but RProcessStepBuilder handles this a bit differently. In general, it is enough to be aware of this and to call output.type.func(output.device).

Now we will read the method parameters we configured using with_method_parameters(...).

parameter_args <- read.method.parameters();

And then we read the line segment data we configured using with_data(data)

letter_segments <- read.dataframe();

This is all that is necessary to receive the configured command parameters that Python executed.

The last thing to do is to apply the values, and execute the method.

do.call(draw_lines_from_dataframe, c(list(letter_segments), parameter_args));