001: /*
002: * This program is free software; you can redistribute it and/or modify
003: * it under the terms of the GNU General Public License as published by
004: * the Free Software Foundation; either version 2 of the License, or
005: * (at your option) any later version.
006: *
007: * This program is distributed in the hope that it will be useful,
008: * but WITHOUT ANY WARRANTY; without even the implied warranty of
009: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
010: * GNU General Public License for more details.
011: *
012: * You should have received a copy of the GNU General Public License
013: * along with this program; if not, write to the Free Software
014: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
015: */
016:
017: /*
018: * MultiFilter.java
019: * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
020: *
021: */
022:
023: package weka.filters;
024:
025: import weka.core.Capabilities;
026: import weka.core.Instance;
027: import weka.core.Instances;
028: import weka.core.Option;
029: import weka.core.OptionHandler;
030: import weka.core.Utils;
031:
032: import java.util.Enumeration;
033: import java.util.Vector;
034:
035: /**
036: <!-- globalinfo-start -->
037: * Applies several filters successively. In case all supplied filters are StreamableFilters, it will act as a streamable one, too.
038: * <p/>
039: <!-- globalinfo-end -->
040: *
041: <!-- options-start -->
042: * Valid options are: <p/>
043: *
044: * <pre> -D
045: * Turns on output of debugging information.</pre>
046: *
047: * <pre> -F <classname [options]>
048: * A filter to apply (can be specified multiple times).</pre>
049: *
050: <!-- options-end -->
051: *
052: * @author FracPete (fracpete at waikato dot ac dot nz)
053: * @version $Revision: 1.6 $
054: * @see weka.filters.StreamableFilter
055: */
056: public class MultiFilter extends SimpleStreamFilter {
057:
058: /** for serialization */
059: private static final long serialVersionUID = -6293720886005713120L;
060:
061: /** The filters */
062: protected Filter m_Filters[] = { new AllFilter() };
063:
064: /** caches the streamable state */
065: protected boolean m_Streamable = false;
066:
067: /** whether we already checked the streamable state */
068: protected boolean m_StreamableChecked = false;
069:
070: /**
071: * Returns a string describing this filter
072: * @return a description of the filter suitable for
073: * displaying in the explorer/experimenter gui
074: */
075: public String globalInfo() {
076: return "Applies several filters successively. In case all supplied filters "
077: + "are StreamableFilters, it will act as a streamable one, too.";
078: }
079:
080: /**
081: * Returns an enumeration describing the available options.
082: *
083: * @return an enumeration of all the available options.
084: */
085: public Enumeration listOptions() {
086: Vector result = new Vector();
087: Enumeration enm = super .listOptions();
088: while (enm.hasMoreElements())
089: result.add(enm.nextElement());
090:
091: result
092: .addElement(new Option(
093: "\tA filter to apply (can be specified multiple times).",
094: "F", 1, "-F <classname [options]>"));
095:
096: return result.elements();
097: }
098:
099: /**
100: * Parses a list of options for this object. <p/>
101: *
102: <!-- options-start -->
103: * Valid options are: <p/>
104: *
105: * <pre> -D
106: * Turns on output of debugging information.</pre>
107: *
108: * <pre> -F <classname [options]>
109: * A filter to apply (can be specified multiple times).</pre>
110: *
111: <!-- options-end -->
112: *
113: * @param options the list of options as an array of strings
114: * @throws Exception if an option is not supported
115: */
116: public void setOptions(String[] options) throws Exception {
117: String tmpStr;
118: String filter;
119: String[] options2;
120: Vector filters;
121:
122: super .setOptions(options);
123:
124: filters = new Vector();
125: while ((tmpStr = Utils.getOption("F", options)).length() != 0) {
126: options2 = Utils.splitOptions(tmpStr);
127: filter = options2[0];
128: options2[0] = "";
129: filters.add(Utils.forName(Filter.class, filter, options2));
130: }
131:
132: // at least one filter
133: if (filters.size() == 0)
134: filters.add(new AllFilter());
135:
136: setFilters((Filter[]) filters
137: .toArray(new Filter[filters.size()]));
138: }
139:
140: /**
141: * Gets the current settings of the filter.
142: *
143: * @return an array of strings suitable for passing to setOptions
144: */
145: public String[] getOptions() {
146: Vector result;
147: String[] options;
148: int i;
149:
150: result = new Vector();
151:
152: options = super .getOptions();
153: for (i = 0; i < options.length; i++)
154: result.add(options[i]);
155:
156: for (i = 0; i < getFilters().length; i++) {
157: result.add("-F");
158: result.add(getFilterSpec(getFilter(i)));
159: }
160:
161: return (String[]) result.toArray(new String[result.size()]);
162: }
163:
164: /**
165: * Returns the Capabilities of this filter.
166: *
167: * @return the capabilities of this object
168: * @see Capabilities
169: */
170: public Capabilities getCapabilities() {
171: if (getFilters().length == 0)
172: return super .getCapabilities();
173: else
174: return getFilters()[0].getCapabilities();
175: }
176:
177: /**
178: * resets the filter, i.e., m_NewBatch to true and m_FirstBatchDone to
179: * false.
180: *
181: * @see #m_NewBatch
182: * @see #m_FirstBatchDone
183: */
184: protected void reset() {
185: super .reset();
186: m_StreamableChecked = false;
187: }
188:
189: /**
190: * Sets the list of possible filters to choose from.
191: * Also resets the state of the filter (this reset doesn't affect the
192: * options).
193: *
194: * @param filters an array of filters with all options set.
195: * @see #reset()
196: */
197: public void setFilters(Filter[] filters) {
198: m_Filters = filters;
199: reset();
200: }
201:
202: /**
203: * Gets the list of possible filters to choose from.
204: *
205: * @return the array of Filters
206: */
207: public Filter[] getFilters() {
208: return m_Filters;
209: }
210:
211: /**
212: * Returns the tip text for this property
213: * @return tip text for this property suitable for
214: * displaying in the explorer/experimenter gui
215: */
216: public String filtersTipText() {
217: return "The base filters to be used.";
218: }
219:
220: /**
221: * Gets a single filter from the set of available filters.
222: *
223: * @param index the index of the filter wanted
224: * @return the Filter
225: */
226: public Filter getFilter(int index) {
227: return m_Filters[index];
228: }
229:
230: /**
231: * returns the filter classname and the options as one string
232: *
233: * @param filter the filter to get the specs for
234: * @return the classname plus options
235: */
236: protected String getFilterSpec(Filter filter) {
237: String result;
238:
239: if (filter == null) {
240: result = "";
241: } else {
242: result = filter.getClass().getName();
243: if (filter instanceof OptionHandler)
244: result += " "
245: + Utils.joinOptions(((OptionHandler) filter)
246: .getOptions());
247: }
248:
249: return result;
250: }
251:
252: /**
253: * tests whether all the enclosed filters are streamable
254: *
255: * @return true if all the enclosed filters are streamable
256: */
257: public boolean isStreamableFilter() {
258: int i;
259:
260: if (!m_StreamableChecked) {
261: m_Streamable = true;
262: m_StreamableChecked = true;
263:
264: for (i = 0; i < getFilters().length; i++) {
265: if (getFilter(i) instanceof MultiFilter)
266: m_Streamable = ((MultiFilter) getFilter(i))
267: .isStreamableFilter();
268: else if (getFilter(i) instanceof StreamableFilter)
269: m_Streamable = true;
270: else
271: m_Streamable = false;
272:
273: if (!m_Streamable)
274: break;
275: }
276:
277: if (getDebug())
278: System.out.println("Streamable: " + m_Streamable);
279: }
280:
281: return m_Streamable;
282: }
283:
284: /**
285: * Returns true if the output format is immediately available after the
286: * input format has been set and not only after all the data has been
287: * seen (see batchFinished()). This method should normally return true
288: * for a stream filter, since the data will be processed in a batch
289: * manner instead (or at least for the second batch of files, see
290: * m_FirstBatchDone).
291: *
292: * @return true if the output format is immediately available
293: * @see #batchFinished()
294: * @see #setInputFormat(Instances)
295: * @see #m_FirstBatchDone
296: */
297: protected boolean hasImmediateOutputFormat() {
298: return isStreamableFilter();
299: }
300:
301: /**
302: * Determines the output format based on the input format and returns
303: * this. In case the output format cannot be returned immediately, i.e.,
304: * hasImmediateOutputFormat() returns false, then this method will called
305: * from batchFinished() after the call of preprocess(Instances), in which,
306: * e.g., statistics for the actual processing step can be gathered.
307: *
308: * @param inputFormat the input format to base the output format on
309: * @return the output format
310: * @throws Exception in case the determination goes wrong
311: * @see #hasImmediateOutputFormat()
312: * @see #batchFinished()
313: * @see #preprocess(Instances)
314: */
315: protected Instances determineOutputFormat(Instances inputFormat)
316: throws Exception {
317: Instances result;
318: int i;
319:
320: result = getInputFormat();
321:
322: for (i = 0; i < getFilters().length; i++) {
323: if (!isFirstBatchDone())
324: getFilter(i).setInputFormat(result);
325: result = getFilter(i).getOutputFormat();
326: }
327:
328: return result;
329: }
330:
331: /**
332: * processes the given instance (may change the provided instance) and
333: * returns the modified version.
334: *
335: * @param instance the instance to process
336: * @return the modified data
337: * @throws Exception in case the processing goes wrong
338: */
339: protected Instance process(Instance instance) throws Exception {
340: Instance result;
341: int i;
342:
343: result = (Instance) instance.copy();
344:
345: for (i = 0; i < getFilters().length; i++) {
346: getFilter(i).input(result);
347: result = getFilter(i).output();
348: }
349:
350: return result;
351: }
352:
353: /**
354: * Processes the given data (may change the provided dataset) and returns
355: * the modified version. This method is called in batchFinished().
356: * This implementation only calls process(Instance) for each instance
357: * in the given dataset.
358: *
359: * @param instances the data to process
360: * @return the modified data
361: * @throws Exception in case the processing goes wrong
362: * @see #batchFinished()
363: * @see #process(Instance)
364: */
365: protected Instances process(Instances instances) throws Exception {
366: Instances result;
367: int i;
368:
369: result = instances;
370:
371: for (i = 0; i < getFilters().length; i++) {
372: if (!isFirstBatchDone())
373: getFilter(i).setInputFormat(result);
374: result = Filter.useFilter(result, getFilter(i));
375: }
376:
377: return result;
378: }
379:
380: /**
381: * Main method for executing this class.
382: *
383: * @param args should contain arguments for the filter: use -h for help
384: */
385: public static void main(String[] args) {
386: runFilter(new MultiFilter(), args);
387: }
388: }
|