package de.mpicbg.knime.scripting.python.plots; import de.mpicbg.knime.scripting.core.FlowVarUtils; import de.mpicbg.knime.scripting.core.exceptions.KnimeScriptingException; import de.mpicbg.knime.scripting.python.AbstractPythonScriptingNodeModel; import de.mpicbg.knime.scripting.python.PythonScriptingBundleActivator; import de.mpicbg.knime.scripting.python.PythonTableConverter; import de.mpicbg.knime.scripting.python.prefs.PythonPreferenceInitializer; import de.mpicbg.knime.scripting.python.srv.CommandOutput; import de.mpicbg.knime.scripting.python.srv.LocalPythonClient; import de.mpicbg.knime.scripting.python.srv.PythonClient; import de.mpicbg.knime.scripting.python.srv.PythonTempFile; import org.eclipse.jface.preference.IPreferenceStore; import org.knime.core.data.image.png.PNGImageContent; import org.knime.core.node.BufferedDataTable; import org.knime.core.node.ExecutionContext; import org.knime.core.node.InvalidSettingsException; import org.knime.core.node.defaultnodesettings.SettingsModelBoolean; import org.knime.core.node.defaultnodesettings.SettingsModelInteger; import org.knime.core.node.defaultnodesettings.SettingsModelString; import org.knime.core.node.port.PortObject; import org.knime.core.node.port.PortObjectSpec; import org.knime.core.node.port.PortType; import org.knime.core.node.port.image.ImagePortObject; import org.knime.core.node.port.image.ImagePortObjectSpec; import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.text.SimpleDateFormat; import java.util.Date; /** * Document me! * * @author Tom Haux */ public class PythonPlotNodeModel extends AbstractPythonScriptingNodeModel { private Image image; private SettingsModelInteger propWidth = PythonPlotNodeFactory.createPropFigureWidth(); private SettingsModelInteger propHeight = PythonPlotNodeFactory.createPropFigureHeight(); private SettingsModelString propOutputFile = PythonPlotNodeFactory.createPropOutputFile(); private SettingsModelBoolean propOverwriteFile = PythonPlotNodeFactory.createOverwriteFile(); private static final String FIGURE_WIDTH_SETTING_NAME = "figure.width"; private static final String FIGURE_HEIGHT_SETTING_NAME = "figure.height"; private static final String OUTPUT_FILE_SETTING_NAME = "figure.output.file"; private static final String OVERWRITE_SETTING_NAME = "overwrite.ok"; private static String TODAY = new SimpleDateFormat("yyMMdd").format(new Date(System.currentTimeMillis())); private final String DEFAULT_PYTHON_PLOTCMD = "plot(kIn)"; protected static final ImagePortObjectSpec IM_PORT_SPEC = new ImagePortObjectSpec(PNGImageContent.TYPE); /** * {@inheritDoc} */ @Override protected PortObjectSpec[] configure(final PortObjectSpec[] inSpecs) throws InvalidSettingsException { super.configure(inSpecs); return new PortObjectSpec[]{IM_PORT_SPEC}; } public PythonPlotNodeModel() { this(createPorts(1), new PortType[]{ImagePortObject.TYPE}); } public PythonPlotNodeModel(PortType[] inPorts) { this(inPorts, new PortType[]{ImagePortObject.TYPE}); } public PythonPlotNodeModel(PortType[] inPorts, PortType[] outports) { super(inPorts, outports); addModelSetting(FIGURE_WIDTH_SETTING_NAME, propWidth); addModelSetting(FIGURE_HEIGHT_SETTING_NAME, propHeight); addModelSetting(OUTPUT_FILE_SETTING_NAME, propOutputFile); addModelSetting(OVERWRITE_SETTING_NAME, propOverwriteFile); } protected void prepareScript(Writer writer) throws IOException { writer.write("import matplotlib\nmatplotlib.use('Agg')\nfrom pylab import *\n"); super.prepareScript(writer, true); float dpi = 100; float width = getDefWidth() / dpi; float height = getDefHeight() / dpi; writer.write("F = gcf()\n"); writer.write("F.set_dpi(" + dpi + ")\n"); writer.write("F.set_size_inches(" + width + "," + height + ")"); } private String prepareOutputFileName() { String fileName = propOutputFile.getStringValue(); // process flow-variables fileName = FlowVarUtils.replaceFlowVars(fileName, this); // replace wildcards // 1) date fileName = fileName.replace("$$DATE$$", TODAY); // 2) user fileName = fileName.replace("$$USER$$", System.getProperty("user.name")); // 3) workspace dir if (fileName.contains("$$WS$$")) { String wsLocation = getFlowVariable("knime.workspace"); fileName = fileName.replace("$$WS$$", wsLocation); } return fileName; } public int getDefHeight() { return propHeight.getIntValue(); } public int getDefWidth() { return propWidth.getIntValue(); } @Override public String getDefaultScript(String defaultScript) { return super.getDefaultScript(DEFAULT_PYTHON_PLOTCMD); } public Image getImage() { return image; } /** * {@inheritDoc} */ @Override protected PortObject[] executeImpl(PortObject[] inData, ExecutionContext exec) throws Exception { // Determine what kind of connection to instantiate (local or remote) IPreferenceStore preferences = PythonScriptingBundleActivator.getDefault().getPreferenceStore(); boolean local = preferences.getBoolean(PythonPreferenceInitializer.PYTHON_LOCAL); String host = preferences.getString(PythonPreferenceInitializer.PYTHON_HOST); int port = preferences.getInt(PythonPreferenceInitializer.PYTHON_PORT); // If the host is empty use a local client, otherwise use the server values python = local ? new LocalPythonClient() : new PythonClient(host, port); // Create the temp files that are needed throughout the node createTempFiles(); // Don't need the output table pyOutFile.delete(); pyOutFile = null; // Prepare the script Writer writer = new BufferedWriter(new FileWriter(scriptFile.getClientFile())); prepareScript(writer); // Add plot-specific commands PythonTempFile imageFile = new PythonTempFile(python, "pyplot", ".png"); writer.write("\nsavefig(r'" + imageFile.getServerPath() + "')\n"); writer.close(); // Copy the script file to the server scriptFile.upload(); // Write the input table and upload it PythonTableConverter.convertTableToCSV(exec, (BufferedDataTable) inData[0], kInFile.getClientFile(), logger); kInFile.upload(); // Run the script String pythonExecPath = local ? preferences.getString(PythonPreferenceInitializer.PYTHON_EXECUTABLE) : "python"; CommandOutput output = python.executeCommand(new String[]{pythonExecPath, scriptFile.getServerPath()}); // Log any output for (String o : output.getStandardOutput()) { logger.info(o); } for (String o : output.getErrorOutput()) { logger.error(o); } // Copy back the remote image imageFile.fetch(); // If the file wasn't found throw an exception if (!imageFile.getClientFile().exists() || imageFile.getClientFile().length() == 0) throw new RuntimeException("No output image found"); // Prepare it for the node view image = PythonPlotCanvas.toBufferedImage(new ImageIcon(imageFile.getClientPath()).getImage()); // Write the image to a file if desired String fileName = prepareOutputFileName(); if (!fileName.isEmpty()) { if (!propOverwriteFile.getBooleanValue() && new File(fileName).exists()) { throw new RuntimeException("Image file '" + fileName + "' already exists, enable overwrite to replace it"); } try { ImageIO.write((BufferedImage) image, "png", new File(fileName)); } catch (Throwable t) { throw new RuntimeException("Error writing image file '" + fileName); } } // Clean up temp files deleteTempFiles(); imageFile.delete(); // Create the image port object PNGImageContent content; File m_imageFile = File.createTempFile("pythonImage", ".png"); ImageIO.write(PythonPlotCanvas.toBufferedImage(image), "png", m_imageFile); FileInputStream in = new FileInputStream(m_imageFile); content = new PNGImageContent(in); in.close(); PortObject[] outPorts = new PortObject[1]; outPorts[0] = new ImagePortObject(content, IM_PORT_SPEC); return outPorts; } /** * {@inheritDoc} */ @Override protected void openIn(PortObject[] inData, ExecutionContext exec) throws KnimeScriptingException { openInPython(inData, exec, logger); setWarningMessage("To push the node's input to R again, you need to reset and re-execute it."); } }