4. Python Integration & Applications

Building practical disease diagnosis systems

Python Integration with Plant Disease Ontology

1. Setting Up the Environment

# Create and activate virtual environment
python -m venv venv
source venv/bin/activate  # Linux/Mac
# venv\Scripts\activate  # Windows

# Install required packages
pip install owlready2 pandas numpy jupyter

2. Loading and Querying the Ontology

#| eval: true
#| echo: true
from owlready2 import *
import pandas as pd

# Load the ontology
onto_path.append("path/to/your/ontology")
onto = get_ontology("plant-disease.owl").load()

# Get all diseases
diseases = list(onto.Disease.instances())
print(f"Loaded {len(diseases)} diseases")

# Create a DataFrame of diseases and their treatments
disease_data = []
for disease in diseases:
    treatments = [t.name for t in disease.treatedWith] if disease.treatedWith else []
    disease_data.append({
        'Disease': disease.name,
        'Type': disease.__class__.__name__,
        'Severity': disease.severity[0] if disease.severity else None,
        'Treatments': ", ".join(treatments)
    })

df_diseases = pd.DataFrame(disease_data)
df_diseases.head()

3. Automated Diagnosis System

class PlantDiseaseDiagnoser:
    def __init__(self, ontology_path):
        self.onto = get_ontology(ontology_path).load()
        self.symptom_map = self._build_symptom_map()
    
    def _build_symptom_map(self):
        """Create a map from symptoms to possible diseases"""
        symptom_map = {}
        for disease in self.onto.Disease.instances():
            if hasattr(disease, 'hasSymptom') and disease.hasSymptom:
                for symptom in disease.hasSymptom:
                    if symptom not in symptom_map:
                        symptom_map[symptom] = []
                    symptom_map[symptom].append(disease)
        return symptom_map
    
    def diagnose(self, symptoms, environment=None):
        """
        Diagnose plant based on symptoms and environment
        
        Args:
            symptoms: List of symptom names
            environment: Dict of environmental conditions
            
        Returns:
            List of (disease, confidence) tuples
        """
        disease_scores = {}
        
        # Score diseases based on symptoms
        for symptom_name in symptoms:
            if symptom_name in self.symptom_map:
                for disease in self.symptom_map[symptom_name]:
                    disease_scores[disease] = disease_scores.get(disease, 0) + 1
        
        # Normalize scores
        total_symptoms = len(symptoms)
        results = []
        for disease, score in disease_scores.items():
            confidence = (score / total_symptoms) * 100
            # Apply environment factors if provided
            if environment and hasattr(disease, 'environmentalFactors'):
                confidence = self._adjust_for_environment(confidence, disease, environment)
            results.append((disease, min(round(confidence, 2), 100)))
        
        return sorted(results, key=lambda x: x[1], reverse=True)
    
    def _adjust_for_environment(self, confidence, disease, environment):
        """Adjust confidence based on environmental factors"""
        # Implementation would check temperature, humidity, etc.
        # against disease's preferred conditions
        return confidence

# Example usage
if __name__ == "__main__":
    diagnoser = PlantDiseaseDiagnoser("plant-disease.owl")
    symptoms = ["YellowSpots", "WhitePowder"]
    environment = {"temperature": 25, "humidity": 75}
    
    print("Diagnosis Results:")
    for disease, confidence in diagnoser.diagnose(symptoms, environment):
        print(f"- {disease.name}: {confidence}% confidence")

4. Integration with Machine Learning

import numpy as np
from sklearn.ensemble import RandomForestClassifier
from owlready2 import get_ontology, default_world

class OntologyAwareClassifier:
    def __init__(self, ontology_path):
        self.onto = get_ontology(ontology_path).load()
        self.model = RandomForestClassifier()
        self.feature_names = None
        
    def extract_features(self, plant_data):
        """Extract features using ontology reasoning"""
        # This would extract relevant features based on the ontology
        # For example, checking disease symptoms, plant types, etc.
        features = []
        
        for data in plant_data:
            # Example feature extraction
            feature_vector = [
                len(data.get('symptoms', [])),
                1 if 'YellowSpots' in data.get('symptoms', []) else 0,
                data.get('environment', {}).get('temperature', 0),
                data.get('environment', {}).get('humidity', 0)
            ]
            features.append(feature_vector)
            
        self.feature_names = [
            'num_symptoms',
            'has_yellow_spots',
            'temperature',
            'humidity'
        ]
        
        return np.array(features)
    
    def train(self, X, y):
        """Train the classifier"""
        self.model.fit(X, y)
    
    def predict_with_confidence(self, X):
        """Make predictions with confidence scores"""
        probas = self.model.predict_proba(X)
        classes = self.model.classes_
        return [(classes[i], proba) for i, proba in enumerate(probas[0])]

# Example usage
if __name__ == "__main__":
    # Initialize the classifier with ontology
    classifier = OntologyAwareClassifier("plant-disease.owl")
    
    # Example training data (in practice, you'd load real data)
    plant_data = [
        {'symptoms': ['YellowSpots', 'WhitePowder'], 'environment': {'temperature': 25, 'humidity': 70}},
        # Add more training examples
    ]
    y = ['PowderyMildew']  # Corresponding labels
    
    # Train the model
    X = classifier.extract_features(plant_data)
    classifier.train(X, y)
    
    # Make a prediction
    test_data = [{'symptoms': ['YellowSpots'], 'environment': {'temperature': 24, 'humidity': 68}}]
    X_test = classifier.extract_features(test_data)
    predictions = classifier.predict_with_confidence(X_test)
    
    print("\nPrediction Results:")
    for disease, confidence in predictions:
        print(f"- {disease}: {confidence*100:.1f}%")

5. Building a Simple Web Application

#| eval: false
#| echo: true
from flask import Flask, request, jsonify, render_template_string
from owlready2 import get_ontology, default_world
import pandas as pd

app = Flask(__name__)

# Initialize the ontology and diagnostic system
onto_path.append("path/to/your/ontology")
onto = get_ontology("plant-disease.owl").load()

# HTML template for the web interface
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
    <title>Plant Disease Diagnosis System</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
        <h1 class="mb-4">🌱 Plant Disease Diagnosis</h1>
        
        <div class="row">
            <div class="col-md-6">
                <div class="card">
                    <div class="card-body">
                        <h5 class="card-title">Enter Symptoms</h5>
                        <form id="diagnosisForm">
                            <div class="mb-3">
                                <label class="form-label">Select Symptoms:</label>
                                <select multiple class="form-select" id="symptoms" name="symptoms" size="8">
                                    {% for symptom in symptoms %}
                                    <option value="{{ symptom }}">{{ symptom }}</option>
                                    {% endfor %}
                                </select>
                            </div>
                            <div class="mb-3">
                                <label class="form-label">Temperature (Β°C)</label>
                                <input type="number" class="form-control" id="temperature" name="temperature" step="0.1">
                            </div>
                            <div class="mb-3">
                                <label class="form-label">Humidity (%)</label>
                                <input type="number" class="form-control" id="humidity" name="humidity" min="0" max="100">
                            </div>
                            <button type="submit" class="btn btn-primary">Diagnose</button>
                        </form>
                    </div>
                </div>
            </div>
            <div class="col-md-6">
                <div class="card">
                    <div class="card-body">
                        <h5 class="card-title">Diagnosis Results</h5>
                        <div id="results">
                            <p class="text-muted">Submit symptoms to see diagnosis results.</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="mt-4">
            <h4>Recent Diagnoses</h4>
            <table class="table table-striped" id="history">
                <thead>
                    <tr>
                        <th>Time</th>
                        <th>Symptoms</th>
                        <th>Diagnosis</th>
                        <th>Confidence</th>
                    </tr>
                </thead>
                <tbody></tbody>
            </table>
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function() {
            $('#diagnosisForm').on('submit', function(e) {
                e.preventDefault();
                
                const symptoms = $('#symptoms').val();
                const temperature = $('#temperature').val();
                const humidity = $('#humidity').val();
                
                $.ajax({
                    url: '/diagnose',
                    method: 'POST',
                    contentType: 'application/json',
                    data: JSON.stringify({
                        symptoms: symptoms,
                        environment: {
                            temperature: parseFloat(temperature),
                            humidity: parseFloat(humidity)
                        }
                    }),
                    success: function(response) {
                        let resultsHtml = '';
                        if (response.diagnoses && response.diagnoses.length > 0) {
                            resultsHtml = '<div class="list-group">';
                            response.diagnoses.forEach(diag => {
                                resultsHtml += `
                                    <div class="list-group-item">
                                        <div class="d-flex w-100 justify-content-between">
                                            <h6 class="mb-1">${diag.disease}</h6>
                                            <span class="badge bg-${diag.confidence > 70 ? 'success' : diag.confidence > 40 ? 'warning' : 'danger'} rounded-pill">
                                                ${diag.confidence}%
                                            </span>
                                        </div>
                                        <p class="mb-1">${diag.description || 'No description available'}</p>
                                        <small>Recommended treatments: ${diag.treatments.join(', ') || 'None specified'}</small>
                                    </div>`;
                            });
                            resultsHtml += '</div>';
                            
                            // Add to history
                            const now = new Date().toLocaleTimeString();
                            $('#history tbody').prepend(`
                                <tr>
                                    <td>${now}</td>
                                    <td>${symptoms.join(', ')}</td>
                                    <td>${response.diagnoses[0].disease}</td>
                                    <td>${response.diagnoses[0].confidence}%</td>
                                </tr>
                            `);
                        } else {
                            resultsHtml = '<div class="alert alert-warning">No matching diseases found for these symptoms.</div>';
                        }
                        $('#results').html(resultsHtml);
                    },
                    error: function() {
                        $('#results').html('<div class="alert alert-danger">Error processing your request. Please try again.</div>');
                    }
                });
            });
        });
    </script>
</body>
</html>
"""

# Get all symptoms for the dropdown
symptoms = sorted(list(set(
    symptom.name for disease in onto.Disease.instances() 
    if hasattr(disease, 'hasSymptom') and disease.hasSymptom
    for symptom in disease.hasSymptom
)))

@app.route('/')
def index():
    return render_template_string(HTML_TEMPLATE, symptoms=symptoms)

@app.route('/diagnose', methods=['POST'])
def diagnose():
    data = request.get_json()
    symptoms = data.get('symptoms', [])
    environment = data.get('environment', {})
    
    # Simple diagnosis logic (extend with your actual diagnosis logic)
    diagnoses = []
    for disease in onto.Disease.instances():
        if hasattr(disease, 'hasSymptom'):
            disease_symptoms = [s.name for s in disease.hasSymptom]
            matching_symptoms = set(symptoms) & set(disease_symptoms)
            if matching_symptoms:
                confidence = (len(matching_symptoms) / len(symptoms)) * 100
                
                # Get treatment information if available
                treatments = []
                if hasattr(disease, 'treatedWith'):
                    treatments = [t.name for t in disease.treatedWith]
                
                # Get description if available
                description = ""
                if hasattr(disease, 'comment') and disease.comment:
                    description = disease.comment[0] if isinstance(disease.comment, list) else str(disease.comment)
                
                diagnoses.append({
                    'disease': disease.name,
                    'confidence': round(confidence, 2),
                    'treatments': treatments,
                    'description': description
                })
    
    # Sort by confidence (highest first)
    diagnoses.sort(key=lambda x: x['confidence'], reverse=True)
    
    return jsonify({
        'diagnoses': diagnoses
    })

if __name__ == '__main__':
    app.run(debug=True)

5.1 Extending the Ontology with New Disease Cases

Adding Fusarium Wheat Blast to the Ontology

Let’s walk through extending our plant disease ontology to include Fusarium Wheat Blast, a serious fungal disease affecting wheat crops. This example demonstrates how to integrate a new disease with its symptoms, treatments, and relationships.

#| eval: true
#| echo: true
from owlready2 import *
import datetime

# Load the existing ontology
onto = get_ontology("plant-disease.owl").load()

with onto:
    # Define the pathogen if it doesn't exist
    if not onto.search_one(iri="*#Magnaporthe_oryzae"):
        # Create the pathogen
        magnaporthe = onto.Fungus("Magnaporthe_oryzae")
        magnaporthe.scientificName = "Magnaporthe oryzae"
        magnaporthe.commonName = ["Rice blast fungus", "Wheat blast fungus"]
        magnaporthe.description = "A plant-pathogenic fungus that causes rice blast and wheat blast diseases."
    else:
        magnaporthe = onto.search_one(iri="*#Magnaporthe_oryzae")

    # Create the disease
    wheat_blast = onto.Disease("Fusarium_Wheat_Blast")
    wheat_blast.label = ["Wheat Blast", "Wheat Blast Disease"]
    wheat_blast.scientificName = "Pyricularia oryzae Triticum pathotype"
    wheat_blast.altName = ["Wheat Blast", "Brusone"]
    wheat_blast.description = "A devastating fungal disease of wheat caused by Magnaporthe oryzae Triticum pathotype."
    
    # Add disease symptoms
    symptoms = [
        ("Bleached_Spike", "Bleached spike symptoms with white to light gray lesions"),
        ("Elliptical_Lesions", "Elliptical or diamond-shaped lesions with gray centers"),
        ("Grain_Abortion", "Partial or complete grain abortion"),
        ("Leaf_Blast", "Lesions on leaves with dark borders")
    ]
    
    symptom_instances = []
    for symptom_name, desc in symptoms:
        symptom = onto.Symptom(symptom_name.replace(" ", "_"))
        symptom.label = [symptom_name]
        symptom.description = desc
        symptom_instances.append(symptom)
    
    wheat_blast.hasSymptom = symptom_instances
    
    # Add causal relationships
    wheat_blast.causedBy = [magnaporthe]
    
    # Add affected plant parts
    wheat_blast.affectsPlantPart = [
        onto.search_one(iri="*#Spike"),
        onto.search_one(iri="*#Leaf"),
        onto.search_one(iri="*#Grain")
    ]
    
    # Add environmental conditions that favor the disease
    favorable_conditions = onto.EnvironmentalCondition("Favorable_WheatBlast_Conditions")
    favorable_conditions.hasTemperature = [onto.Temperature(value=25, unit="Β°C")]  # Optimal around 25Β°C
    favorable_conditions.hasHumidity = [onto.Humidity(value=90, unit="%")]  # High humidity
    favorable_conditions.hasRainfall = [onto.Rainfall(value=10, unit="mm/day")]  # Rainy conditions
    
    wheat_blast.favoredBy = [favorable_conditions]
    
    # Add management practices
    management = onto.ManagementPractice("WheatBlast_Management")
    management.includesPractice = [
        "Use of resistant varieties",
        "Fungicide application at booting stage",
        "Crop rotation with non-host crops",
        "Destruction of infected crop residues"
    ]
    
    wheat_blast.managedBy = [management]
    
    # Add chemical controls
    fungicides = [
        ("Azoxystrobin", "QCL (Quinone outside inhibitor)", "Preventative and curative"),
        ("Tebuconazole", "DMI (Demethylation inhibitor)", "Protectant")
    ]
    
    for name, mode, action in fungicides:
        fungicide = onto.Fungicide(name.replace(" ", "_"))
        fungicide.modeOfAction = mode
        fungicide.actionType = action
        wheat_blast.treatedWith.append(fungicide)
    
    # Add geographic distribution
    distribution = onto.GeographicDistribution("WheatBlast_Distribution")
    distribution.occursIn = ["South America", "South Asia", "Africa"]
    distribution.firstReported = "1985"
    
    wheat_blast.hasDistribution = [distribution]
    
    # Add references
    reference = onto.ScientificReference("WheatBlast_Ref1")
    reference.title = "Wheat Blast: A New Fungal Inhabitant to Bangladesh Threatening World Wheat Production"
    reference.authors = ["Tofazzal Islam", "et al."]
    reference.year = "2016"
    reference.journal = "Plant Pathology Journal"
    
    wheat_blast.hasReference = [reference]

# Save the updated ontology
onto.save("plant-disease-updated.owl")

Validating the New Disease Entry

After adding the new disease, it’s important to validate the ontology for consistency and run reasoners to infer new knowledge.

#| eval: true
#| echo: true
from owlready2 import sync_reasoner

# Load the updated ontology
onto = get_ontology("plant-disease-updated.owl").load()

# Run the reasoner to infer new knowledge
with onto:
    print("Running reasoner...")
    sync_reasoner()
    
    # Check for inconsistencies
    inconsistent = list(default_world.inconsistent_classes())
    print(f"Inconsistent classes: {len(inconsistent)}")
    for cls in inconsistent:
        print(f" - {cls}")
    
    # Save the inferred ontology
    onto.save("plant-disease-inferred.owl")

# Query the new disease information
wheat_blast = onto.search_one(iri="*#Fusarium_Wheat_Blast")
print(f"\nDisease: {wheat_blast.label[0]}")
print(f"Pathogen: {wheat_blast.causedBy[0].label[0]}")
print("\nSymptoms:")
for symptom in wheat_blast.hasSymptom:
    print(f" - {symptom.label[0]}: {symptom.description}")

print("\nManagement Practices:")
for practice in wheat_blast.managedBy[0].includesPractice:
    print(f" - {practice}")

print("\nRecommended Fungicides:")
for fungicide in wheat_blast.treatedWith:
    print(f" - {fungicide.name} ({fungicide.modeOfAction}): {fungicide.actionType}")

Visualizing the New Disease in the Ontology

To better understand how the new disease fits into the existing ontology, we can generate a visualization of its relationships.

#| eval: true
#| echo: true
from owlready2 import get_ontology
import pydot
from IPython.display import Image, display

def visualize_ontology(ontology_path, focus_class, depth=2):
    """Generate a visualization of the ontology focusing on a specific class"""
    # Load the ontology
    onto = get_ontology(ontology_path).load()
    
    # Create a new directed graph
    graph = pydot.Dot(graph_type='digraph', rankdir='LR')
    
    # Add nodes and edges for the focus class and its relationships
    focus_node = pydot.Node(focus_class.name, style='filled', fillcolor='lightblue')
    graph.add_node(focus_node)
    
    # Get all properties of the focus class
    for prop in dir(onto[focus_class.name]):
        if not prop.startswith('_') and not prop in ['get_iri', 'is_a']:
            try:
                values = getattr(onto[focus_class.name], prop)
                if values:  # Only process if there are values
                    if not isinstance(values, list):
                        values = [values]
                    
                    for value in values:
                        # Skip empty values or literals
                        if value is None or isinstance(value, (str, int, float, bool)):
                            continue
                            
                        # Create a node for the value
                        if hasattr(value, 'name'):
                            value_node = pydot.Node(value.name, shape='box')
                            graph.add_node(value_node)
                            graph.add_edge(pydot.Edge(focus_node, value_node, label=prop))
            except:
                continue
    
    # Save and display the graph
    output_path = f"{focus_class.name.lower()}_ontology.png"
    graph.write_png(output_path)
    display(Image(filename=output_path))

# Visualize the Wheat Blast disease in the ontology
visualize_ontology("plant-disease-updated.owl", onto.Fusarium_Wheat_Blast)

Practical Exercise: Adding Another Disease

As an exercise, try adding a new disease to the ontology following the same pattern. For example, you could add β€œBanana Fusarium Wilt TR4” with the following characteristics:

  1. Pathogen: Fusarium odoratissimum (Fusarium oxysporum f. sp. cubense Tropical Race 4)
  2. Symptoms:
    • Yellowing of older leaves
    • Wilting and collapse of leaves
    • Reddish-brown vascular discoloration
  3. Affected Parts: Stem, vascular system
  4. Management:
    • Use of disease-free planting material
    • Soil disinfection
    • Quarantine measures
  5. Distribution: Southeast Asia, Africa, Middle East, Latin America

6. Advanced Features and Integrations

6.1 Real-time Sensor Data Integration

import requests
from datetime import datetime

class SensorDataIntegrator:
    def __init__(self, ontology):
        self.onto = ontology
        self.sensor_data = {}
    
    def fetch_environmental_data(self, location_id):
        """Fetch environmental data from IoT sensors or weather API"""
        # Example: Fetch from OpenWeatherMap API
        # In production, replace with actual sensor data or API calls
        try:
            # This is a mock implementation
            self.sensor_data = {
                'temperature': 24.5,  # in Β°C
                'humidity': 65.2,     # in %
                'soil_moisture': 42.8, # in %
                'light_intensity': 850, # in Β΅mol/mΒ²/s
                'timestamp': datetime.now().isoformat()
            }
            return True
        except Exception as e:
            print(f"Error fetching sensor data: {e}")
            return False
    
    def update_ontology_with_sensor_data(self):
        """Update the ontology with the latest sensor readings"""
        with self.onto:
            # Create or update environmental conditions in the ontology
            env = self.onto.Environment("current_environment")
            env.hasTemperature = [self.onto.Temperature(value=self.sensor_data['temperature'], unit="Β°C")]
            env.hasHumidity = [self.onto.Humidity(value=self.sensor_data['humidity'], unit="%")]
            env.hasSoilMoisture = [self.onto.SoilMoisture(value=self.sensor_data['soil_moisture'], unit="%")]
            env.timestamp = [self.sensor_data['timestamp']]
            
            # Sync changes
            sync_reasoner()
            return env

6.2 Batch Processing for Large Datasets

import pandas as pd
from concurrent.futures import ThreadPoolExecutor

class BatchProcessor:
    def __init__(self, ontology_path, batch_size=100):
        self.ontology = get_ontology(ontology_path).load()
        self.batch_size = batch_size
    
    def process_csv_batch(self, csv_path):
        """Process plant observation data from CSV in batches"""
        results = []
        
        # Read CSV in chunks
        for chunk in pd.read_csv(csv_path, chunksize=self.batch_size):
            chunk_results = self._process_chunk(chunk)
            results.extend(chunk_results)
            
            # Sync with ontology after each batch
            with self.ontology:
                sync_reasoner()
        
        return pd.DataFrame(results)
    
    def _process_chunk(self, chunk):
        """Process a single batch of data"""
        results = []
        
        for _, row in chunk.iterrows():
            # Process each row (example: create individuals in the ontology)
            plant_id = f"plant_{row['id']}"
            plant = self.ontology.Plant(plant_id)
            
            # Add properties from CSV
            if 'species' in row:
                plant.hasSpecies = [row['species']]
            if 'health_status' in row:
                plant.hasHealthStatus = [row['health_status']]
            
            results.append({
                'plant_id': plant_id,
                'status': 'processed',
                'timestamp': datetime.now().isoformat()
            })
        
        return results

7. Deployment and Scaling

7.1 Containerization with Docker

# Dockerfile for Plant Disease Diagnosis System
FROM python:3.9-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    python3-dev \
    && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Environment variables
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
ENV ONTOLOGY_PATH=/data/plant-disease.owl

# Create data directory
RUN mkdir -p /data

# Expose port
EXPOSE 5000

# Run the application
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

7.2 Kubernetes Deployment

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: plant-disease-diagnosis
  labels:
    app: plant-disease
spec:
  replicas: 3
  selector:
    matchLabels:
      app: plant-disease
  template:
    metadata:
      labels:
        app: plant-disease
    spec:
      containers:
      - name: app
        image: your-username/plant-disease-diagnosis:latest
        ports:
        - containerPort: 5000
        volumeMounts:
        - name: ontology-volume
          mountPath: /data
        env:
        - name: FLASK_ENV
          value: "production"
        - name: ONTOLOGY_PATH
          value: "/data/plant-disease.owl"
      volumes:
      - name: ontology-volume
        persistentVolumeClaim:
          claimName: ontology-pvc
---
# Service
apiVersion: v1
kind: Service
metadata:
  name: plant-disease-service
spec:
  selector:
    app: plant-disease
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5000
  type: LoadBalancer

8. Practical Exercises

Exercise 1: Implement a Caching Layer

from functools import lru_cache
from datetime import datetime, timedelta

class OntologyCache:
    def __init__(self, ontology_path, ttl_hours=24):
        self.ontology_path = ontology_path
        self.ttl = timedelta(hours=ttl_hours)
        self.last_loaded = None
        self._ontology = None
    
    @property
    def ontology(self):
        if self._should_reload():
            self._load_ontology()
        return self._ontology
    
    def _should_reload(self):
        if self._ontology is None or self.last_loaded is None:
            return True
        return datetime.now() - self.last_loaded > self.ttl
    
    def _load_ontology(self):
        print("Loading ontology...")
        self._ontology = get_ontology(self.ontology_path).load()
        self.last_loaded = datetime.now()
    
    @lru_cache(maxsize=128)
    def get_disease_by_name(self, disease_name):
        """Get disease by name with caching"""
        with self.ontology:
            disease = self.ontology.search_one(iri=f"*#{disease_name}")
            if disease:
                return {
                    'name': disease.name,
                    'symptoms': [s.name for s in disease.hasSymptom] if hasattr(disease, 'hasSymptom') else [],
                    'treatments': [t.name for t in disease.treatedWith] if hasattr(disease, 'treatedWith') else []
                }
        return None

Exercise 2: Implement a REST API with FastAPI

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import uvicorn

app = FastAPI(title="Plant Disease Diagnosis API")

class SymptomRequest(BaseModel):
    symptoms: List[str]
    environment: Optional[dict] = {}
    crop_type: Optional[str] = None

class DiseasePrediction(BaseModel):
    disease: str
    confidence: float
    treatments: List[str]
    description: Optional[str] = None

@app.post("/api/diagnose", response_model=List[DiseasePrediction])
async def diagnose_disease(request: SymptomRequest):
    """
    Diagnose plant disease based on symptoms and environmental conditions
    """
    try:
        # Initialize the diagnoser (from previous examples)
        diagnoser = PlantDiseaseDiagnoser("plant-disease.owl")
        
        # Get predictions
        predictions = diagnoser.diagnose(
            symptoms=request.symptoms,
            environment=request.environment
        )
        
        # Format response
        results = []
        for disease, confidence in predictions:
            treatments = [t.name for t in disease.treatedWith] if hasattr(disease, 'treatedWith') else []
            description = disease.comment[0] if hasattr(disease, 'comment') and disease.comment else None
            
            results.append(DiseasePrediction(
                disease=disease.name,
                confidence=confidence,
                treatments=treatments,
                description=description
            ))
        
        return results
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

9. Conclusion and Next Steps

Key Takeaways

  1. Flexible Integration: Python’s OWLReady2 library provides powerful tools for working with OWL ontologies
  2. Scalability: The system can be scaled using batch processing and caching strategies
  3. Deployment: Containerization and orchestration enable reliable production deployment
  4. Extensibility: The architecture supports integration with various data sources and ML models

10. Additional Resources

11. Troubleshooting Common Issues

Issue: Slow Query Performance

Solution: - Add appropriate indexes to your ontology - Use SPARQL queries with LIMIT and OFFSET for pagination - Implement caching for frequent queries

Issue: Memory Usage

Solution: - Process large ontologies in smaller chunks - Use sync_reasoner(infer_property_values=False) to reduce memory usage - Consider using a triple store for very large ontologies

Issue: Inconsistent Reasoning Results

Solution: - Check for inconsistent or conflicting axioms in your ontology - Verify that all necessary imports are properly resolved - Clear the reasoner’s cache and restart the application

12. Example Project Structure

plant-disease-diagnosis/
β”œβ”€β”€ app/                                 # Main application package
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ main.py                         # FastAPI application
β”‚   β”œβ”€β”€ models/                         # Pydantic models
β”‚   β”‚   └── schemas.py
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ ontology.py                # Ontology service
β”‚   β”‚   β”œβ”€β”€ diagnosis.py               # Diagnosis logic
β”‚   β”‚   └── ml_models.py               # ML model integration
β”‚   └── api/
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ endpoints.py               # API endpoints
β”‚       └── dependencies.py            # FastAPI dependencies
β”œβ”€β”€ data/
β”‚   └── plant-disease.owl              # Ontology file
β”œβ”€β”€ tests/                             # Unit and integration tests
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ test_services.py
β”‚   └── test_api.py
β”œβ”€β”€ docker-compose.yml                 # For local development
β”œβ”€β”€ Dockerfile                         # Production Dockerfile
β”œβ”€β”€ requirements.txt                   # Python dependencies
└── README.md                          # Project documentation

This completes our comprehensive guide to Python integration with plant disease ontologies. The provided examples and patterns should give you a solid foundation for building robust, scalable applications in this domain. results = diagnoser.diagnose(symptoms, environment)

# Convert results to JSON-serializable format
response = [{
    'disease': disease.name,
    'confidence': confidence,
    'treatments': [t.name for t in disease.treatedWith] if disease.treatedWith else []
} for disease, confidence in results]

return jsonify(response)

if name == β€˜main’: app.run(debug=True)


## 6. Next Steps

1. **Deploy to Production**
   - Containerize with Docker
   - Set up a GraphDB instance
   - Create a REST API for ontology access

2. **Enhance the System**
   - Add image recognition for symptom detection
   - Integrate weather data for better predictions
   - Implement a feedback loop to improve the ontology

3. **Explore Advanced Features**
   - Implement a Mixture of Experts (MOE) architecture
   - Add support for multilingual symptom descriptions
   - Create a mobile app for field diagnosis

## Complete Project Structure

plant-disease-diagnosis/ β”œβ”€β”€ data/ β”‚ β”œβ”€β”€ plant-disease.owl β”‚ └── training_data.csv β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ init.py β”‚ β”œβ”€β”€ diagnoser.py β”‚ β”œβ”€β”€ ontology_utils.py β”‚ └── ml_model.py β”œβ”€β”€ tests/ β”‚ └── test_diagnoser.py β”œβ”€β”€ requirements.txt └── README.md ```

Additional Resources