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 - App Interface | Android - Full Experience |
---|---|
![]() |
![]() |
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 identifiersDATE
: Date fieldsNUMBER
: 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