Adding a New Interface ====================== This guide provides step-by-step instructions for creating a new user interface for Granny. Interface modules provide different ways for users to interact with Granny's analysis capabilities. Overview -------- An interface module in Granny: - Inherits from the ``GrannyUI`` abstract base class - Integrates with Python's ``argparse`` for argument handling - Dynamically discovers and exposes all available analyses - Automatically generates UI elements based on analysis parameters - Manages the execution of analyses with user-provided values Existing interfaces include: - **GrannyCLI** - Command-line interface for batch processing - **GrannyPyQt** - Graphical desktop interface (PyQt5) - **Mobile** - Mobile interface (placeholder for future development) Use Cases for New Interfaces ----------------------------- You might want to create a new interface for: - Web-based interface (Flask/Django) - REST API for programmatic access - Jupyter notebook integration - Desktop GUI with different framework (Tkinter, wxPython) - Mobile applications (iOS/Android) - Voice-controlled interface - Batch processing scripts with configuration files The Interface Architecture --------------------------- Interfaces in Granny follow a specific workflow: 1. **Argument Setup Phase:** - Interface is instantiated with an ``ArgumentParser`` - Interface adds its own specific arguments via ``addProgramArgs()`` - Interface discovers available analyses - Interface adds analysis-specific arguments to the parser 2. **Execution Phase:** - The ``run()`` method is called - Interface parses command-line arguments (or gets user input) - Interface instantiates the selected analysis - Interface sets analysis parameters from user input - Interface calls ``analysis.performAnalysis()`` - Interface handles the results Step-by-Step Guide ------------------- Step 1: Create Your Interface File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create a new Python file in ``Granny/Interfaces/UI/`` (or appropriate subdirectory): .. code-block:: bash touch Granny/Interfaces/UI/GrannyWeb.py Step 2: Import Required Modules ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Start your file with necessary imports: .. code-block:: python """ Web-based interface for Granny using Flask. Author: Your Name Date: YYYY-MM-DD """ import argparse from typing import Optional from Granny.Analyses.Analysis import Analysis from Granny.Interfaces.UI.GrannyUI import GrannyUI # Import all available analyses from Granny.Analyses.Segmentation import Segmentation from Granny.Analyses.BlushColor import BlushColor from Granny.Analyses.PeelColor import PeelColor from Granny.Analyses.StarchArea import StarchArea from Granny.Analyses.SuperficialScald import SuperficialScald Step 3: Define Your Interface Class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create your class inheriting from ``GrannyUI``: .. code-block:: python class GrannyWeb(GrannyUI): """ Web-based interface for Granny analysis. This interface provides a web server that allows users to upload images and run analyses through a browser. Attributes: analysis_name (str): Name of the selected analysis port (int): Port number for the web server """ def __init__(self, parser: argparse.ArgumentParser): """ Initialize the web interface. Args: parser: ArgumentParser instance for handling command-line arguments """ super().__init__(parser) self.analysis_name: str = "" self.port: int = 5000 Step 4: Implement addProgramArgs() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This method adds interface-specific arguments to the argument parser: .. code-block:: python def addProgramArgs(self) -> None: """ Add web interface specific arguments to the argument parser. This method is called during initialization to set up all command-line arguments that this interface needs. """ # Add interface-specific arguments iface_grp = self.parser.add_argument_group("Web interface args") iface_grp.add_argument( "--port", dest="port", type=int, default=5000, help="Port number for the web server (default: 5000)", ) iface_grp.add_argument( "--host", dest="host", type=str, default="127.0.0.1", help="Host address for the web server (default: 127.0.0.1)", ) iface_grp.add_argument( "--debug", dest="debug", action="store_true", help="Run server in debug mode", ) **Note:** Unlike the CLI interface, a web interface might not need to specify analysis types in command-line arguments, as users will select analyses through the web UI. However, you may want to add arguments for server configuration. Step 5: Implement run() ~~~~~~~~~~~~~~~~~~~~~~~ This is the main entry point for your interface: .. code-block:: python def run(self): """ Start the web interface. This method launches the Flask web server and handles user interactions through HTTP requests. """ # Parse command-line arguments args, _ = self.parser.parse_known_args() # Import Flask here to avoid dependency if not using this interface try: from flask import Flask, render_template, request, jsonify except ImportError: print("Error: Flask is not installed.") print("Install it with: pip install flask") return # Create Flask app app = Flask(__name__) # Store reference to self for route handlers interface = self @app.route('/') def index(): """Serve the main page.""" # Get list of available analyses analyses = Analysis.__subclasses__() analysis_info = [ { "name": cls.__analysis_name__, "class": cls.__name__ } for cls in analyses ] return render_template('index.html', analyses=analysis_info) @app.route('/run_analysis', methods=['POST']) def run_analysis(): """Handle analysis execution requests.""" try: # Get analysis name from request analysis_name = request.form.get('analysis') if not analysis_name: return jsonify({"error": "No analysis specified"}), 400 # Find and instantiate the analysis analysis = interface._get_analysis(analysis_name) if not analysis: return jsonify({"error": f"Unknown analysis: {analysis_name}"}), 400 # Set parameters from form data interface._set_analysis_params(analysis, request.form) # Run the analysis results = analysis.performAnalysis() return jsonify({ "success": True, "message": f"Processed {len(results)} images", "results": [img.getFileName() for img in results] }) except Exception as e: return jsonify({"error": str(e)}), 500 # Start the server print(f"Starting Granny web interface on {args.host}:{args.port}") print(f"Open your browser to: http://{args.host}:{args.port}") app.run(host=args.host, port=args.port, debug=args.debug) def _get_analysis(self, analysis_name: str) -> Optional[Analysis]: """ Get an analysis instance by name. Args: analysis_name: Machine-readable name of the analysis Returns: Analysis instance or None if not found """ analyses = Analysis.__subclasses__() for cls in analyses: if cls.__analysis_name__ == analysis_name: return cls() return None def _set_analysis_params(self, analysis: Analysis, form_data: dict) -> None: """ Set analysis parameters from form data. Args: analysis: Analysis instance to configure form_data: Dictionary of form parameters """ params = analysis.getInParams() analysis.resetInParams() for param in params.values(): # Get value from form data form_key = param.getLabel() if form_key in form_data: value = form_data[form_key] # Convert to appropriate type param_type = param.getType() if param_type == int: value = int(value) elif param_type == float: value = float(value) param.setValue(value) print(f" {param.getLabel()}: {value}") else: # Use default value print(f" {param.getLabel()}: (default) {param.getValue()}") analysis.addInParam(param) Step 6: Register Your Interface ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Edit ``Granny/GrannyBase.py`` to register your new interface: .. code-block:: python # At the top, import your interface from Granny.Interfaces.UI.GrannyWeb import GrannyWeb # In the run() function, add your interface to the choices parser.add_argument( "-i", dest="interface", type=str, required=True, choices=["cli", "gui", "web"], # Add "web" here help="Indicates the user interface to use.", ) # Add elif branch for your interface elif interface == "web": iface = GrannyWeb(parser) Step 7: Create Required Assets (if applicable) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For a web interface, you'll need HTML templates: Create ``templates/index.html``: .. code-block:: html