Compare Python SDK Package Versions Side by Side: Build a Visual Benchmark Tool with PySide6 and OpenCV

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.

What you’ll build: A desktop GUI application in Python that runs two different SDK versions in isolated subprocesses, processes the same images through each version side by side, and overlays barcode detection results for direct visual comparison.

Key Takeaways

  • You can benchmark any pip-installable package across versions by running each version in its own venv subprocess — no conflicts, no reinstalling.
  • Dynamsoft Capture Vision SDK’s CaptureVisionRouter returns barcode location points that can be rendered directly onto OpenCV frames for a visual diff.
  • PySide6’s QImage provides a low-overhead bridge between OpenCV Mat arrays and Qt display widgets, keeping the GUI responsive during batch processing.
  • This subprocess isolation pattern is broadly applicable: swap in any Python callable to get a reproducible A/B benchmark framework.

Common Developer Questions

  • How do I compare two pip package versions without uninstalling and reinstalling?
  • How do I run different Python package versions simultaneously in the same script?
  • How do I draw barcode detection bounding boxes on an image using OpenCV in Python?

Demo: Python SDK Version Comparison Tool

Prerequisites

Why a Side-by-Side SDK Version Benchmark Matters

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

Step 1: Set Up Isolated Python Virtual Environments

Create Separate Environments for Each SDK Version

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
    

Install Required Dependencies

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              

Step 2: Detect the Installed SDK Version in Each Virtual Environment

Auto-Detect SDK Versions Across Environments

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

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

Configure Multiple SDK Environments via 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))

Step 3: Run SDK Detection in Isolated Subprocesses

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"}}
    
    # Process image
    cvr = CaptureVisionRouter()
    result = cvr.capture(image_path, EnumPresetTemplate.PT_READ_BARCODES.value)
    
    if result.get_error_code() != EnumErrorCode.EC_OK:
        return success"}}
    
    # Extract barcodes
    barcodes = []
    items = result.get_items()
    for item in items:
        if item.get_type() == 2:  # Barcode item
            barcode_data = text
            
            # 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

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)
        )

Step 4: Visualize Barcode Detection Results with OpenCV Overlays

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)}

Python SDK Comparison Tool

Common Issues & Edge Cases

  • Subprocess timeout on large images: The default timeout=30 in subprocess.run() may expire for high-resolution images or slow machines. Increase the timeout or pre-scale images to a maximum dimension before processing.
  • SDK version not detected after venv creation: SDKVersionDetector scans a fixed list of paths. If your virtual environment is in a non-standard location, use the Browse button in the configuration dialog to add the python.exe path manually.
  • QImage overlay appears offset on HiDPI screens: Ensure you pass the correct bytes_per_line value explicitly rather than relying on defaults, and verify you are not mixing BGR and RGB channel orders before creating the QImage.

Source Code

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