ImSwitch Jupyter Integration with extipy
This document provides comprehensive documentation for integrating Jupyter notebooks with ImSwitch using the extipy package. This integration allows you to connect Jupyter Lab/Notebook to a running ImSwitch instance for live hardware control, data analysis, and debugging.
⚠️ Important Warning: When using this integration, the ImSwitch application cannot be stopped with ordinary means (Ctrl+C) because the embedded kernel takes control of the main thread. Use this feature carefully in production environments.
Quick Start
# 1. Install extipy
pip install git+https://github.com/ebanner/extipy.git
# 2. Start ImSwitch with embedded kernel
python -m imswitch --with-kernel
# 3. In a separate terminal, start Jupyter Lab with extipy provisioner
jupyter lab --KernelProvisionerFactory.default_provisioner_name=extipy-provisioner
# 4. Create a new notebook and connect to the ImSwitch kernel
Installation
Install extipy
# From GitHub (recommended)
pip install git+https://github.com/ebanner/extipy.git
# Or from source
git clone https://github.com/ebanner/extipy.git
cd extipy
pip install -e .
Command Line Arguments
ImSwitch supports the following command-line arguments for Jupyter integration:
| Flag | Description | Default | 
|---|---|---|
| --with-kernel | Start with embedded Jupyter kernel | False | 
| --headless | Run without GUI | False | 
| --config-file | Path to setup configuration file | None | 
| --config-folder | Path to config folder | None | 
| --ext-data-folder | Path for data storage | None | 
| --http-port | HTTP server port | 8001 | 
| --socket-port | WebSocket port | 8002 | 
| --no-ssl | Disable SSL | False | 
| --scan-ext-drive-mount | Scan for external USB drives | False | 
| --ext-drive-mount | External drive mount point | None | 
Example Commands
# Basic usage with kernel
python -m imswitch --with-kernel
# Headless mode with kernel (recommended for servers)
python -m imswitch --headless --with-kernel
# Full configuration example
python -m imswitch --headless --with-kernel \
    --config-file /path/to/your/config.json \
    --config-folder /path/to/config \
    --ext-data-folder /path/to/data \
    --http-port 8001 \
    --socket-port 8002
Usage Examples
Accessing Hardware Managers
Once connected to the ImSwitch kernel, you have direct access to all hardware managers:
# Check available variables
print("Available variables:")
for var_name in sorted(dir()):
    if not var_name.startswith('_'):
        print(f"  {var_name}: {type(globals().get(var_name, 'Not found'))}")
# Access the master controller
print("Master Controller:", master)
# List available managers
managers = [attr for attr in dir(master) if attr.endswith('Manager') and not attr.startswith('_')]
print("Available Managers:")
for manager in managers:
    print(f"  {manager}: {getattr(master, manager, 'Not available')}")
Laser Control
# Check available lasers
if 'lasersManager' in globals():
    print("Available lasers:")
    for laser_name in lasersManager.getAllDeviceNames():
        print(f"  - {laser_name}")
        
    # Control first laser
    laser_names = lasersManager.getAllDeviceNames()
    if laser_names:
        first_laser = laser_names[0]
        current_power = lasersManager[first_laser].power
        print(f"Current power: {current_power}")
        
        # Set new power (be careful!)
        # lasersManager[first_laser].setPower(50)
Camera/Detector Control
# Check available detectors
if 'detectorsManager' in globals():
    print("Available detectors:")
    for detector_name in detectorsManager.getAllDeviceNames():
        print(f"  - {detector_name}")
        
    # Capture an image
    detector_names = detectorsManager.getAllDeviceNames()
    if detector_names:
        detector = detectorsManager[detector_names[0]]
        image = detector.getLatestFrame()
        print(f"Image shape: {image.shape if image is not None else 'No image captured'}")
Stage/Positioner Control
# Check available positioners
if 'positionersManager' in globals():
    print("Available positioners:")
    for positioner_name in positionersManager.getAllDeviceNames():
        print(f"  - {positioner_name}")
        
    # Get current position
    positioner_names = positionersManager.getAllDeviceNames()
    if positioner_names:
        positioner = positionersManager[positioner_names[0]]
        current_pos = positioner.position
        print(f"Current position: {current_pos}")
        
        # Move relatively (be careful!)
        # positioner.move(value=10, axis='X', is_absolute=False, is_blocking=True)
Data Analysis and Visualization
import numpy as np
import matplotlib.pyplot as plt
# Capture and analyze an image
if 'detectorsManager' in globals():
    detector_names = detectorsManager.getAllDeviceNames()
    if detector_names:
        detector = detectorsManager[detector_names[0]]
        image = detector.getLatestFrame()
        
        if image is not None:
            # Display image
            plt.figure(figsize=(10, 8))
            plt.imshow(image, cmap='gray')
            plt.title('Latest Frame')
            plt.colorbar()
            plt.show()
            
            # Print statistics
            print(f"Image shape: {image.shape}")
            print(f"Min: {np.min(image)}, Max: {np.max(image)}")
            print(f"Mean: {np.mean(image):.2f}, Std: {np.std(image):.2f}")
Safety and Best Practices
Safety Considerations
- Hardware Safety: Always be cautious when controlling hardware remotely: - Start with low power settings for lasers
- Make small movements for stages/positioners
- Monitor equipment during remote operations
 
- Application Control: Remember that the kernel integration affects application lifecycle: - The application cannot be stopped normally with Ctrl+C
- Plan for proper shutdown procedures
- Use this feature primarily for development and testing
 
- Network Security: When running in headless mode: - Ensure proper network security
- Use VPN or secure networks for remote access
- Consider authentication mechanisms
 
Safe Hardware Operation Template
def safe_laser_operation(laser_name, target_power, max_power=100):
    """
    Safely control laser power with checks and limits.
    
    Args:
        laser_name: Name of the laser to control
        target_power: Desired power setting
        max_power: Maximum allowed power for safety
    """
    if 'lasersManager' not in globals():
        print("Error: Lasers manager not available")
        return False
        
    if laser_name not in lasersManager.getAllDeviceNames():
        print(f"Error: Laser '{laser_name}' not found")
        return False
        
    if target_power > max_power:
        print(f"Error: Target power {target_power} exceeds maximum {max_power}")
        return False
        
    try:
        laser = lasersManager[laser_name]
        current_power = laser.power
        print(f"Current power: {current_power}")
        print(f"Setting power to: {target_power}")
        
        # Uncomment to actually set power
        # laser.setPower(target_power)
        # new_power = laser.power
        # print(f"New power: {new_power}")
        
        return True
        
    except Exception as e:
        print(f"Error controlling laser: {e}")
        return False
# Example usage (commented for safety)
# safe_laser_operation('Laser_1', 25, max_power=50)
Troubleshooting
Common Issues and Solutions
- Connection Issues: If Jupyter Lab cannot connect to the kernel: - Ensure ImSwitch is running with --with-kernelflag
- Check that extipy is properly installed
- Verify that no firewall is blocking the connection
 
- Ensure ImSwitch is running with 
- Kernel Not Responding: If the kernel becomes unresponsive: - The ImSwitch application cannot be stopped with Ctrl+C when kernel is active
- You may need to force-quit the ImSwitch process
- Restart both ImSwitch and Jupyter Lab
 
- Hardware Manager Not Available: If managers are not accessible: - Check that ImSwitch loaded the hardware configuration correctly
- Verify that hardware devices are connected and configured
- Check the ImSwitch logs for initialization errors
 
Debugging Commands
# Check ImSwitch module structure
print("Module Main Controllers:")
if 'moduleMainControllers' in globals():
    for name, controller in moduleMainControllers.mapping.items():
        print(f"  {name}: {type(controller)}")
# Check kernel connection info
import json
import glob
import subprocess
try:
    result = subprocess.run(['jupyter', '--runtime-dir'], 
                          capture_output=True, text=True, check=True)
    runtime_dir = result.stdout.strip()
    print(f"Jupyter runtime directory: {runtime_dir}")
    
    # List connection files
    connection_files = glob.glob(f"{runtime_dir}/kernel-*.json")
    print(f"Active kernel connection files:")
    for file in connection_files:
        print(f"  {file}")
        
except Exception as e:
    print(f"Error getting runtime info: {e}")
Technical Details
How It Works
The integration uses the extipy package, which provides a custom Jupyter kernel provisioner that can connect to existing IPython kernels. When ImSwitch starts with the --with-kernel flag:
- ImSwitch creates an embedded IPython kernel in its main process
- The kernel has access to all ImSwitch managers and controllers
- extipy's provisioner detects the latest kernel connection file
- Jupyter Lab/Notebook connects to the existing kernel instead of starting a new one
Architecture
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Jupyter Lab   │◄──►│   extipy         │◄──►│   ImSwitch      │
│   /Notebook     │    │   provisioner    │    │   + IPython     │
│                 │    │                  │    │   kernel        │
└─────────────────┘    └──────────────────┘    └─────────────────┘
Contributing
If you find issues or have suggestions for improvements:
- Check the existing issues in the ImSwitch repository
- Create a new issue with detailed description
- Include relevant error messages and system information
- Consider submitting a pull request with fixes
License
This integration follows the same license as ImSwitch (GNU General Public License v3.0).
Acknowledgments
- extipy - The package that makes this integration possible
- pyxll-jupyter - Original inspiration for the kernel provisioner approach
- ImSwitch development team - For creating the extensible architecture that enables this integration