Background

pathology-image-features builds off of ontology principles to standardize common processes in an image analysis pipeline, specifically for the analysis of pathology data. This allows for better understanding how different types of quantitative metrics and processes are related to each other, as well as preparing data for input into an RDF/graph database.

With this library, one can:

  • Read annotations for whole slide images (WSIs) from a variety of formats

  • Extract corresponding image regions for individual structures

  • String together complicated processes with minimal coding required

  • Define quantitative feature extraction pipelines with built-in documentation

Why use ontology? What is ontology?

In philosophy, ontology is the study of being (Wikipedia). Ontologists collaborate (and sometimes argue) on the fundamental structure of reality and every entity within it. How different entities are defined, how they relate to one another, how individual “instances” of an entity are differentiated, and how sub-categories of entities are distinguished all fall under the umbrella of ontology.

The highly specific nature of ontological concepts results in a highly formalized language for definitions which can be difficult for non-ontology experts to understand. One such example of an ontology is the Basic Formal Ontology. As an “upper-level” ontology, the BFO is meant to be as general as possible. This means that you won’t find any specific terms relating to physical, chemical, or biological entities where a more specific “middle-” or “lower-” level ontology would be more appropriate. The Gene Ontology is used for characterizing how individual genes are involved in specific biological processes and is useful for determining what is impacted under experimental conditions.

Integrating ontology in Python

In the context of programming, ontology principles are actually employed fairly often without people realizing it. Take a look at the following code adapted from Owlready2 documentation.

from restaurant.menu import Pizza
from grocery_store.items import (
    Cheese, PizzaSauce,
    Pepperoni, Sausage,
    Olives, Peppers
)

class VegetarianPizza(Pizza):
    def add_topping(self, topping):
        if type(topping) in [Pepperoni,Sausage]:
            raise Exception('No meat allowed!')
        self.toppings.append(topping)

class PepperoniAndCheesePizza(Pizza):
    def add_topping(self, topping):
        if type(topping) not in [Cheese,Pepperoni,PizzaSauce]:
            raise Exception('What part of Pepperoni and Cheese do you not understand??')
        self.toppings.append(topping)

We can clearly see that VegetarianPizza and PepperoniAndCheesePizza, while both being types of Pizza, are constrained in the types of toppings that can be added to them. Using inheritance in Python, we can avoid restating a large amount of shared properties while assigning specific methods and properties to subclasses based on their desired function or characteristics. Extending on this example, we can define which types of customers can have which types of Pizza based on any dietary restrictions they might have (think VegetarianCustomer vs. CarnivoreCustomer). In OWL (Web Ontology Language), a VegetarianPizza can be defined like this (from pizza.owl):

<owl:Class rdf:about="#VegetarianPizza">
    <rdfs:label xml:lang="pt"
        >PizzaVegetariana</rdfs:label>
    <rdfs:comment xml:lang="en"
        >Any pizza that does not have fish topping and does not have meat topping is a VegetarianPizza. Members of this class do not need to have any toppings at all.</rdfs:comment>
    <owl:equivalentClass>
        <owl:Class>
            <owl:intersectionOf rdf:parseType="Collection">
                <owl:Class rdf:about="#Pizza"/>
                <owl:Class>
                    <owl:complementOf>
                        <owl:Restriction>
                            <owl:onProperty rdf:resource="#hasTopping"/>
                            <owl:someValuesFrom rdf:resource="#MeatTopping"/>
                        </owl:Restriction>
                    </owl:complementOf>
                </owl:Class>
                <owl:Class>
                    <owl:complementOf>
                        <owl:Restriction>
                            <owl:onProperty rdf:resource="#hasTopping"/>
                            <owl:someValuesFrom rdf:resource="#FishTopping"/>
                        </owl:Restriction>
                    </owl:complementOf>
                </owl:Class>
            </owl:intersectionOf>
        </owl:Class>
    </owl:equivalentClass>
</owl:Class>

Ontology in pathology image analysis

Pathology images are different from everyday, ordinary images for a variety of reasons. They are generally very high-resolution, they contain tissue which may have a specific type of stain applied, and they sometimes contain annotations of regions of interest (ROIs). In the process of analyzing these images, custom workflows are developed and subsequent datasets are constructed consisting of more images, annotations, or quantitative metrics.

While writing up an ontology for these different components of an image analysis workflow, we realized that it would be also be beneficial to define the actual Python scripts which are used in the execution of these parts within the ontology. Not only would this prevent confusion in how a specific metric is calculated, it would also provide a means for serializing a pipeline into a graph database which can be reconstructed and run on subsequent datasets. And therefore, pathology-image-features was born!