CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-oemer

End-to-end Optical Music Recognition (OMR) system for transcribing musical notation from images into structured MusicXML format.

Pending
Overview
Eval results
Files

layer-management.mddocs/

Layer Management System

Global state management system for intermediate processing results, enabling modular pipeline architecture and debugging capabilities. The layer system allows each processing stage to register intermediate results for use by subsequent stages.

Capabilities

Layer Registration

Register processing layers for global access throughout the pipeline.

def register_layer(name: str, layer: ndarray) -> None:
    """
    Register a processing layer with a given name.
    
    Stores the layer data in global memory for access by other
    processing stages. Prints a warning if layer name already exists.
    
    Parameters:
    - name (str): Unique identifier for the layer  
    - layer (ndarray): Numpy array containing the layer data
    
    Raises:
    AssertionError: If layer is not a numpy array
    """

Layer Retrieval

Retrieve registered processing layers by name.

def get_layer(name: str) -> ndarray:
    """
    Retrieve a registered processing layer by name.
    
    Parameters:
    - name (str): Name of the layer to retrieve
    
    Returns:
    ndarray: The requested layer data
    
    Raises:
    KeyError: If no layer with the given name exists
    """

Layer Management

Manage the lifecycle of registered layers.

def delete_layer(name: str) -> None:
    """
    Delete a registered layer and free its memory.
    
    Silently handles cases where the layer doesn't exist.
    
    Parameters:
    - name (str): Name of the layer to delete
    """

def list_layers() -> List[str]:
    """
    List all currently registered layer names.
    
    Returns:
    List[str]: List of all registered layer names
    """

def show_access_count() -> None:
    """
    Display access statistics for all registered layers.
    
    Shows how many times each layer has been accessed,
    useful for debugging and optimization.
    """

Standard Layer Names

The oemer pipeline uses standardized layer names for consistency:

Input Layers

  • "original_image" - Original input image (RGB)
  • "staff_pred" - Staff line predictions from neural network
  • "symbols_pred" - Combined symbol predictions
  • "notehead_pred" - Note head predictions
  • "stems_rests_pred" - Stems and rests predictions
  • "clefs_keys_pred" - Clefs and accidentals predictions

Extracted Elements

  • "staffs" - Array of Staff instances
  • "zones" - Staff zone boundaries
  • "notes" - Array of NoteHead instances
  • "note_groups" - Array of NoteGroup instances
  • "clefs" - Array of Clef instances
  • "sfns" - Array of sharp/flat/natural instances
  • "rests" - Array of Rest instances
  • "barlines" - Array of Barline instances

Processing Layers

  • "note_id" - Note ID mapping layer
  • "group_map" - Note group mapping layer
  • "bboxes" - Bounding box registration layer

Usage Examples

Basic Layer Operations

from oemer.layers import register_layer, get_layer, delete_layer, list_layers
import numpy as np
import cv2

# Register an image layer
image = cv2.imread("sheet_music.jpg")
register_layer("original_image", image)

# Register prediction layers
staff_predictions = np.random.randint(0, 2, (1000, 1500), dtype=np.uint8)
register_layer("staff_pred", staff_predictions)

symbol_predictions = np.random.randint(0, 3, (1000, 1500), dtype=np.uint8)
register_layer("symbols_pred", symbol_predictions)

# List all registered layers
print("Registered layers:", list_layers())

# Retrieve layers for processing
original = get_layer("original_image")
staff = get_layer("staff_pred")
symbols = get_layer("symbols_pred")

print(f"Original image shape: {original.shape}")
print(f"Staff predictions shape: {staff.shape}")
print(f"Symbol predictions shape: {symbols.shape}")

# Clean up specific layer
delete_layer("symbols_pred")
print("Layers after deletion:", list_layers())

Pipeline Integration

from oemer.layers import register_layer, get_layer
from oemer.staffline_extraction import extract as staff_extract
from oemer.notehead_extraction import extract as note_extract
import numpy as np

def process_with_layers(image_path: str):
    """Example of layer-based processing pipeline."""
    
    # 1. Load and register input image
    image = cv2.imread(image_path)
    register_layer("original_image", image)
    
    # 2. Generate and register predictions (simplified)
    # In real usage, these come from neural network inference
    h, w = image.shape[:2]
    staff_pred = np.random.randint(0, 2, (h, w), dtype=np.uint8)
    symbols_pred = np.random.randint(0, 2, (h, w), dtype=np.uint8)
    
    register_layer("staff_pred", staff_pred)
    register_layer("symbols_pred", symbols_pred)
    
    # 3. Extract stafflines (reads from layers automatically)
    staffs, zones = staff_extract()
    register_layer("staffs", np.array(staffs))
    register_layer("zones", zones)
    
    # 4. Extract noteheads (reads from layers automatically)
    notes = note_extract()
    register_layer("notes", np.array(notes))
    
    # 5. Access results from any stage
    extracted_staffs = get_layer("staffs")
    extracted_notes = get_layer("notes")
    
    print(f"Extracted {len(extracted_staffs)} staffs")
    print(f"Extracted {len(extracted_notes)} notes")
    
    return extracted_staffs, extracted_notes

# Run the pipeline
staffs, notes = process_with_layers("test_score.jpg")

Memory Management

from oemer.layers import register_layer, delete_layer, list_layers, show_access_count
import numpy as np

def memory_efficient_processing():
    """Example of memory-efficient layer management."""
    
    # Register large intermediate layers
    large_layer1 = np.zeros((5000, 5000), dtype=np.float32)
    large_layer2 = np.zeros((5000, 5000), dtype=np.float32)
    
    register_layer("temp_layer1", large_layer1)
    register_layer("temp_layer2", large_layer2)
    
    print(f"Layers registered: {list_layers()}")
    
    # Process data using the layers
    data1 = get_layer("temp_layer1")
    data2 = get_layer("temp_layer2")
    
    # Combine and create final result
    result = data1 + data2
    register_layer("final_result", result)
    
    # Clean up intermediate layers to free memory
    delete_layer("temp_layer1")
    delete_layer("temp_layer2")
    
    print(f"Layers after cleanup: {list_layers()}")
    
    # Show access statistics
    show_access_count()
    
    return get_layer("final_result")

result = memory_efficient_processing()

Debugging with Layers

from oemer.layers import register_layer, get_layer, list_layers
import matplotlib.pyplot as plt
import numpy as np

def debug_pipeline_stage(stage_name: str):
    """Debug a specific pipeline stage by examining its layers."""
    
    print(f"\n=== Debugging {stage_name} ===")
    print(f"Available layers: {list_layers()}")
    
    # Check if expected layers exist
    expected_layers = ["original_image", "staff_pred", "symbols_pred"]
    for layer_name in expected_layers:
        try:
            layer = get_layer(layer_name)
            print(f"✓ {layer_name}: shape={layer.shape}, dtype={layer.dtype}")
            
            # Show basic statistics
            if layer.dtype in [np.uint8, np.int32, np.float32]:
                print(f"  Range: [{np.min(layer)}, {np.max(layer)}]")
                print(f"  Non-zero pixels: {np.count_nonzero(layer)}")
                
        except KeyError:
            print(f"✗ {layer_name}: NOT FOUND")
    
    # Visualize layers if available
    try:
        original = get_layer("original_image")
        staff = get_layer("staff_pred")
        
        fig, axes = plt.subplots(1, 2, figsize=(12, 6))
        
        axes[0].imshow(original)
        axes[0].set_title("Original Image")
        axes[0].axis('off')
        
        axes[1].imshow(staff, cmap='gray')
        axes[1].set_title("Staff Predictions")
        axes[1].axis('off')
        
        plt.tight_layout()
        plt.savefig(f"debug_{stage_name}.png")
        print(f"Saved debug visualization: debug_{stage_name}.png")
        
    except KeyError as e:
        print(f"Could not create visualization: {e}")

# Use in pipeline debugging
debug_pipeline_stage("after_inference")

Layer Data Validation

from oemer.layers import get_layer, register_layer
import numpy as np

def validate_layer_consistency():
    """Validate that all layers have consistent dimensions."""
    
    try:
        # Get core layers
        original = get_layer("original_image")
        staff = get_layer("staff_pred")
        symbols = get_layer("symbols_pred")
        
        # Check shape consistency
        h, w = original.shape[:2]
        
        if staff.shape != (h, w):
            print(f"WARNING: Staff predictions shape {staff.shape} != image shape {(h, w)}")
            
        if symbols.shape != (h, w):
            print(f"WARNING: Symbol predictions shape {symbols.shape} != image shape {(h, w)}")
            
        # Check data ranges
        if staff.dtype == np.uint8 and (staff.min() < 0 or staff.max() > 255):
            print(f"WARNING: Staff predictions out of uint8 range: [{staff.min()}, {staff.max()}]")
            
        if symbols.dtype == np.uint8 and (symbols.min() < 0 or symbols.max() > 255):
            print(f"WARNING: Symbol predictions out of uint8 range: [{symbols.min()}, {symbols.max()}]")
        
        print("✓ Layer consistency validation passed")
        
    except KeyError as e:
        print(f"✗ Layer validation failed: Missing layer {e}")
    except Exception as e:
        print(f"✗ Layer validation error: {e}")

# Run validation during pipeline execution
validate_layer_consistency()

Best Practices

Layer Naming Conventions

  • Use descriptive, lowercase names with underscores
  • Follow the standard naming scheme when possible
  • Use prefixes for temporary or debug layers (temp_, debug_)

Memory Management

  • Delete large intermediate layers when no longer needed
  • Use show_access_count() to identify unused layers
  • Clear all layers between processing different images

Error Handling

  • Always check if required layers exist before accessing them
  • Handle KeyError exceptions when retrieving layers
  • Validate layer shapes and data types for consistency

The layer management system provides a flexible foundation for the oemer pipeline, enabling modular development, easy debugging, and efficient memory usage.

Install with Tessl CLI

npx tessl i tessl/pypi-oemer

docs

index.md

inference.md

layer-management.md

main-pipeline.md

note-grouping.md

notehead-extraction.md

staffline-detection.md

tile.json