DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

Building a Visual SDK Comparison Tool: Evaluating Python Package Performance Across Versions

In the rapidly evolving world of software development, SDK providers frequently release new versions with performance improvements, bug fixes, and enhanced features. However, developers often struggle to quantify the actual benefits of upgrading to newer versions. In this article, we'll take Dynamsoft Capture Vision SDK as an example to explore how to build a comprehensive visual comparison tool for evaluating performance differences between SDK versions using Python, PySide6, OpenCV.

Demo: Python SDK Version Comparison Tool

Prerequisites

Why We Need SDK Version Comparison Tools

When working with computer vision SDKs like Dynamsoft's Capture Vision Bundle for barcode detection, performance improvements can significantly impact application efficiency. A new SDK version might detect more barcodes, process images faster, or provide better accuracy. However, without proper testing infrastructure, these improvements remain theoretical. A visual comparison tool addresses this challenge by:

  • Quantifying Performance Gains: Providing measurable metrics on detection accuracy and processing speed
  • Visual Validation: Offering side-by-side image comparisons with overlay visualizations
  • Batch Testing: Enabling comprehensive testing across multiple images and scenarios
  • Decision Support: Helping developers make informed upgrade decisions based on concrete data

Setting Up the Development Environment

Virtual Environment Configuration

The foundation of our comparison tool lies in proper environment isolation. Different SDK versions require separate Python environments to avoid conflicts. For example, to test Dynamsoft Capture Vision SDK versions 3.0.4100 and 3.0.6000, we can set up the environments as follows:

  1. Create a dedicated Python environment for each SDK version:

    # Create dedicated environments for each SDK version python -m venv D:/envs/sdk_v1 python -m venv D:/envs/sdk_v2 
  2. Activate the environments and install the required SDK versions:

    D:/envs/sdk_v1/Scripts/activate pip install dynamsoft-capture-vision-bundle==3.0.4100 D:/envs/sdk_v2/Scripts/activate pip install dynamsoft-capture-vision-bundle==3.0.6000 

Dependency Management

The tool requires several key dependencies for different functionalities:

PySide6>=6.5.0 opencv-python>=4.8.0 numpy>=1.24.0 Pillow>=10.0.0 
Enter fullscreen mode Exit fullscreen mode

Core Implementation: SDK Detection and Management

Automatic SDK Version Detection

One of the tool's key features is automatic SDK version detection from virtual environments:

class SDKVersionDetector: """Utility class to detect SDK versions in virtual environments""" @staticmethod def detect_sdk_version(python_path: str) -> Optional[str]: """Detect the Dynamsoft Capture Vision version in a given Python environment""" try: # Create a script to check the SDK version  version_script = ''' import sys try: import dynamsoft_capture_vision_bundle # Try to get version from package metadata try: import pkg_resources version = pkg_resources.get_distribution("dynamsoft-capture-vision-bundle").version print(f"VERSION:{version}") except: # Fallback: try to get from module attributes if hasattr(dynamsoft_capture_vision_bundle, '__version__'): print(f"VERSION:{dynamsoft_capture_vision_bundle.__version__}") else: # Try importing a module and checking its attributes from dynamsoft_capture_vision_bundle import CaptureVisionRouter if hasattr(CaptureVisionRouter, 'get_version'): print(f"VERSION:{CaptureVisionRouter.get_version()}") else: print("VERSION:unknown") except ImportError as e: print(f"ERROR:SDK not installed - {e}") except Exception as e: print(f"ERROR:Failed to detect version - {e}") ''' result = subprocess.run([ python_path, '-c', version_script ], capture_output=True, text=True, timeout=30) if result.returncode == 0: output = result.stdout.strip() if output.startswith("VERSION:"): version = output[8:].strip() return version if version != "unknown" else None elif output.startswith("ERROR:"): print(f"SDK detection error: {output[6:]}") return None else: print(f"Failed to detect SDK version: {result.stderr}") return None except Exception: pass return None 
Enter fullscreen mode Exit fullscreen mode

This detection mechanism ensures that the tool can automatically identify SDK versions across different virtual environments, simplifying the configuration process for users.

Dynamic Configuration Dialog

The tool provides an intuitive configuration interface for managing multiple SDK environments:

class SDKConfigDialog(QDialog): """Dialog for configuring SDK virtual environments""" def auto_detect_environments(self): virtual_env_paths = [ "D:/envs/sdk_v1/Scripts/python.exe", "D:/envs/sdk_v2/Scripts/python.exe", "C:/envs/sdk_v1/Scripts/python.exe", "C:/envs/sdk_v2/Scripts/python.exe", os.path.expanduser("~/envs/sdk_v1/Scripts/python.exe"), os.path.expanduser("~/envs/sdk_v2/Scripts/python.exe") ] found_envs = [] for path in virtual_env_paths: if SDKVersionDetector.validate_python_path(path): version = SDKVersionDetector.detect_sdk_version(path) if version: exists = False for row in range(self.config_table.rowCount()): if self.config_table.item(row, 1).text() == path: exists = True break if not exists: env_name = f"SDK v{version}" self.add_environment_to_table(env_name, path) found_envs.append((env_name, version)) 
Enter fullscreen mode Exit fullscreen mode

Subprocess-Based SDK Execution

A Python environment can only install one version of a package at a time. To compare multiple SDK versions, we need to run detection scripts in isolated subprocesses that utilize different Python virtual environments with the respective SDK versions installed.

def process_single_image(self, image_path: str, sdk_version: SDKVersion) -> ProcessingResult: try: temp_dir = Path(tempfile.mkdtemp()) script_path = temp_dir / f"processor_{sdk_version.version.replace('.', '_')}.py" script_content = f'''#!/usr/bin/env python3 import sys import json import time import os from dynamsoft_capture_vision_bundle import * LICENSE_KEY = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==" def process_image(image_path): start_time = time.time() # Initialize license error_code, error_message = LicenseManager.init_license(LICENSE_KEY) if error_code not in [EnumErrorCode.EC_OK, EnumErrorCode.EC_LICENSE_CACHE_USED]: return {{"success": False, "error": f"License error: {{error_message}}"}} # Process image cvr = CaptureVisionRouter() result = cvr.capture(image_path, EnumPresetTemplate.PT_READ_BARCODES.value) if result.get_error_code() != EnumErrorCode.EC_OK: return {{"success": False, "error": f"Capture error: {{result.get_error_code()}}"}} # Extract barcodes barcodes = [] items = result.get_items() for item in items: if item.get_type() == 2: # Barcode item barcode_data = {{ "text": item.get_text(), "format": item.get_format_string(), "confidence": getattr(item, 'get_confidence', lambda: 0.0)() }} # Get location points try: location = item.get_location() if location and hasattr(location, 'points'): barcode_data["points"] = [ [int(location.points[0].x), int(location.points[0].y)], [int(location.points[1].x), int(location.points[1].y)], [int(location.points[2].x), int(location.points[2].y)], [int(location.points[3].x), int(location.points[3].y)] ] else: barcode_data["points"] = [] except: barcode_data["points"] = [] barcodes.append(barcode_data) return {{ "success": True, "processing_time": time.time() - start_time, "barcodes": barcodes }} if __name__ == "__main__": result = process_image(sys.argv[1]) print(json.dumps(result)) ''' with open(script_path, 'w', encoding='utf-8') as f: f.write(script_content) result = subprocess.run([ sdk_version.python_path, str(script_path), image_path ], capture_output=True, text=True, timeout=30) if result.returncode == 0: data = json.loads(result.stdout) if data["success"]: barcodes = [ BarcodeResult( text=b["text"], format=b["format"], confidence=b["confidence"], points=b["points"] ) for b in data["barcodes"] ] return ProcessingResult( success=True, sdk_version=sdk_version.version, processing_time=data["processing_time"], barcodes=barcodes ) except Exception as e: return ProcessingResult( success=False, sdk_version=sdk_version.name, processing_time=0.0, barcodes=[], error=str(e) ) 
Enter fullscreen mode Exit fullscreen mode

Image Processing and Overlay Visualization

A critical feature of the comparison tool is the ability to visualize barcode detection results directly on images. We draw barcode bounding boxes and labels onto Mat, and then convert Mat data to QPixmap for display in the GUI.

def load_image_and_draw_overlays(image_path: str, results_dict: Optional[Dict[str, 'ProcessingResult']] = None) -> Dict[str, QPixmap]: try: img = cv2.imread(image_path) if img is None: return {"image": QPixmap(image_path)} img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) pixmaps = {} if results_dict: for sdk_name, result in results_dict.items(): img_copy = img_rgb.copy() if result.success and result.barcodes: color = (255, 0, 150) for i, barcode in enumerate(result.barcodes): if barcode.points and len(barcode.points) >= 4: points = np.array(barcode.points, dtype=np.int32) cv2.polylines(img_copy, [points], True, color, 2) overlay = img_copy.copy() cv2.fillPoly(overlay, [points], color) cv2.addWeighted(overlay, 0.2, img_copy, 0.8, 0, img_copy) if points.size > 0: text_pos = (int(points[0][0]), int(points[0][1]) - 10) text = f"{i+1}: {barcode.text[:20]}" cv2.putText(img_copy, text, text_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1, cv2.LINE_AA) height, width, channel = img_copy.shape bytes_per_line = 3 * width q_img = QImage(img_copy.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) pixmaps[sdk_name] = QPixmap.fromImage(q_img) else: height, width, channel = img_rgb.shape bytes_per_line = 3 * width q_img = QImage(img_rgb.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) pixmaps["image"] = QPixmap.fromImage(q_img) return pixmaps except Exception as e: print(f"Error loading image with OpenCV: {e}") return {"image": QPixmap(image_path)} 
Enter fullscreen mode Exit fullscreen mode

Python SDK Comparison Tool

Source Code

https://github.com/yushulx/python-barcode-qrcode-sdk/tree/main/examples/official/comparison_tool

Top comments (0)