/* * ------------------------------------------------------------------------ * * Copyright (C) 2003 - 2013 * University of Konstanz, Germany and * KNIME GmbH, Konstanz, Germany * Website: http://www.knime.org; Email: contact@knime.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 3, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses>. * * Additional permission under GNU GPL version 3 section 7: * * KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs. * Hence, KNIME and ECLIPSE are both independent programs and are not * derived from each other. Should, however, the interpretation of the * GNU GPL Version 3 ("License") under any applicable laws result in * KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants * you the additional permission to use and propagate KNIME together with * ECLIPSE with only the license terms in place for ECLIPSE applying to * ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the * license terms of ECLIPSE themselves allow for the respective use and * propagation of ECLIPSE together with KNIME. * * Additional permission relating to nodes for KNIME that extend the Node * Extension (and in particular that are based on subclasses of NodeModel, * NodeDialog, and NodeView) and that only interoperate with KNIME through * standard APIs ("Nodes"): * Nodes are deemed to be separate and independent programs and to not be * covered works. Notwithstanding anything to the contrary in the * License, the License does not apply to Nodes, you are not required to * license Nodes under the License, and you are granted a license to * prepare and propagate Nodes, in each case even if such Nodes are * propagated with or for interoperation with KNIME. The owner of a Node * may freely choose the license terms applicable to such Node, including * when such Node is propagated with or for interoperation with KNIME. * --------------------------------------------------------------------- * * */ package org.knime.knip.base.nodes.proc.imgjep; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import org.knime.base.data.statistics.StatisticsTable; import org.knime.core.data.DataTableSpec; import org.knime.core.data.DataType; import org.knime.core.data.DoubleValue; import org.knime.core.data.IntValue; import org.knime.core.node.BufferedDataTable; import org.knime.core.node.CanceledExecutionException; import org.knime.core.node.ExecutionMonitor; import org.knime.core.node.InvalidSettingsException; import org.knime.knip.base.data.img.ImgPlusValue; import org.knime.knip.base.nodes.proc.imgjep.fun.JEPAbs; import org.knime.knip.base.nodes.proc.imgjep.fun.JEPAtan2; import org.knime.knip.base.nodes.proc.imgjep.fun.JEPAvg; import org.knime.knip.base.nodes.proc.imgjep.fun.JEPExp; import org.knime.knip.base.nodes.proc.imgjep.fun.JEPLog; import org.knime.knip.base.nodes.proc.imgjep.fun.JEPMax; import org.knime.knip.base.nodes.proc.imgjep.fun.JEPMin; import org.knime.knip.base.nodes.proc.imgjep.fun.JEPSqrt; /** * Helper class that parses the expression and generates the JEP object. * * @author <a href="mailto:dietzc85@googlemail.com">Christian Dietz</a> * @author <a href="mailto:horn_martin@gmx.de">Martin Horn</a> * @author <a href="mailto:michael.zinsmaier@googlemail.com">Michael Zinsmaier</a> * @author Bernd Wiswedel, University of Konstanz */ final class ImgExpressionParser { /** * @param col Input column index. * @return "col" + col (used for JEP) */ static String createColField(final int col) { return "col" + col; } /** * @param c jep constant name. * @param col index of column. * @return constant identifier for jep. */ static String createConstantName(final ImgJEPConstant c, final int col) { return c.getFunctionName() + "_" + createColField(col); } /** * For each of the columns: Do we need to calculate mean, min, max, etc... */ private final boolean[] m_constantFlags; /** Input expression. */ private final String m_expression; /** Final JEP object. */ private final ImgJEP m_jep; /** Housing the output expression (as passed to JEP). */ private final StringBuilder m_jepExprBuilder; /** Offset while parsing the expression. */ private int m_offset; /** Input data table spec (used for variable identification. */ private final DataTableSpec m_spec; /** * Creates new parser instance, does the parsing. * * @param expression Input expression. * @param imgJEP the image operations * @param spec Respective table spec. * @param rowCount Total row count (is a constant). * @throws InvalidSettingsException If parsing fails. */ ImgExpressionParser(final String expression, final DataTableSpec spec, final int rowCount) throws InvalidSettingsException { m_expression = expression; m_jepExprBuilder = new StringBuilder(); m_spec = spec; m_constantFlags = new boolean[spec.getNumColumns()]; m_jep = parse(rowCount); } /** * Does the calculation of the constant variables and then puts it into the JEP object. * * @param mon for progress / cancel * @param bdt the input table. * @throws CanceledExecutionException If canceled. */ void calculateConstants(final ExecutionMonitor mon, final BufferedDataTable bdt) throws CanceledExecutionException { assert (bdt.getDataTableSpec().equalStructure(m_spec)); final StatisticsTable t = new StatisticsTable(bdt, mon); for (final String s : getColumnsWithConstants()) { final int index = m_spec.findColumnIndex(s); for (final ImgJEPConstant c : ImgJEPConstant.values()) { if (c.getNrArgs() > 0) { final double val = c.readFromStatisticsTable(t, index); m_jep.getVar(createConstantName(c, index)).setValue(val); } } } } /** * Check if we find a constant function identifier from current offset until next dollar sign. * * @param end Position of next dollar sign * @return end if no constant is found, otherwise end of the constant def. * @throws InvalidSettingsException If that fails. */ private int checkForConstantFunction(final int end) throws InvalidSettingsException { int min = Integer.MAX_VALUE; ImgJEPConstant constant = null; for (final ImgJEPConstant c : ImgJEPConstant.values()) { if (c.getNrArgs() > 0) { final int location = m_expression.indexOf(c.getFunctionName() + "(", m_offset); if ((location >= 0) && (location < end) && (location < min)) { constant = c; min = location; } } } if (constant == null) { return end; } // now we found something like "...COL_MEAN" (where min points // to 'C') final int startBracket = min + constant.getFunctionName().length(); // at least 4 characters follow, e.g. $c$) (where c is the // column name) if (((startBracket + 4) > (m_expression.length() - 1)) || (m_expression.charAt(startBracket) != '(')) { throw new InvalidSettingsException("Can't parse column name " + "of constant function " + constant.toString()); } final int startDollar = startBracket + 1; if (m_expression.charAt(startDollar) != '$') { throw new InvalidSettingsException("Can't parse column name " + "of constant function " + constant.toString()); } final int endDollar = m_expression.indexOf('$', startDollar + 1); if (endDollar < 0) { throw new InvalidSettingsException("Can't parse column name" + "of constant function " + constant.toString()); } if (endDollar < startDollar) { throw new InvalidSettingsException("No closing $ for: \"" + m_expression.substring(startDollar, Math.max(m_expression.length(), startDollar + 10)) + "\""); } final int endBracket = endDollar + 1; if (m_expression.charAt(endBracket) != ')') { throw new InvalidSettingsException("Can't parse column name " + "of constant function " + constant.toString()); } final String colIdPound = m_expression.substring(startDollar, endDollar + 1); final String colId = colIdPound.substring(1, colIdPound.length() - 1); final int colIndex = m_spec.findColumnIndex(colId); if (colIndex < 0) { throw new InvalidSettingsException("No such column: " + colId); } final DataType colType = m_spec.getColumnSpec(colIndex).getType(); if (!colType.isCompatible(DoubleValue.class) && !colType.isCompatible(IntValue.class)) { throw new InvalidSettingsException("Can't use column \"" + colId + "\", not numeric!"); } m_constantFlags[colIndex] = true; final String colConstantName = createConstantName(constant, colIndex); m_jepExprBuilder.append(m_expression.substring(m_offset, min)); m_jepExprBuilder.append(colConstantName); return Math.min(endBracket + 1, m_expression.length()); } /** @return name of columns for which constants need to be calculated. */ String[] getColumnsWithConstants() { final List<String> result = new ArrayList<String>(); for (int i = 0; i < m_constantFlags.length; i++) { if (m_constantFlags[i]) { result.add(m_spec.getColumnSpec(i).getName()); } } return result.toArray(new String[result.size()]); } /** * Getter for table spec (as passed in constructor). * * @return The spec. */ DataTableSpec getDataTableSpec() { return m_spec; } /** * @return The final jep object. */ ImgJEP getJep() { return m_jep; } /** * @param rowCount */ private ImgJEP parse(final int rowCount) throws InvalidSettingsException { final LinkedHashSet<String> variableHash = new LinkedHashSet<String>(); int numImgCols = 0; while (m_offset < m_expression.length()) { final int start = m_expression.indexOf('$', m_offset); if (start < m_offset) { break; } final int newOffset = checkForConstantFunction(start); if (newOffset > start) { m_offset = newOffset; continue; // encountered constant function, // everything done } final int end = m_expression.indexOf('$', start + 1); if (end < start) { throw new InvalidSettingsException("No closing $ for: \"" + m_expression.substring(start, Math.max(m_expression.length(), start + 10)) + "\""); } final String colIdPound = m_expression.substring(start, end + 1); final String colId = colIdPound.substring(1, colIdPound.length() - 1); m_jepExprBuilder.append(m_expression.substring(m_offset, start)); String colFieldName; final int colIndex = m_spec.findColumnIndex(colId); if (colIndex < 0) { throw new InvalidSettingsException("No such column: " + colId); } colFieldName = createColField(colIndex); final DataType colType = m_spec.getColumnSpec(colIndex).getType(); if (!colType.isCompatible(ImgPlusValue.class) && !colType.isCompatible(DoubleValue.class) && !colType.isCompatible(IntValue.class)) { throw new InvalidSettingsException("Can't use column \"" + colId + "\", not compatible!"); } m_jepExprBuilder.append(colFieldName); m_offset = Math.min(end + 1, m_expression.length()); variableHash.add(colFieldName); if (colType.isCompatible(ImgPlusValue.class)) { numImgCols++; } } if (numImgCols == 0) { throw new InvalidSettingsException("No image column in the expression."); } m_jepExprBuilder.append(m_expression.substring(Math.min(m_offset, m_expression.length()))); final ImgJEP result = new ImgJEP(); result.addStandardConstants(); result.addFunction(ImgJEPFunction.max.toString(), new JEPMax()); result.addFunction(ImgJEPFunction.min.toString(), new JEPMin()); result.addFunction(ImgJEPFunction.average.toString(), new JEPAvg()); result.addFunction(ImgJEPFunction.exp.toString(), new JEPExp()); result.addFunction(ImgJEPFunction.log.toString(), new JEPLog()); result.addFunction(ImgJEPFunction.sqrt.toString(), new JEPSqrt()); result.addFunction(ImgJEPFunction.abs.toString(), new JEPAbs()); result.addFunction(ImgJEPFunction.atan2.toString(), new JEPAtan2()); // result.addFunction(ImgJEPFunction.min_in_args.toString(), new // Min()); // result.addFunction(ImgJEPFunction.average.toString(), new // Avg()); result.setImplicitMul(true); // result.addConstant(ImgJEPConstant.ROWCOUNT.toString(), // rowCount); for (final String s : variableHash) { result.addVariable(s, 0.0); } for (int i = 0; i < m_constantFlags.length; i++) { if (m_constantFlags[i]) { for (final ImgJEPConstant c : ImgJEPConstant.values()) { if (c.getNrArgs() > 0) { result.addVariable(createConstantName(c, i), 0.0); } } } } // result.addVariable(ImgJEPConstant.ROWINDEX.toString(), 0); result.parseExpression(m_jepExprBuilder.toString()); if (result.hasError()) { throw new InvalidSettingsException("Unable to parse m_expression\n" + result.getErrorInfo()); } return result; } }