Package: dkpro-cassis
DKProcassis(pronunciation: [ka.sis]) provides a pure-Python implementation of theCommon Analysis System(CAS)
as defined by theUIMAframework. The CAS is a data structure representing an object to
be enriched with annotations (the co-calledSubject of Analysis, shortSofA).This library enables the creation and manipulation of annotated documents (CAS objects) and their associated type systems as well as loading
and saving them in theCAS XMI XML representationor theCAS JSON representationin Python programs. This can ease in particular the integration of Python-based Natural Language Processing (e.g.spacyorNLTK) and Machine Learning librarys (e.g.scikit-learnorKeras) in UIMA-based text analysis workflows.An example of cassis in action is thespacy recommender for INCEpTION,
which wraps the spacy NLP library as a web service which can be used in conjunction with theINCEpTIONtext annotation platform to automatically generate annotation suggestions.FeaturesCurrently supported features are:Text SofAsDeserializing/serializing UIMA CAS from/to XMIDeserializing/serializing UIMA CAS from/to JSONDeserializing/serializing type systems from/to XMLSelecting annotations, selecting covered annotations, adding annotationsType inheritanceMultiple SofA supportType system can be changed after loadingPrimitive and reference features and arrays of primitives and referencesSome features are still under development, e.g.Proper type checkingXML/XMI schema validationInstallationTo install the package withpip, just runpip install dkpro-cassisUsageExample CAS XMI and types system files can be found undertests\test_files.Reading a CAS fileFrom XMI:A CAS can be deserialized from the UIMA CAS XMI (XML 1.0) format either
by reading from a file or string usingload_cas_from_xmi.fromcassisimport*withopen('typesystem.xml','rb')asf:typesystem=load_typesystem(f)withopen('cas.xmi','rb')asf:cas=load_cas_from_xmi(f,typesystem=typesystem)From JSON:The UIMA JSON CAS format is also supported and can be loaded usingload_cas_from_json.
Most UIMA JSON CAS files come with an embedded typesystem, so it is not necessary to specify one.fromcassisimport*withopen('cas.json','rb')asf:cas=load_cas_from_json(f)Writing a CAS fileTo XMI:A CAS can be serialized to XMI either by writing to a file or be
returned as a string usingcas.to_xmi().fromcassisimport*# Returned as a stringxmi=cas.to_xmi()# Written to filecas.to_xmi("my_cas.xmi")To JSON:A CAS can also be written to JSON usingcas.to_json().fromcassisimport*# Returned as a stringxmi=cas.to_json()# Written to filecas.to_json("my_cas.json")Creating a CASA CAS (Common Analysis System) object typically represents a (text) document. When using cassis,
you will likely most often reading existing CAS files, modify them and then
writing them out again. But you can also create CAS objects from scratch,
e.g. if you want to convert some data into a CAS object in order to create a pre-annotated text.
If you do not have a pre-defined typesystem to work with, you will have to define one.typesystem=TypeSystem()cas=Cas(sofa_string="Joe waited for the train . The train was late .",document_language="en",typesystem=typesystem)print(cas.sofa_string)print(cas.sofa_mime)print(cas.document_language)Adding annotationsNote:type names used below are examples only. The actual CAS files you will be
dealing with will use other names! You can get a list of the types usingcas.typesystem.get_types().Given a type system with a typecassis.Tokenthat has anidandposfeature, annotations can be added in the following:fromcassisimport*withopen('typesystem.xml','rb')asf:typesystem=load_typesystem(f)withopen('cas.xmi','rb')asf:cas=load_cas_from_xmi(f,typesystem=typesystem)Token=typesystem.get_type('cassis.Token')tokens=[Token(begin=0,end=3,id='0',pos='NNP'),Token(begin=4,end=10,id='1',pos='VBD'),Token(begin=11,end=14,id='2',pos='IN'),Token(begin=15,end=18,id='3',pos='DT'),Token(begin=19,end=24,id='4',pos='NN'),Token(begin=25,end=26,id='5',pos='.'),]fortokenintokens:cas.add(token)Selecting annotationsfromcassisimport*withopen('typesystem.xml','rb')asf:typesystem=load_typesystem(f)withopen('cas.xmi','rb')asf:cas=load_cas_from_xmi(f,typesystem=typesystem)forsentenceincas.select('cassis.Sentence'):fortokenincas.select_covered('cassis.Token',sentence):print(token.get_covered_text())# Annotation values can be accessed as propertiesprint('Token: begin={0}, end={1}, id={2}, pos={3}'.format(token.begin,token.end,token.id,token.pos))Getting and setting (nested) featuresIf you want to access a variable but only have its name as a string or have nested feature structures,
e.g. a feature structure  with featureathat has a
featurebthat has a featurec, some of which can beNone, then you can use the
following:fs.get("var_name")# Orfs["var_name"]Or in the nested case,fs.get("a.b.c")fs["a.b.c"]IfaorborcareNone, then this returns instead of
throwing an error.Another example would be a StringList containing["Foo", "Bar", "Baz"]:assertlst.get("head")=="foo"assertlst.get("tail.head")=="bar"assertlst.get("tail.tail.head")=="baz"assertlst.get("tail.tail.tail.head")==Noneassertlst.get("tail.tail.tail.tail.head")==NoneThe same goes for setting:# Functionallst.set("head","new_foo")lst.set("tail.head","new_bar")lst.set("tail.tail.head","new_baz")assertlst.get("head")=="new_foo"assertlst.get("tail.head")=="new_bar"assertlst.get("tail.tail.head")=="new_baz"# Bracket accesslst["head"]="newer_foo"lst["tail.head"]="newer_bar"lst["tail.tail.head"]="newer_baz"assertlst["head"]=="newer_foo"assertlst["tail.head"]=="newer_bar"assertlst["tail.tail.head"]=="newer_baz"Creating types and adding featuresfromcassisimport*typesystem=TypeSystem()parent_type=typesystem.create_type(name='example.ParentType')typesystem.create_feature(domainType=parent_type,name='parentFeature',rangeType=TYPE_NAME_STRING)child_type=typesystem.create_type(name='example.ChildType',supertypeName=parent_type.name)typesystem.create_feature(domainType=child_type,name='childFeature',rangeType=TYPE_NAME_INTEGER)annotation=child_type(parentFeature='parent',childFeature='child')When adding new features, these changes are propagated. For example,
adding a feature to a parent type makes it available to a child type.
Therefore, the type system does not need to be frozen for consistency.
The type system can be changed even after loading, it is not frozen
like in UIMAj.Sofa supportA Sofa represents some form of an unstructured artifact that is processed in a UIMA pipeline. It contains for instance
the document text. Currently, new Sofas can be created. This is automatically done when creating a new view. Basic
properties of the Sofa can be read and written:cas=Cas(sofa_string="Joe waited for the train . The train was late .",document_language="en")print(cas.sofa_string)print(cas.sofa_mime)print(cas.document_language)Array supportArray feature values are not simply Python arrays, but they are wrapped in a feature structure of
a UIMA array type such asuima.cas.FSArray.fromcassisimport*fromcassis.typesystemimportTYPE_NAME_FS_ARRAY,TYPE_NAME_ANNOTATIONtypesystem=TypeSystem()ArrayHolder=typesystem.create_type(name='example.ArrayHolder')typesystem.create_feature(domainType=ArrayHolder,name='array',rangeType=TYPE_NAME_FS_ARRAY)cas=Cas(typesystem=typesystem)Annotation=cas.typesystem.get_type(TYPE_NAME_ANNOTATION)FSArray=cas.typesystem.get_type(TYPE_NAME_FS_ARRAY)ann=Annotation(begin=0,end=1)cas.add(ann1)holder=ArrayHolder(array=FSArray(elements=[ann,ann,ann]))cas.add(holder)Managing viewsA view into a CAS contains a subset of feature structures and annotations. One view corresponds to exactly one Sofa. It
can also be used to query and alter information about the Sofa, e.g. the document text. Annotations added to one view
are not visible in another view.  A view Views can be created and changed. A view has the same methods and attributes
as aCas.fromcassisimport*withopen('typesystem.xml','rb')asf:typesystem=load_typesystem(f)Token=typesystem.get_type('cassis.Token')# This creates automatically the view `_InitialView`cas=Cas()cas.sofa_string="I like cheese ."cas.add_all([Token(begin=0,end=1),Token(begin=2,end=6),Token(begin=7,end=13),Token(begin=14,end=15)])print([x.get_covered_text()forxincas.select_all()])# Create a new view and work on it.view=cas.create_view('testView')view.sofa_string="I like blackcurrant ."view.add_all([Token(begin=0,end=1),Token(begin=2,end=6),Token(begin=7,end=19),Token(begin=20,end=21)])print([x.get_covered_text()forxinview.select_all()])Merging type systemsSometimes, it is desirable to merge two type systems. Withcassis, this can be
achieved via themerge_typesystemsfunction. The detailed rules of merging can be foundhere.fromcassisimport*withopen('typesystem.xml','rb')asf:typesystem=load_typesystem(f)ts=merge_typesystems([typesystem,load_dkpro_core_typesystem()])Type checkingWhen adding annotations, no type checking is performed for simplicity reasons.
In order to check types, call thecas.typecheck()method. Currently, it only
checks whether elements inuima.cas.FSArrayare
adhere to the specifiedelementType.DKPro Core IntegrationA CAS using the DKPro Core Type System can be created viafromcassisimport*cas=Cas(typesystem=load_dkpro_core_typesystem())fortincas.typesystem.get_types():print(t)MiscellaneousIf feature names clash with Python magic variablesIf your type system defines a type calledselfortype, then it will be made
available as a member variableself_ortype_on the respective type:fromcassisimport*fromcassis.typesystemimport*typesystem=TypeSystem()ExampleType=typesystem.create_type(name='example.Type')typesystem.create_feature(domainType=ExampleType,name='self',rangeType=TYPE_NAME_STRING)typesystem.create_feature(domainType=ExampleType,name='type',rangeType=TYPE_NAME_STRING)annotation=ExampleType(self_="Test string1",type_="Test string2")print(annotation.self_)print(annotation.type_)LeniencyIf the type for a feature structure is not found in the typesystem, it will raise an exception by default.
If you want to ignore these kind of errors, you can passlenient=Trueto theCasconstructor or
toload_cas_from_xmi.Large XMI filesIf you try to parse large XMI files and get an error message likeXMLSyntaxError: internal error: Huge input lookup,
then you can disable this security check by passingtrusted=Trueto your calls toload_cas_from_xmi.Citing & AuthorsIf you find this repository helpful, feel free to cite@software{klie2020_cassis,author={Jan-Christoph Klie and
                  Richard Eckart de Castilho},title={DKPro Cassis - Reading and Writing UIMA CAS Files in Python},publisher={Zenodo},doi={10.5281/zenodo.3994108},url={https://github.com/dkpro/dkpro-cassis}}DevelopmentThe required dependencies are managed bypip. A virtual environment
containing all needed packages for development and production can be
created and activated byvirtualenv venv --python=python3 --no-site-packages
source venv/bin/activate
pip install -e ".[test, dev, doc]"The tests can be run in the current environment by invokingmake testor in a clean environment viatoxReleaseMake sure all issues for the milestone are completed, otherwise move them to the nextCheckout themainbranchBump the version incassis/__version__.pyto a stable one, e.g.__version__ = "0.6.0", commit and push, wait until the build completed. An example commit message would beNo issue. Release 0.6.0Create a tag for that version via e.g.git tag v0.6.0and push the tags viagit push--tags. Pushing a tag triggers the release to pypiBump the version incassis/__version__.pyto the next development version, e.g.0.7.0-dev, commit and push that. An example commit message would beNo issue. Bump version after releaseOnce the build has completed and pypi accepted the new version, go to the Github release and write the changelog based on the issues in the respective milestoneCreate a new milestone for the next version