# Multiple CAS Views of an Artifact

Multiple CAS Views

UIMA provides an extension to the basic model of the CAS which supports analysis of multiple views of the same artifact, all contained with the CAS. This chapter describes the concepts, terminology, and the API and XML extensions that enable this.

Multiple CAS Views can simplify things when different versions of the artifact are needed at different stages of the analysis. They are also key to enabling multimodal analysis where the initial artifact is transformed from one modality to another, or where the artifact itself is multimodal, such as the audio, video and closed-captioned text associated with an MPEG object. Each representation of the artifact can be analyzed independently with the standard UIMA programming model; in addition, multi-view components and applications can be constructed.

UIMA supports this by augmenting the CAS with additional light-weight CAS objects, one for each view, where these objects share most of the same underlying CAS, except for two things: each view has its own set of indexed Feature Structures, and each view has its own subject of analysis (Sofa) - its own version of the artifact being analyzed. The Feature Structure instances themselves are in the shared part of the CAS; only the entries in the indexes are unique for each CAS view.

All of these CAS view objects are kept together with the CAS, and passed as a unit between components in a UIMA application. APIs exist which allow components and applications to switch among the various view objects, as needed.

Feature Structures may be indexed in multiple views, if necessary. New methods on CAS Views facilitate adding or removing Feature Structures to or from their index repositories:

    aView.addFsToIndexes(aFeatureStructure)
    aView.removeFsFromIndexes(aFeatureStructure)

specify the view in which this Feature Structure should be added to or removed from the indexes.

## CAS Views and Sofas

Sofas (see ) and CAS Views are linked. In this implementation, every CAS view has one associated Sofa, and every Sofa has one associated CAS View.

### Naming CAS Views and Sofas

The developer assigns a name to the View / Sofa, which is a simple string (following the rules for Java identifiers, usually without periods, but see special exception below). These names are declared in the component XML metadata, and are used during assembly and by the runtime to enable switching among multiple Views of the CAS at the same time.

The name is called the Sofa name, for historical reasons, but it applies equally to the View. In the rest of this chapter, we'll refer to it as the Sofa name.

Some applications contain components that expect a variable number of Sofas as input or output. An example of a component that takes a variable number of input Sofas could be one that takes several translations of a document and merges them, where each translation was in a separate Sofa.

You can specify a variable number of input or output sofa names, where each name has the same base part, by writing the base part of the name (with no periods), followed by a period character and an asterisk character (.*). These denote sofas that have names matching the base part up to the period; for example, names such as `base_name_part.TTX_3d` would match a specification of `base_name_part.*`.

### Multi-View, Single-View components & applications

Multi/Single View parts in Applications

Components and applications can be written to be Multi-View or Single-View. Most components used as primitive building blocks are expected to be Single-View. UIMA provides capabilities to combine these kinds of components with Multi-View components when assembling analysis aggregates or applications.

Single-View components and applications use only one subject of analysis, and one CAS View. The code and descriptors for these components do not use the facilities described in this chapter.

Conversely, Multi-View components and applications are aware of the possibility of multiple Views and Sofas, and have code and XML descriptors that create and manipulate them.

## Multi-View Components

### How UIMA decides if a component is Multi-View

Deciding: Multi-View

Every UIMA component has an associated XML Component Descriptor. Multi-View components are identified simply as those whose descriptors declare one or more Sofa names in their Capability sections, as inputs or outputs. If a Component Descriptor does not mention any input or output Sofa names, the framework treats that component as a Single-View component.

A Multi-View component is passed a special kind of a CAS object, called a base CAS, which it must use to switch to the particular view it wishes to process. The base CAS object itself has no Sofa and no ability to use Indexes; only the views have that capability.

### Multi-View: additional capabilities

Additional capabilities provided for components and applications aware of the possibilities of multiple Views and Sofas include:

- Creating new Views, and for each, setting up the associated Sofa data

- Getting a reference to an existing View and its associated Sofa, by name

- Specifying a view in which to index a particular Feature Structure instance

### Component XML metadata

Each Multi-View component that creates a Sofa or wants to switch to a specific previously created Sofa must declare the name for the Sofa in the capabilities section. For example, a component expecting as input a web document in html format and creating a plain text document for further processing might declare:

    <capabilities>
      <capability>
        <inputs/>
        <outputs/>
        <inputSofas>
          <sofaName>rawContent</sofaName>
        </inputSofas>
        <outputSofas>
          <sofaName>detagContent</sofaName>
        </outputSofas>
      </capability>
    </capabilities>

Details on this specification are found in . The Component Descriptor Editor supports Sofa declarations on the Capabilites Page.

## Sofa Capabilities and APIs for Applications

Sofa Capabilities & APIs for Apps

In addition to components, applications can make use of these capabilities. When an application creates a new CAS, it also creates the initial view of that CAS - and this view is the object that is returned from the create call. Additional views beyond this first one can be dynamically created at any time. The application can use the Sofa APIs described in to specify the data to be analyzed.

If an Application creates a new CAS, the initial CAS that is created will be a view named _InitialView. This name can be used in the application and in Sofa Mapping (see the next section) to refer to this otherwise unnamed view.

## Sofa Name Mapping

Sofa Name mapping is the mechanism which enables UIMA component developers to choose locally meaningful Sofa names in their source code and let aggregate, collection processing engine developers, and application developers connect output Sofas created in one component to input Sofas required in another.

At a given aggregation level, the assembler or application developer defines names for all the Sofas, and then specifies how these names map to the contained components, using the Sofa Map.

Consider annotator code to create a new CAS view:

    CAS viewX = cas.createView("X");

Or code to get an existing CAS view:

    CAS viewX = cas.getView("X");

Without Sofa name mapping the SofaID for the new Sofa will be X. However, if a name mapping for X has been specified by the aggregate or CPE calling this annotator, the actual SofaID in the CAS can be different.

All Sofas in a CAS must have unique names. This is accomplished by mapping all declared Sofas as described in the following sections. An attempt to create a Sofa with a SofaID already in use will throw an exception.

Sofa name mapping must not use the . (period) character. Runtime Sofa mapping maps names up to the . and appends the period and the following characters to the mapped name.

To get a Java Iterator for all the views in a CAS:

    Iterator allViews = cas.getViewIterator();

To get a Java Iterator for selected views in a CAS, for example, views whose name is either exactly equal to namePrefix or is of the form namePrefix.suffix, where suffix can be any String:

    Iterator someViews = cas.getViewIterator(String namePrefix);

Sofa name mapping is applied to namePrefix.

Sofa name mappings are not currently supported for remote Analysis Engines. See .

### Name Mapping in an Aggregate Descriptor

For each component of an Aggregate, name mapping specifies the conversion between component Sofa names and names at the aggregate level.

Here's an example. Consider two Multi-View annotators to be assembled into an aggregate which takes an audio segment consisting of spoken English and produces a German text translation.

The first annotator takes an audio segment as input Sofa and produces a text transcript as output Sofa. The annotator designer might choose these Sofa names to be AudioInput and TranscribedText.

The second annotator is designed to translate text from English to German. This developer might choose the input and output Sofa names to be EnglishDocument and GermanDocument, respectively.

In order to hook these two annotators together, the following section would be added to the top level of the aggregate descriptor:

    <sofaMappings>
      <sofaMapping>
        <componentKey>SpeechToText</componentKey>
        <componentSofaName>AudioInput</componentSofaName>
        <aggregateSofaName>SegementedAudio</aggregateSofaName>
      </sofaMapping>
      <sofaMapping>
        <componentKey>SpeechToText</componentKey>
        <componentSofaName>TranscribedText</componentSofaName>
        <aggregateSofaName>EnglishTranscript</aggregateSofaName>
      </sofaMapping>
      <sofaMapping>
        <componentKey>EnglishToGermanTranslator</componentKey>
        <componentSofaName>EnglishDocument</componentSofaName>
        <aggregateSofaName>EnglishTranscript</aggregateSofaName>
      </sofaMapping>
      <sofaMapping>
        <componentKey>EnglishToGermanTranslator</componentKey>
        <componentSofaName>GermanDocument</componentSofaName>
        <aggregateSofaName>GermanTranslation</aggregateSofaName>
      </sofaMapping>
    </sofaMappings>

The Component Descriptor Editor supports Sofa name mapping in aggregates and simplifies the task. See for details.

### Name Mapping in a CPE Descriptor

The CPE descriptor aggregates together a Collection Reader and CAS Processors (Annotators and CAS Consumers). Sofa mappings can be added to the following elements of CPE descriptors: `<collectionIterator>`, `<casInitializer>` and the `<casProcessor>`. To be consistent with the organization of CPE descriptors, the maps for the CPE descriptor are distributed among the XML markup for each of the parts (collectionIterator, casInitializer, casProcessor). Because of this the`<componentKey>` element is not needed. Finally, rather than sub-elements for the parts, the XML markup for these uses attributes. See .

Here's an example. Let's use the aggregate from the previous section in a collection processing engine. Here we will add a Collection Reader that outputs audio segments in an output Sofa named nextSegment. Remember to declare an output Sofa nextSegment in the collection reader description. We'll add a CAS Consumer in the next section.

    <collectionReader>
      <collectionIterator>
        <descriptor>
        . . .
        </descriptor>
        <configurationParameterSettings>...</configurationParameterSettings>
        <sofaNameMappings>
          <sofaNameMapping componentSofaName="nextSegment"
                           cpeSofaName="SegementedAudio"/>
          </sofaNameMappings>
      </collectionIterator>
      <casInitializer/>
    <collectionReader>

At this point the CAS Processor section for the aggregate does not need any Sofa mapping because the aggregate input Sofa has the same name, SegementedAudio, as is being produced by the Collection Reader.

### Specifying the CAS View for a Single-View Component

CAS View for Single-View Parts

Single-View components receive a Sofa named _InitialView, or a Sofa that is mapped to this name.

For example, assume that the CAS Consumer to be used in our CPE is a Single-View component that expects the analysis results associated with the input CAS, and that we want it to use the results from the translated German text Sofa. The following mapping added to the CAS Processor section for the CPE will instruct the CPE to get the CAS view for the German text Sofa and pass it to the CAS Consumer:

    <casProcessor>
      . . .
      <sofaNameMappings>
        <sofaNameMapping componentSofaName="_InitialView"
                               cpeSofaName="GermanTranslation"/>
      <sofaNameMappings>
    </casProcessor>

An alternative syntax for this kind of mapping is to simply leave out the component sofa name in this case.

### Name Mapping in a UIMA Application

Applications which instantiate UIMA components directly using the UIMAFramework methods can also create a top level Sofa mapping using the additional parameters capability.

    //create a "root" UIMA context for your whole application

    UimaContextAdmin rootContext =
       UIMAFramework.newUimaContext(UIMAFramework.getLogger(),
          UIMAFramework.newDefaultResourceManager(),
          UIMAFramework.newConfigurationManager());

    input = new XMLInputSource("test.xml");
    desc = UIMAFramework.getXMLParser().parseAnalysisEngineDescription(input);

    //setup sofa name mappings using the api

    HashMap sofamappings = new HashMap();
    sofamappings.put("localName1", "globalName1");
    sofamappings.put("localName2", "globalName2");

    //create a UIMA Context for the new AE we are about to create

    //first argument is unique key among all AEs used in the application
    UimaContextAdmin childContext = rootContext.createChild("myAE", sofamap);

    //instantiate AE, passing the UIMA Context through the additional
    //parameters map

    Map additionalParams = new HashMap();
    additionalParams.put(Resource.PARAM_UIMA_CONTEXT, childContext);

    AnalysisEngine ae =
            UIMAFramework.produceAnalysisEngine(desc,additionalParams);

Sofa mappings are applied from the inside out, i.e., local to global. First, any aggregate mappings are applied, then any CPE mappings, and finally, any specified using this additional parameters capability.

### Name Mapping for Remote Services

Currently, no client-side Sofa mapping information is passed from a UIMA client to a remote service. This can cause complications for UIMA services in a Multi-View application.

Remote Multi-View services will work only if the service is Single-View, or if the Sofa names expected by the service exactly match the Sofa names produced by the client.

If your application requires Sofa mappings for a remote Analysis Engine, you can wrap your remotely deployed AE in an aggregate (on the remote side), and specify the necessary Sofa mappings in the descriptor for that aggregate.

## JCas extensions for Multiple Views

The JCas interface to the CAS can be used with any / all views, as well as the base CAS sent to Multi-View components. You can always get a JCas object from an existing CAS object by using the method getJCas(); this call will create the JCas if it doesn't already exist. If it does exist, it just returns the existing JCas that corresponds to the CAS.

JCas implements the getView(...) method, enabling switching to other named views, just like the corresponding method on the CAS. The JCas version, however, returns JCas objects, instead of CAS objects, corresponding to the view.

## Sample Multi-View Application

The UIMA SDK contains a simple Sofa example application which demonstrates many Sofa specific concepts and methods. The source code for the application driver is in `examples/src/org/apache/uima/examples/SofaExampleApplication.java` and the Multi-View annotator is given in `SofaExampleAnnotator.java` in the same directory.

This sample application demonstrates a language translator annotator which expects an input text Sofa with an English document and creates an output text Sofa containing a German translation. Some of the key Sofa concepts illustrated here include:

- Sofa creation.

- Access of multiple CAS views.

- Unique feature structure index space for each view.

- Feature structures containing cross references between annotations in different CAS views.

- The strong affinity of annotations with a specific Sofa.

### Annotator Descriptor

The annotator descriptor in `examples/descriptors/analysis_engine/SofaExampleAnnotator.xml` declares an input Sofa named EnglishDocument and an output Sofa named GermanDocument. A custom type CrossAnnotation is also defined:

    <typeDescription>
      <name>sofa.test.CrossAnnotation</name>
      <description/>
      <supertypeName>uima.tcas.Annotation</supertypeName>
      <features>
        <featureDescription>
          <name>otherAnnotation</name>
          <description/>
          <rangeTypeName>uima.tcas.Annotation</rangeTypeName>
        </featureDescription>
      </features>
    </typeDescription>

The `CrossAnnotation` type is derived from `uima.tcas.Annotation` and includes one new feature: a reference to another annotation.

### Application Setup

The application driver instantiates an analysis engine, `seAnnotator`, from the annotator descriptor, obtains a new base CAS using that engine's CAS definition, and creates the expected input Sofa using:

    CAS cas = seAnnotator.newCAS();
    CAS aView = cas.createView("EnglishDocument");

Since `seAnnotator` is a primitive component, and no Sofa mapping has been defined, the SofaID will be EnglishDocument. Local Sofa data is set using:

    aView.setDocumentText("this beer is good");

At this point the CAS contains all necessary inputs for the translation annotator and its process method is called.

### Annotator Processing

Annotator processing consists of parsing the English document into individual words, doing word-by-word translation and concatenating the translations into a German translation. Analysis metadata on the English Sofa will be an annotation for each English word. Analysis metadata on the German Sofa will be a `CrossAnnotation` for each German word, where the `otherAnnotation` feature will be a reference to the associated English annotation.

Code of interest includes two CAS views:

    // get View of the English text Sofa
    englishView = aCas.getView("EnglishDocument");

    // Create the output German text Sofa
    germanView = aCas.createView("GermanDocument");

the indexing of annotations with the appropriate view:

    englishView.addFsToIndexes(engAnnot);
    . . .
    germanView.addFsToIndexes(germAnnot);

and the combining of metadata belonging to different Sofas in the same feature structure:

    // add link to English text
    germAnnot.setFeatureValue(other, engAnnot);

### Accessing the results of analysis

The application needs to get the results of analysis, which may be in different views. Analysis results for each Sofa are dumped independently by iterating over all annotations for each associated CAS view. For the English Sofa:

    //get annotation iterator for this CAS
    FSIndex anIndex = aView.getAnnotationIndex();
    FSIterator anIter = anIndex.iterator();
    while (anIter.isValid()) {
      AnnotationFS annot = (AnnotationFS) anIter.get();
      System.out.println(" " + annot.getType().getName()
                             + ": " + annot.getCoveredText());
      anIter.moveToNext();
    }

Iterating over all German annotations looks the same, except for the following:

    if (annot.getType() == cross) {
      AnnotationFS crossAnnot =
              (AnnotationFS) annot.getFeatureValue(other);
      System.out.println("   other annotation feature: "
              + crossAnnot.getCoveredText());
    }

Of particular interest here is the built-in Annotation type method `getCoveredText()`. This method uses the begin and end features of the annotation to create a substring from the CAS document. The SofaRef feature of the annotation is used to identify the correct Sofa's data from which to create the substring.

The example program output is:

    ---Printing all annotations for English Sofa---
    uima.tcas.DocumentAnnotation: this beer is good
    uima.tcas.Annotation: this
    uima.tcas.Annotation: beer
    uima.tcas.Annotation: is
    uima.tcas.Annotation: good

    ---Printing all annotations for German Sofa---
    uima.tcas.DocumentAnnotation: das bier ist gut
    sofa.test.CrossAnnotation: das
     other annotation feature: this
    sofa.test.CrossAnnotation: bier
     other annotation feature: beer
    sofa.test.CrossAnnotation: ist
     other annotation feature: is
    sofa.test.CrossAnnotation: gut
     other annotation feature: good

## Views API Summary

The recommended way to deliver a particular CAS view to a Single-View component is to use by Sofa-mapping in the CPE and/or aggregate descriptors.

For Multi-View components or applications, the following methods are used to create or get a reference to a CAS view for a particular Sofa:

Creating a new View:

    JCas newView = aJCas.createView(String localNameOfTheViewBeforeMapping);
    CAS  newView = aCAS .createView(String localNameOfTheViewBeforeMapping);

Getting a View from a CAS or JCas:

    JCas myView = aJCas.getView(String localNameOfTheViewBeforeMapping);
    CAS  myView = aCAS .getView(String localNameOfTheViewBeforeMapping);
    Iterator allViews = aCasOrJCas.getViewIterator();
    Iterator someViews = aCasOrJCas.getViewIterator(String localViewNamePrefix);

The following methods are useful for all annotators and applications:

Setting Sofa data for a CAS or JCas:

    aCasOrJCas.setDocumentText(String docText);
    aCasOrJCas.setSofaDataString(String docText, String mimeType);
    aCasOrJCas.setSofaDataArray(FeatureStructure array, String mimeType);
    aCasOrJCas.setSofaDataURI(String uri, String mimeType);

Getting Sofa data for a particular CAS or JCas:

    String doc = aCasOrJCas.getDocumentText();
    String doc = aCasOrJCas.getSofaDataString();
    FeatureStructure array = aCasOrJCas.getSofaDataArray();
    String uri = aCasOrJCas.getSofaDataURI();
    InputStream is = aCasOrJCas.getSofaDataStream();

## Sofa Incompatibilities between UIMA version 1 and version 2

Sofa Incompatibilities: V1 and V2

A major change in version 2 is related to the support of Single-View components and applications. Given an analysis engine, `ae`, the API

    CAS cas = ae.newCas();

used to return the base CAS. Now it returns a view of the Sofa named _InitialView. This Sofa will actually only be created if any Sofa data is set for this view. The initial view is used for Single-View applications and Multi-View annotators with no Sofa mapping.

The process method of Multi-View annotators receive the base CAS, however the base CAS no longer has an index repository to hold global data. Global data needs to be put in a specific named CAS view of your choice.

Because of these changes, the following scenarios will break with v2.0 clients:

- Any version 1.x services (you must migrate the services to version 2).

- Applications or components explicitly referencing _DefaultTextSofaName in code or descriptors.

- Multi-View applications using the Base CAS index repository.