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));