ITC Scanner

A Flutter plugin for extracting data from Ghana vehicle documents using ML Kit text recognition and advanced AI processing.

Features

  • 🚗 Extract data from Ghana driver and vehicle licensing authority documents (Road Worthy)
  • 📋 Extract data from Ghana vehicle registration forms (Registration Particulars)
  • 📱 Cross-platform support (Android/iOS)
  • 🔧 Simple integration with image bytes input
  • 📊 Structured output with confidence scores
  • Direct key-value access for easy third-party integration
  • 🛡️ Robust error handling for production use

Screenshots

iOS - Document Scanning iOS - Extraction Results
iOS Scanning iOS Results
iOS - App Interface Android - Full Experience
iOS Interface Android

Cross-platform support - works on both iOS and Android

Installation

dependencies:
  itc_scanner: ^0.0.1
flutter pub get

Usage

Basic Usage

import 'package:itc_scanner/itc_scanner.dart';
import 'dart:typed_data';

final scanner = ItcScanner();

// Extract Registration Particulars (uses advanced KIT processing)
Future<void> scanRegistrationForm(Uint8List imageBytes) async {
  try {
    final result = await scanner.extractDocument(
      imageBytes, 
      extractionKey: 'itc_reg_form'
    );
    
    if (result?['success'] == true) {
      // Easy direct access
      final regNumber = result!['data']['reg_number'];
      final make = result['data']['make'];
      final owner = result['data']['owner'];
      
      print('Registration: $regNumber');
      print('Make: $make');
      print('Owner: $owner');
    }
  } catch (e) {
    print('Error: $e');
  }
}

// Extract Driver Vehicle Licenses (Road Worthy) (uses local Kit)
Future<void> scanRoadWorthy(Uint8List imageBytes) async {
  try {
    final result = await scanner.extractDocument(imageBytes);
    
    if (result?['success'] == true) {
      // Easy direct access
      final regNumber = result!['data']['reg_number'];
      final make = result['data']['make'];
      final documentId = result['data']['document_id'];
      
      print('Registration: $regNumber');
      print('Make: $make');
      print('Document ID: $documentId');
    }
  } catch (e) {
    print('Error: $e');
  }
}

Complete Example with Image Picker

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:itc_scanner/itc_scanner.dart';
import 'dart:typed_data';
import 'dart:io';

class DocumentScannerWidget extends StatefulWidget {
  @override
  _DocumentScannerWidgetState createState() => _DocumentScannerWidgetState();
}

class _DocumentScannerWidgetState extends State<DocumentScannerWidget> {
  final _scanner = ItcScanner();
  final _picker = ImagePicker();
  Map<String, dynamic>? _result;
  bool _isLoading = false;

  Future<void> _scanRegistrationForm() async {
    final XFile? image = await _picker.pickImage(
      source: ImageSource.camera,
      maxWidth: 1024,
      maxHeight: 1024,
      imageQuality: 90,
    );
    
    if (image != null) {
      await _processImage(File(image.path), isRegistration: true);
    }
  }

  Future<void> _scanRoadWorthy() async {
    final XFile? image = await _picker.pickImage(
      source: ImageSource.camera,
      maxWidth: 1024,
      maxHeight: 1024,
      imageQuality: 90,
    );
    
    if (image != null) {
      await _processImage(File(image.path), isRegistration: false);
    }
  }

  Future<void> _processImage(File imageFile, {required bool isRegistration}) async {
    setState(() => _isLoading = true);
    
    try {
      final Uint8List imageBytes = await imageFile.readAsBytes();
      final result = await _scanner.extractDocument(
        imageBytes,
        extractionKey: isRegistration ? 'itc_reg_form' : null,
      );
      
      setState(() => _result = result);
      
      // Direct data access
      if (result?['success'] == true) {
        final data = result!['data'] as Map<String, dynamic>;
        print('Registration Number: ${data['reg_number']}');
        print('Make: ${data['make']}');
        
        if (isRegistration) {
          print('Owner: ${data['owner']}');
          print('Color: ${data['colour']}');
        } else {
          print('Document ID: ${data['document_id']}');
          print('Expiry: ${data['expiry_date']}');
        }
      }
    } catch (e) {
      print('Error: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: _isLoading ? null : _scanRegistrationForm,
          child: Text('Scan Registration Form'),
        ),
        ElevatedButton(
          onPressed: _isLoading ? null : _scanRoadWorthy,
          child: Text('Scan Road Worthy'),
        ),
        if (_result != null) ...[
          Text('Success: ${_result!['success']}'),
          Text('Processing Time: ${_result!['processingTime']}ms'),
          // Access data directly
          if (_result!['data'] != null)
            ...(_result!['data'] as Map<String, dynamic>).entries.map(
              (entry) => Text('${entry.key}: ${entry.value}'),
            ),
        ],
      ],
    );
  }
}

Response Format

The plugin returns both detailed field information and direct key-value access:

{
  "success": true,
  "documentType": "VEHICLE_REGISTRATION",
  "processingTime": 1250,
  "fields": [
    {
      "label": "Registration Number",
      "value": "GE4019-23",
      "confidence": 0.95,
      "fieldType": "ID_NUMBER"
    }
  ],
  "data": {
    "reg_number": "GE4019-23",
    "make": "NISSAN",
    "colour": "SILVER",
    "owner": "STEPHANIE OTOO DANKWA"
  }
}

Direct Access Keys

Registration Particulars (extractionKey: 'itc_reg_form')

Perfect for vehicle registration forms with comprehensive vehicle and owner information:

final data = result['data'];
final regNumber = data['reg_number'];
final owner = data['owner'];
final postalAddress = data['postal_address'];
final resAddress = data['res_address'];
final make = data['make'];
final colour = data['colour'];
final model = data['model'];
final type = data['type'];
final chassisNumber = data['chassis_number'];
final countryOfOrigin = data['country_of_origin'];
final yearOfManufacturing = data['year_of_manufacturing'];
final numberOfAxles = data['number_of_axles'];
final numberOfWheels = data['number_of_wheels'];
final numberOfPersons = data['number_of_persons'];
final engineMake = data['engine_make'];
final engineCylinders = data['engine_number_of_clys'];
final engineCC = data['engine_cc'];
final horsepower = data['hp'];
final fuelType = data['fuel'];
final useType = data['use_private_or_commercial'];
final dateOfEntry = data['date_of_entry'];

Road Worthy Documents (no extractionKey)

Perfect for driver and vehicle licensing authority documents:

final data = result['data'];
final regNumber = data['reg_number'];
final documentId = data['document_id'];
final make = data['make'];
final model = data['model'];
final colour = data['colour'];
final use = data['use'];
final expiryDate = data['expiry_date'];

Error Handling

The plugin provides comprehensive error handling:

try {
  final result = await scanner.extractDocument(imageBytes);
  
  if (result?['success'] == true) {
    // Process successful extraction
    final data = result!['data'] as Map<String, dynamic>;
    // All keys are guaranteed to exist (may be empty strings)
  } else {
    // Handle extraction failure
    print('Extraction failed');
  }
} on Exception catch (e) {
  // Handle plugin errors
  if (e.toString().contains('INVALID_IMAGE')) {
    print('Invalid image format');
  } else if (e.toString().contains('EXTRACTION_FAILED')) {
    print('Could not extract document data');
  } else {
    print('Unexpected error: $e');
  }
}

Requirements

  • Android: API 24+
  • iOS: 15.5+
  • Flutter: 3.3.0+

Field Types

  • TEXT: General text (Make, Model, Color)
  • ID_NUMBER: Document identifiers
  • DATE: Date fields
  • NUMBER: Numeric values

Tips for Best Results

  • Use good lighting when capturing documents
  • Keep document flat and properly aligned
  • Optimize image size: Use maxWidth: 1024, maxHeight: 1024, imageQuality: 90 for better performance
  • Recommended image size: 1024x1024 or higher (but not too large)
  • Smaller images = faster processing and better app performance
  • Choose the correct document type for optimal results

Integration Notes

  • Empty Fields: All data keys are always present, returning empty strings for missing information
  • Cross-Platform: Identical response format on Android and iOS
  • Production Ready: Robust error handling for real-world usage
  • No Internet Required: Road Worthy extraction works completely offline

Support

ITC Consortium Ghana
Email: apps@itconsortiumgh.com

License

Proprietary - Internal use only