Advanced Usage
Adding properties and restrictions to classes is a good way to preserve some desired functionality when classes are serialized so that all new instances of that class have the same property values.
Properties
Three different types of properties are implemented in pathology-image-features. These are:
Annotation Properties (strings which describe what a class or instance is)
Object Properties (links between classes/instances and other classes/instances)
Datatype Properties (assignments of data values to classes/instances)
Properties can be accessed for new instances of a class through it’s attributes. Properties are automatically given an “alias” which (in most cases) is resolvable to a valid Python attribute name. This alias is either the “rdfs:label” value for the property, the result of replacing the spaces in the “rdfs:label” value with underscores, or the final portion of the URI for the property after removing it’s namespace.
For example, the property “is about” (IAO:0000136) can be accessed from an instance in the following ways:
print(f'{instance.is_about=}')
print('------------------------')
print(f'{instance.get_property("is about")=}')
print('------------------------')
print(f'{instance.get_property("http://purl.obolibrary.org/obo/IAO_0000136")=}')
print('------------------------')
Object and Datatype Properties have a “range” which defines the type of value that can be assigned to them. This is where you can specify both Python data types (for Datatype properties) and class/instance types for Object Properties. They also have a “domain” which defines which classes can use this property. Both domain and range can consist of either lists of classes or single classes. New properties can be defined using the following:
from pathology_image_features.ontology.properties import ObjectProperty, AnnotationProperty, DatatypeProperty
package_version = AnnotationProperty(iri = "http://example.org/package_version", definition = "The version of a package used.", name = "package version")
user_login_name = DatatypeProperty(iri = "http://example.org/user_login_name", definition = "The login name of the user.", name = "user login name", range = str)
# Where User and Group are defined classes elsewhere
user_group = ObjectProperty(iri = "http://example.org/user_group", definition = "The group the user belongs to.", name = "user group", domain = User, range = Group)
After creating a new property, you can register it with the current graph using the ‘register_property’ method. This allows you to refer to it in class attributes and restrictions using it’s alias.
from pathology_image_features.ontology import PIFO
from pathology_image_features.ontology.restrictions import Only
from somewhere import user_group, User, GroupA
PIFO.register_property(user_group)
class UserInGroupA(User):
"""A class which is represented by any user belonging to Group A."""
equivalent_to = Only(on_property="user_group", on_class=GroupA)
Restrictions
Restrictions are used to describe classes based on some property requirements. In pathology-image-features, the class attribute ‘equivalent_to’ is where restrictions are accessed and applied on the creation of new instances.
Builtin restriction types include:
Some: Specifying that at least instances of this class must have at least one value for a property that matches a given class.
Only: Specifying that only instances of this class may have a specified property/value combination.
Cardinality -> MinCardinality, MaxCardinality, ExactCardinality: Specifying that an instance must have a minimum, maximum, or exact number of values for a given property.
Combinations of Restrictions can also be made using the “And”, “Or”, and “Not” classes.
from pathology_image_features.ontology.restrictions import And, Or, Not, Some, Only, MinCardinality, MaxCardinality, ExactCardinality
class UserInGroup(User):
"""A class representing users which belong to at most one group."""
equivalent_to = And([
Some(on_property="user_group", on_class=Group), # Must have the user_group property (whose range is already Group so this is redundant)
MaxCardinality(on_property="user_group", value = 1) # Must have at most one value for the user_group property
])
# Since the property "user_group" has a defined domain and range, the "Some" restriction can be omitted.
class UserInGroup(User):
equivalent_to = MaxCardinality(on_property="user_group", value = 1)
class UserInMultipleGroups(User):
"""A class representing users which belong to more than one group."""
equivalent_to = MinCardinality(on_property="user_group", value = 2)
Different behavior can be registered for instance creation when a “equivalent_to” attribute is present for a class. In some cases, it may be desirable to raise an error if a new instance does not have a value for a property which is defined in a Restriction.
Restriction subclasses have a “on_missing” keyword argument on initialization which can be replaced with either “None” or a custom Callable to indicate what should be done if a property is not found for a given instance. By default, pathology-image-features will create a new instance of the “on_class” property and assign it to that instance if it is not present.
class UserInGroup(User):
equivalent_to = MaxCardinality(on_property="user_group", value = 1, on_missing=None)
# This will cause an error as no "user_group" value was provided to the instance
new_user = UserInGroup()
# This will not raise an error as the "user_group" value is specified for this instance
new_user = UserInGroup(user_group=GroupA())
def custom_on_missing(restriction, instance):
"""If you want to set a default value as "GroupA" for non-grouped UserInGroup instances."""
default_val = GroupA()
user_group.__set__(instance, default_val)
setattr(
instance.__class__,
"user_group",
property(
lambda inst: user_group.__get__(inst),
lambda inst, val: user_group.__set__(inst,val),
lambda inst: user_group.__delete__(inst)
)
)
class UserInGroup(User):
equivalent_to = MaxCardinality(on_property="user_group", value = 1, on_missing=custom_on_missing)
new_user = UserInGroup()
print(f'{isinstance(new_user.user_group[0], GroupA)}') # Will print True