content: /**
   * Copyright 2010 DFKI GmbH.
   * All Rights Reserved.  Use is subject to license terms.
   * <p/>
   * This file is part of MARY TTS.
   * <p/>
   * MARY TTS is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation, version 3 of the License.
   * <p/>
   * 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 Lesser General Public License for more details.
   * <p/>
   * You should have received a copy of the GNU Lesser General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package marytts.modules.acoustic;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.List;
  
  import marytts.datatypes.MaryXML;
  import marytts.exceptions.MaryConfigurationException;
  import marytts.features.FeatureProcessorManager;
  import marytts.features.FeatureRegistry;
  import marytts.features.FeatureVector;
  import marytts.features.TargetFeatureComputer;
  import marytts.unitselection.select.Target;
  import marytts.unitselection.select.UnitSelector;
  import mf.org.w3c.dom.Element;
  
  /**
   * Base class for acoustic modeling; specific Models should extend this and
   * override methods as needed.
   *
   * @author steiner
   */
  public abstract class Model {
  
      /**
       * The stream from which we will read our acoustic model.
       */
      protected InputStream dataStream;
  
      /**
       * The voice with which this model is associated
       */
      protected String voiceName;
  
      /**
       * The attribute into which the predicted acoustic feature should be
       * written.
       */
      protected String targetAttributeName;
  
      protected String targetAttributeFormat;
  
      /**
       * The name of the predicted acoustic feature, if any. The feature processor
       * that will be created from this will read the value from
       * {@link #targetAttributeName}.
       */
      protected String featureName;
  
      /**
       * The feature processors used for prediction.
       */
      protected FeatureProcessorManager featureManager;
  
      /**
       * The names of the features used for prediction.
       */
      protected String predictionFeatureNames;
  
      /**
       * The producer of feature vectors for the features in
       * {@link #predictionFeatureNames} as computed by the feature processors in
       * {@link #featureManager}.
       */
      protected TargetFeatureComputer featureComputer;
  
      protected String predictFrom;
  
      protected String applyTo;
  
      /**
       * Model constructor
       *
       * @param featureManager        the feature processor manager used to compute the symbolic
       *                              features used for prediction
       * @param dataFileName          data file for this Model
       * @param targetAttributeName   attribute in MaryXML to predict
       * @param targetAttributeFormat printf-style format String to specify the attribute value,
       *                              i.e. "%.3f" to round to 3 decimal places; "%s" by default
       * @param featureName           name of the custom continuous feature predicted by this model,
       *                              or null
       * @param predictFrom           key of Element Lists from which to predict values; "segments"
       *                              by default
       * @param applyTo               key of Element Lists to which to apply values; "segments" by
       *                              default
       */
      protected Model(FeatureProcessorManager featureManager, String voiceName,
                      InputStream dataStream, String targetAttributeName,
                      String targetAttributeFormat, String featureName,
                      String predictFrom, String applyTo) {
          this.featureManager = featureManager;
          this.voiceName = voiceName;
          this.dataStream = dataStream;
          this.targetAttributeName = targetAttributeName;
          if (targetAttributeFormat == null) {
              targetAttributeFormat = "%s";
          }
          this.targetAttributeFormat = targetAttributeFormat;
          this.featureName = featureName;
          if (predictFrom == null) {
              predictFrom = "segments";
          }
          this.predictFrom = predictFrom;
          if (applyTo == null) {
              applyTo = "segments";
          }
          this.applyTo = applyTo;
      }
  
      /**
       * Try to load this model and set the target feature computer appropriately.
       * This must be called from the constructor of subclasses, so that the
       * subclass implementation of loadDataFile() is visible.
       *
       * @throws MaryConfigurationException if the model cannot be set up properly.
       */
      protected final void load() throws MaryConfigurationException {
          try {
              loadData();
          } catch (IOException ioe) {
              throw new MaryConfigurationException(
                      "Cannot load model data from stream", ioe);
          }
          setupFeatureComputer();
      }
  
      /**
       * Load dataFile for this model; only extension classes know how to do this
       *
       * @throws IOException                if any files cannot be properly read
       * @throws MaryConfigurationException if files can be read but contain problematic content
       */
      protected abstract void loadData() throws IOException,
              MaryConfigurationException;
  
      protected final void setupFeatureComputer()
              throws MaryConfigurationException {
          try {
              featureComputer = FeatureRegistry.getTargetFeatureComputer(
                      featureManager, predictionFeatureNames);
          } catch (IllegalArgumentException iae) {
              throw new MaryConfigurationException(
                      "Incompatible features between model and feature processor manager.\n"
                              + "The model needs the following features:\n"
                              + predictionFeatureNames + "\n"
                              + "The FeatureProcessorManager for locale "
                              + featureManager.getLocale() + " ("
                              + featureManager.getClass().toString()
                              + ") can produce the following features:\n"
                              + featureManager.listFeatureProcessorNames(), iae);
          }
      }
  
      /**
       * Apply this Model to a List of Elements, predicting from those same
       * Elements
       *
       * @param elements Elements for which to predict the values
       * @throws MaryConfigurationException if attribute values cannot be predicted because of an invalid
       *                                    voice configuration
       */
      public void applyTo(List<Element> elements)
              throws MaryConfigurationException {
          applyFromTo(elements, elements);
      }
  
      /**
       * Apply this Model to a List of Elements, predicting from a different List
       * of Elements
       *
       * @param predictFromElements Elements from which to predict the values
       * @param applyToElements     Elements to which to apply the values predicted by this Model
       * @throws MaryConfigurationException if attribute values cannot be predicted because of an invalid
       *                                    voice configuration
       */
      public void applyFromTo(List<Element> predictFromElements,
                              List<Element> applyToElements) throws MaryConfigurationException {
          assert predictFromElements != null;
          assert applyToElements != null;
          assert predictFromElements.size() == applyToElements.size();
  
          List<Target> predictFromTargets = getTargets(predictFromElements);
  
          for (int i = 0; i < applyToElements.size(); i++) {
              Target target = predictFromTargets.get(i);
  
              float targetValue;
              try {
                  targetValue = evaluate(target);
              } catch (Exception e) {
                  throw new MaryConfigurationException(
                          "Could not predict value for target: '" + target + "'",
                          e);
              }
  
              Element element = applyToElements.get(i);
  
              // "evaluate" pseudo XPath syntax:
              // TODO this needs to be extended to take into account
              // targetAttributeNames like "foo/@bar", which would add the
              // bar attribute to the foo child of this element, creating the
              // child if not already present...
              if (targetAttributeName.startsWith("@")) {
                  targetAttributeName = targetAttributeName.replaceFirst("@", "");
              }
  
              String formattedTargetValue = null;
              try {
                  formattedTargetValue = String.format(targetAttributeFormat,
                          targetValue);
              } catch (Exception e) {
                  throw new MaryConfigurationException(
                          "Could not format target value '" + targetValue
                                  + "' using format '" + targetAttributeFormat
                                  + "'", e);
              }
  
              // System.out.println("formattedTargetValue = " +
              // formattedTargetValue);
  
              // if the attribute already exists for this element, append
              // targetValue:
              if (element.hasAttribute(targetAttributeName)) {
                  formattedTargetValue = element
                          .getAttribute(targetAttributeName)
                          + " "
                          + formattedTargetValue;
              }
  
              // set the new attribute value:
              element.setAttribute(targetAttributeName, formattedTargetValue);
          }
      }
  
      /**
       * For a list of <code>PHONE</code> elements, return a list of Targets,
       * where each Target is constructed from the corresponding Element.
       *
       * @param elements List of Elements
       * @return List of Targets
       */
      protected List<Target> getTargets(List<Element> elements) {
          List<Target> targets = new ArrayList<Target>(elements.size());
          for (Element element : elements) {
              assert element.getTagName() == MaryXML.PHONE;
              String phone = UnitSelector.getPhoneSymbol(element);
              Target target = new Target(phone, element);
              targets.add(target);
              // compute FeatureVectors for Targets:
              FeatureVector targetFeatureVector = featureComputer
                      .computeFeatureVector(target);
              target.setFeatureVector(targetFeatureVector); // this is critical!
              element.setUserData("target", target, Target.targetFeatureCloner);
          }
          return targets;
      }
  
      /**
       * Evaluate model on a Target to obtain the target value as a float.
       *
       * @param target
       * @return target value
       * @throws Exception if the target value cannot be predicted
       */
      protected abstract float evaluate(Target target) throws Exception;
  
      // several getters:
  
      /**
       * @return the name of the voice that this model is associated with
       */
      public String getVoiceName() {
          return voiceName;
      }
  
      /**
       * @return the featureName
       */
      public String getFeatureName() {
          return featureName;
      }
  
      /**
       * @return the targetAttributeName
       */
      public String getTargetAttributeName() {
          return targetAttributeName;
      }
  
      /**
       * @return the key of Element Lists from which to predict with this Model
       */
      public String getPredictFrom() {
          return predictFrom;
      }
  
      /**
       * @return the key of Element Lists to which to apply this Model
       */
      public String getApplyTo() {
          return applyTo;
      }
  }
  