/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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/>. */ /* * MultiFilter.java * Copyright (C) 2005-2012 University of Waikato, Hamilton, New Zealand * */ package weka.filters; import java.util.Enumeration; import java.util.Vector; import weka.core.Capabilities; import weka.core.Instance; import weka.core.Instances; import weka.core.Option; import weka.core.OptionHandler; import weka.core.RevisionUtils; import weka.core.Utils; /** <!-- globalinfo-start --> * Applies several filters successively. In case all supplied filters are StreamableFilters, it will act as a streamable one, too. * <p/> <!-- globalinfo-end --> * <!-- options-start --> * Valid options are: <p/> * * <pre> -D * Turns on output of debugging information.</pre> * * <pre> -F <classname [options]> * A filter to apply (can be specified multiple times).</pre> * <!-- options-end --> * * @author FracPete (fracpete at waikato dot ac dot nz) * @version $Revision: 9717 $ * @see weka.filters.StreamableFilter */ public class MultiFilter extends SimpleStreamFilter { /** for serialization */ private static final long serialVersionUID = -6293720886005713120L; /** The filters */ protected Filter m_Filters[] = {new AllFilter()}; /** caches the streamable state */ protected boolean m_Streamable = false; /** whether we already checked the streamable state */ protected boolean m_StreamableChecked = false; /** * Returns a string describing this filter * @return a description of the filter suitable for * displaying in the explorer/experimenter gui */ public String globalInfo() { return "Applies several filters successively. In case all supplied filters " + "are StreamableFilters, it will act as a streamable one, too."; } /** * Returns an enumeration describing the available options. * * @return an enumeration of all the available options. */ public Enumeration listOptions() { Vector result = new Vector(); Enumeration enm = super.listOptions(); while (enm.hasMoreElements()) result.add(enm.nextElement()); result.addElement(new Option( "\tA filter to apply (can be specified multiple times).", "F", 1, "-F <classname [options]>")); return result.elements(); } /** * Parses a list of options for this object. <p/> * <!-- options-start --> * Valid options are: <p/> * * <pre> -D * Turns on output of debugging information.</pre> * * <pre> -F <classname [options]> * A filter to apply (can be specified multiple times).</pre> * <!-- options-end --> * * @param options the list of options as an array of strings * @throws Exception if an option is not supported */ public void setOptions(String[] options) throws Exception { String tmpStr; String filter; String[] options2; Vector filters; super.setOptions(options); filters = new Vector(); while ((tmpStr = Utils.getOption("F", options)).length() != 0) { options2 = Utils.splitOptions(tmpStr); filter = options2[0]; options2[0] = ""; filters.add(Utils.forName(Filter.class, filter, options2)); } // at least one filter if (filters.size() == 0) filters.add(new AllFilter()); setFilters((Filter[]) filters.toArray(new Filter[filters.size()])); } /** * Gets the current settings of the filter. * * @return an array of strings suitable for passing to setOptions */ public String[] getOptions() { Vector result; String[] options; int i; result = new Vector(); options = super.getOptions(); for (i = 0; i < options.length; i++) result.add(options[i]); for (i = 0; i < getFilters().length; i++) { result.add("-F"); result.add(getFilterSpec(getFilter(i))); } return (String[]) result.toArray(new String[result.size()]); } /** * Returns the Capabilities of this filter. * * @return the capabilities of this object * @see Capabilities */ public Capabilities getCapabilities() { if (getFilters().length == 0) { Capabilities result = super.getCapabilities(); result.disableAll(); return result; } else { return getFilters()[0].getCapabilities(); } } /** * resets the filter, i.e., m_NewBatch to true and m_FirstBatchDone to * false. * * @see #m_NewBatch * @see #m_FirstBatchDone */ protected void reset() { super.reset(); m_StreamableChecked = false; } /** * Sets the list of possible filters to choose from. * Also resets the state of the filter (this reset doesn't affect the * options). * * @param filters an array of filters with all options set. * @see #reset() */ public void setFilters(Filter[] filters) { m_Filters = filters; reset(); } /** * Gets the list of possible filters to choose from. * * @return the array of Filters */ public Filter[] getFilters() { return m_Filters; } /** * Returns the tip text for this property * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String filtersTipText() { return "The base filters to be used."; } /** * Gets a single filter from the set of available filters. * * @param index the index of the filter wanted * @return the Filter */ public Filter getFilter(int index) { return m_Filters[index]; } /** * returns the filter classname and the options as one string * * @param filter the filter to get the specs for * @return the classname plus options */ protected String getFilterSpec(Filter filter) { String result; if (filter == null) { result = ""; } else { result = filter.getClass().getName(); if (filter instanceof OptionHandler) result += " " + Utils.joinOptions(((OptionHandler) filter).getOptions()); } return result; } /** * tests whether all the enclosed filters are streamable * * @return true if all the enclosed filters are streamable */ public boolean isStreamableFilter() { int i; if (!m_StreamableChecked) { m_Streamable = true; m_StreamableChecked = true; for (i = 0; i < getFilters().length; i++) { if (getFilter(i) instanceof MultiFilter) m_Streamable = ((MultiFilter) getFilter(i)).isStreamableFilter(); else if (getFilter(i) instanceof StreamableFilter) m_Streamable = true; else m_Streamable = false; if (!m_Streamable) break; } if (getDebug()) System.out.println("Streamable: " + m_Streamable); } return m_Streamable; } /** * Returns true if the output format is immediately available after the * input format has been set and not only after all the data has been * seen (see batchFinished()). This method should normally return true * for a stream filter, since the data will be processed in a batch * manner instead (or at least for the second batch of files, see * m_FirstBatchDone). * * @return true if the output format is immediately available * @see #batchFinished() * @see #setInputFormat(Instances) * @see #m_FirstBatchDone */ protected boolean hasImmediateOutputFormat() { return isStreamableFilter(); } /** * Determines the output format based on the input format and returns * this. In case the output format cannot be returned immediately, i.e., * hasImmediateOutputFormat() returns false, then this method will called * from batchFinished() after the call of preprocess(Instances), in which, * e.g., statistics for the actual processing step can be gathered. * * @param inputFormat the input format to base the output format on * @return the output format * @throws Exception in case the determination goes wrong * @see #hasImmediateOutputFormat() * @see #batchFinished() * @see #preprocess(Instances) */ protected Instances determineOutputFormat(Instances inputFormat) throws Exception { Instances result; int i; result = getInputFormat(); for (i = 0; i < getFilters().length; i++) { if (!isFirstBatchDone()) getFilter(i).setInputFormat(result); result = getFilter(i).getOutputFormat(); } return result; } /** * processes the given instance (may change the provided instance) and * returns the modified version. * * @param instance the instance to process * @return the modified data * @throws Exception in case the processing goes wrong */ protected Instance process(Instance instance) throws Exception { Instance result; int i; result = (Instance) instance.copy(); for (i = 0; i < getFilters().length; i++) { if (getFilter(i).input(result)) { result = getFilter(i).output(); } else { // if a filter says nothing to collect then terminate result = null; break; } } return result; } /** * Processes the given data (may change the provided dataset) and returns * the modified version. This method is called in batchFinished(). * This implementation only calls process(Instance) for each instance * in the given dataset. * * @param instances the data to process * @return the modified data * @throws Exception in case the processing goes wrong * @see #batchFinished() * @see #process(Instance) */ public Instances process(Instances instances) throws Exception { Instances result; int i; result = instances; for (i = 0; i < getFilters().length; i++) { if (!isFirstBatchDone()) getFilter(i).setInputFormat(result); result = Filter.useFilter(result, getFilter(i)); } return result; } /** * Signify that this batch of input to the filter is finished. If * the filter requires all instances prior to filtering, output() * may now be called to retrieve the filtered instances. Any * subsequent instances filtered should be filtered based on setting * obtained from the first batch (unless the setInputFormat has been * re-assigned or new options have been set). * * @return true if there are instances pending output * @throws IllegalStateException if no input format has been set. */ public boolean batchFinished() throws Exception { super.batchFinished(); for (int i = 0; i > getFilters().length; i++) { getFilter(i).batchFinished(); } return (numPendingOutput() != 0); } /** * RemoveWithValues may return false from input() (thus not * making an instance available immediately) even after * the first batch has been completed due to matching a value * that the user wants to remove. Therefore this method returns * true. * * @return true if one of the base filters returns true for * this method. */ public boolean mayRemoveInstanceAfterFirstBatchDone() { boolean result = false; for (Filter f : m_Filters) { result = (result || f.mayRemoveInstanceAfterFirstBatchDone()); } return result; } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 9717 $"); } /** * Main method for executing this class. * * @param args should contain arguments for the filter: use -h for help */ public static void main(String[] args) { runFilter(new MultiFilter(), args); } }