All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gate.mimir.measurements.MeasurementAnnotationHelper Maven / Gradle / Ivy

Go to download

SemanticAnnotationHelper designed to allow flexible queries over Measurement annotations.

There is a newer version: 6.2
Show newest version
/*
 *  MeasurementAnnotationHelper.java
 *
 *  Copyright (c) 2007-2011, The University of Sheffield.
 *
 *  This file is part of GATE Mímir (see http://gate.ac.uk/family/mimir.html), 
 *  and is free software, licenced under the GNU Lesser General Public License,
 *  Version 3, June 2007 (also included with this distribution as file
 *  LICENCE-LGPL3.html).
 *
 *  Valentin Tablan, 05 Aug 2009
 *  
 *  $Id: MeasurementAnnotationHelper.java 17261 2014-01-30 14:05:14Z valyt $
 */
package gate.mimir.measurements;

import gate.Gate;
import gate.creole.Parameter;
import gate.creole.ParameterException;
import gate.creole.ParameterList;
import gate.creole.ResourceReference;
import gate.creole.measurements.Measurement;
import gate.creole.measurements.MeasurementsParser;
import gate.mimir.AbstractSemanticAnnotationHelper;
import gate.mimir.Constraint;
import gate.mimir.ConstraintType;
import gate.mimir.SemanticAnnotationHelper;
import gate.mimir.index.AtomicAnnotationIndex;
import gate.mimir.index.Mention;
import gate.mimir.search.QueryEngine;
import gate.mimir.util.DelegatingSemanticAnnotationHelper;
import gate.util.GateRuntimeException;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * {@link SemanticAnnotationHelper} that supports querying of Measurement
 * annotations produced by the GATE Tagger_Measurements plugin.
 * The annotations are indexed by their normalized value and unit but can
 * be queried using a "virtual" feature named spec, expressing
 * a measurement specification in terms such as "1 to 3 feet".  The spec
 * is mapped into a range query over the appropriate normalized values, so
 * can match annotations that express the same measurement in different
 * terms (e.g. "400mm").
 */
public class MeasurementAnnotationHelper extends
                                        DelegatingSemanticAnnotationHelper {

  private static final long serialVersionUID = 1875846689536452288L;

  protected static final String ANNOTATION_TYPE = "Measurement";

  protected static final String TYPE_FEATURE = "type";

  protected static final String TYPE_SCALAR = "scalar";

  protected static final String TYPE_INTERVAL = "interval";

  protected static final String DIMENSION_FEATURE = "dimension";

  protected static final String NORM_UNIT_FEATURE = "normalisedUnit";

  protected static final String NORM_VALUE_FEATURE = "normalisedValue";

  protected static final String NORM_MIN_VALUE_FEATURE = "normalisedMinValue";

  protected static final String NORM_MAX_VALUE_FEATURE = "normalisedMaxValue";

  protected static final String SPEC_FAKE_FEATURE = "spec";
  
  protected static final String OLD_DEFAULT_UNITS_FILE = "resources/units.dat";

  protected String unitsFileLocation = null;

  protected static final String OLD_DEFAULT_COMMON_WORDS = "resources/common_words.txt";
  
  protected String commonWordsLocation = null;
  
  protected String locale = null;
  
  protected String encoding = null;
  
  /**
   * The measurements parser used to parse the virtual "spec" feature.
   */
  protected transient MeasurementsParser measurementsParser;
  
  protected transient Class delegateHelperType;

  public MeasurementAnnotationHelper() {
    super();
    setAnnotationType(ANNOTATION_TYPE);
    // our nominal features include "spec", those passed to the delegate
    // at init() time will not.
    super.setNominalFeatures(new String[]{SPEC_FAKE_FEATURE, TYPE_FEATURE,
      DIMENSION_FEATURE, NORM_UNIT_FEATURE});
    super.setFloatFeatures(new String[]{NORM_VALUE_FEATURE,
      NORM_MIN_VALUE_FEATURE, NORM_MAX_VALUE_FEATURE});
  }
  
  public String getUnitsFile() {
    return unitsFileLocation;
  }

  public void setUnitsFile(String unitsFile) {
    this.unitsFileLocation = unitsFile;
  }

  public String getCommonWords() {
    return commonWordsLocation;
  }

  public void setCommonWords(String commonWords) {
    this.commonWordsLocation = commonWords;
  }

  public String getLocale() {
    return locale;
  }

  public void setLocale(String locale) {
    this.locale = locale;
  }

  public String getEncoding() {
    return encoding;
  }

  public void setEncoding(String encoding) {
    this.encoding = encoding;
  }

  public Class getDelegateHelperType() {
    return delegateHelperType;
  }

  public void setDelegateHelperType(Class delegateHelperType) {
    this.delegateHelperType = delegateHelperType;
  }

  // override feature setters as Measurement helper does not support additional
  // features

  @Override
  public void setNominalFeatures(String[] features) {
    throw new UnsupportedOperationException("MeasurementAnnotationHelper uses "
        + "a fixed feature set.");
  }

  @Override
  public void setIntegerFeatures(String[] features) {
    throw new UnsupportedOperationException("MeasurementAnnotationHelper uses "
        + "a fixed feature set.");
  }

  @Override
  public void setFloatFeatures(String[] features) {
    throw new UnsupportedOperationException("MeasurementAnnotationHelper uses "
        + "a fixed feature set.");
  }

  @Override
  public void setTextFeatures(String[] features) {
    throw new UnsupportedOperationException("MeasurementAnnotationHelper uses "
        + "a fixed feature set.");
  }

  @Override
  public void setUriFeatures(String[] features) {
    throw new UnsupportedOperationException("MeasurementAnnotationHelper uses "
        + "a fixed feature set.");
  }

  @Override
  public void init(AtomicAnnotationIndex indexer) {
    // create the delegate - needs to happen before super.init
    if(getDelegate() == null) {
      if(delegateHelperType == null) { throw new IllegalArgumentException(
          "No value provided for delegateHelperType"); }
        try {
          AbstractSemanticAnnotationHelper theDelegate =
            delegateHelperType.newInstance();
          theDelegate.setAnnotationType(getAnnotationType());
          // nominal features for delegate do not include spec
          theDelegate.setNominalFeatures(new String[]{TYPE_FEATURE,
            DIMENSION_FEATURE, NORM_UNIT_FEATURE});
          theDelegate.setFloatFeatures(new String[]{NORM_VALUE_FEATURE,
            NORM_MIN_VALUE_FEATURE, NORM_MAX_VALUE_FEATURE});
          
          setDelegate(theDelegate);
        } catch(Exception e) {
          throw new IllegalArgumentException("The delegate helper class " +
            delegateHelperType.getName() + " could not be instantiated", e);
        }
    }
    super.init(indexer);
    
    String theEncoding = encoding;
    if(theEncoding == null) {
      theEncoding = (String)defaultParamValue("encoding");
    }
    String theLocale = locale;
    if(theLocale == null) {
      theLocale = (String)defaultParamValue("locale");
    }
    try {
      ResourceReference commonUrl = null;
      if(commonWordsLocation == null || OLD_DEFAULT_COMMON_WORDS.equals(commonWordsLocation)) {
        commonUrl = (ResourceReference)defaultParamValue("commonURL");
      } else {
        commonUrl = new ResourceReference(URI.create(commonWordsLocation));
      }
      ResourceReference unitsUrl = null;
      if(unitsFileLocation == null || OLD_DEFAULT_UNITS_FILE.equals(unitsFileLocation)) {
        unitsUrl = (ResourceReference)defaultParamValue("unitsURL");
      } else {
        unitsUrl = new ResourceReference(URI.create(unitsFileLocation));
      }

      measurementsParser = new MeasurementsParser(theEncoding, theLocale,
              unitsUrl.toURL(), commonUrl.toURL());
    } catch(IOException | URISyntaxException e) {
      throw new GateRuntimeException(
              "Could not create measurements parser for MeasurementAnnotationHelper", e);
    }
  }
  
  protected Object defaultParamValue(String paramName) {
    try {
      ParameterList pl = Gate.getCreoleRegister().get("gate.creole.measurements.MeasurementsTagger").getParameterList();
      for(List disj : pl.getInitimeParameters()) {
        for(Parameter p : disj) {
          if(p.getName().equals(paramName)) {
            return p.getDefaultValue();
          }
        }
      }
    } catch(ParameterException e) {
      // ignore
    }

    throw new GateRuntimeException("Couldn't find parameter " + paramName + " in MeasurementsTagger PR");
  }
  
  protected List[] convertSpecConstraint(Constraint specConstraint){
    if(!(specConstraint.getValue() instanceof String)) { throw new IllegalArgumentException(
            "The custom feature " + SPEC_FAKE_FEATURE
                    + " only accepts String values!"); }
    if(specConstraint.getPredicate() != ConstraintType.EQ) { throw new IllegalArgumentException(
            "The custom feature " + SPEC_FAKE_FEATURE
                    + " only accepts 'equals' constraints!"); }
    String specString = (String)specConstraint.getValue();
    // a spec string is one of:
    // " ", or
    // " to  "
    String[] elements = specString.split("\\s+");
    if(elements.length < 2){
      throw new IllegalArgumentException(
              "Invalid measurement specification; the valid syntax is:\n" +
              "number unit, or" +
              "number to number unit");
    }
    if(elements[1].equalsIgnoreCase("to")) {
      //interval
      if(elements.length < 4){
        throw new IllegalArgumentException(
                "Invalid measurement specification; the valid syntax is:\n" +
                "number unit, or\n" +
                "number to number unit");
      }
      double minValue;
      try{
        minValue = Double.parseDouble(elements[0]);
      }catch(NumberFormatException e) {
        throw new IllegalArgumentException(elements[0] + 
                " is not a valid number", e);
      }
      double maxValue;
      try{
        maxValue = Double.parseDouble(elements[2]);
      }catch(NumberFormatException e) {
        throw new IllegalArgumentException(elements[2] + 
                " is not a valid number", e);
      }
      StringBuilder unitBuilder = new StringBuilder(elements[3]);
      for(int i = 4; i < elements.length; i++){
        unitBuilder.append(' ');
        unitBuilder.append(elements[i]);
      }
      String unit = unitBuilder.toString();
      //parse the two values
      Measurement minV = measurementsParser.parse(minValue, unit);
      if(minV == null){
        throw new IllegalArgumentException("Don't understand measurement "+
                minValue + " " + unit + ". Please rephrase."); 
      }
      Measurement maxV = measurementsParser.parse(maxValue, unit);
      if(maxV == null){
        throw new IllegalArgumentException("Don't understand measurement "+
                minValue + " " + unit + ". Please rephrase."); 
      }
      //finally, rephrase the query in terms that the standard helper 
      //understands.

      //we need to match simple measurements that fall inside the interval
      List alternative1 = new ArrayList();
//      alternative1.add(new Constraint(ConstraintType.EQ, TYPE_FEATURE, 
//              TYPE_SCALAR));
      alternative1.add(new Constraint(ConstraintType.EQ, NORM_UNIT_FEATURE, 
              maxV.getNormalizedUnit()));
      alternative1.add(new Constraint(ConstraintType.GE, NORM_VALUE_FEATURE, 
              minV.getNormalizedValue()));
      alternative1.add(new Constraint(ConstraintType.LE, NORM_VALUE_FEATURE, 
              maxV.getNormalizedValue()));
      //we need to match interval measurements whose start falls inside the interval
      List alternative2 = new ArrayList();
//      alternative2.add(new Constraint(ConstraintType.EQ, TYPE_FEATURE, 
//              TYPE_INTERVAL));
      alternative2.add(new Constraint(ConstraintType.EQ, NORM_UNIT_FEATURE, 
              maxV.getNormalizedUnit()));
      alternative2.add(new Constraint(ConstraintType.GE, NORM_MIN_VALUE_FEATURE, 
              minV.getNormalizedValue()));
      alternative2.add(new Constraint(ConstraintType.LE, NORM_MIN_VALUE_FEATURE, 
              maxV.getNormalizedValue()));
      //we need to match interval measurements whose end falls inside the interval
      List alternative3 = new ArrayList();
//      alternative3.add(new Constraint(ConstraintType.EQ, TYPE_FEATURE, 
//              TYPE_INTERVAL));
      alternative3.add(new Constraint(ConstraintType.EQ, NORM_UNIT_FEATURE, 
              maxV.getNormalizedUnit()));
      alternative3.add(new Constraint(ConstraintType.GE, NORM_MAX_VALUE_FEATURE, 
              minV.getNormalizedValue()));
      alternative3.add(new Constraint(ConstraintType.LE, NORM_MAX_VALUE_FEATURE, 
              maxV.getNormalizedValue()));
      return new List[]{alternative1, alternative2, alternative3};
    }else{
      //simple measurement
      double value;
      try{
        value = Double.parseDouble(elements[0]);
      }catch(NumberFormatException e) {
        throw new IllegalArgumentException(elements[0] + 
                " is not a valid number", e);
      }
      StringBuilder unitBuilder = new StringBuilder(elements[1]);
      for(int i = 2; i < elements.length; i++){
        unitBuilder.append(' ');
        unitBuilder.append(elements[i]);
      }
      String unit = unitBuilder.toString();
      //parse the single value
      Measurement valV = measurementsParser.parse(value, unit);
      if(valV == null){
        throw new IllegalArgumentException("Don't understand measurement "+
                value + " " + unit + ". Please rephrase."); 
      }
      
      //we need to match simple measurements that are equal to the value (with 
      //the double precision taken into account)      
      List alternative1 = new ArrayList();
//      alternative1.add(new Constraint(ConstraintType.EQ, TYPE_FEATURE, 
//              TYPE_SCALAR));
      alternative1.add(new Constraint(ConstraintType.EQ, NORM_UNIT_FEATURE, 
              valV.getNormalizedUnit()));
      alternative1.add(new Constraint(ConstraintType.GE, NORM_VALUE_FEATURE, 
              valV.getNormalizedValue() - Double.MIN_VALUE));
      alternative1.add(new Constraint(ConstraintType.LE, NORM_VALUE_FEATURE, 
              valV.getNormalizedValue() + Double.MIN_VALUE));
      //we need to match interval measurements which contain the value
      List alternative2 = new ArrayList();
//      alternative2.add(new Constraint(ConstraintType.EQ, TYPE_FEATURE, 
//              TYPE_INTERVAL));
      alternative2.add(new Constraint(ConstraintType.EQ, NORM_UNIT_FEATURE, 
              valV.getNormalizedUnit()));
      alternative2.add(new Constraint(ConstraintType.LE, NORM_MIN_VALUE_FEATURE, 
              valV.getNormalizedValue()));
      alternative2.add(new Constraint(ConstraintType.GE, NORM_MAX_VALUE_FEATURE, 
              valV.getNormalizedValue()));
      return new List[]{alternative1, alternative2};
    }
  }

  /**
   * Query for Measurement annotations, handling the "spec" feature by
   * converting it to the equivalent constraints on the indexed features.
   */
  @Override
  public List getMentions(String annotationType,
          List constraints, QueryEngine engine) {
    List passThroughConstraints = new ArrayList(
            constraints.size());
    Constraint specConstraint = null;
    //filter custom constraints
    for(Constraint aConstraint : constraints) {
      if(aConstraint.getFeatureName().equals(SPEC_FAKE_FEATURE)) {
        if(specConstraint != null){
          throw new IllegalArgumentException("Only one constraint of type ." +
                  SPEC_FAKE_FEATURE + " is permitted!"); 
        }else{
          specConstraint = aConstraint;
        }
      } else {
        passThroughConstraints.add(aConstraint);
      }
    }
    if(specConstraint == null){
      //no custom constraints
      return super.getMentions(annotationType, passThroughConstraints, engine);
    }else{
      //process the custom constraints
      List[] alternatives = convertSpecConstraint(specConstraint);
      //use Set to avoid duplicates
      Set results = new HashSet();
      for(List anAlternative : alternatives){
        List superConstraints = new ArrayList(
                passThroughConstraints.size() + anAlternative.size());
        superConstraints.addAll(passThroughConstraints);
        superConstraints.addAll(anAlternative);
        results.addAll(super.getMentions(annotationType, superConstraints, 
                engine));
      }
      return new ArrayList(results);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy