CAsMan

CAsMan Antenna Module Documentation

Complete guide to the antenna positioning module for array management, baseline calculations, and interferometry applications.

Table of Contents

  1. Overview
  2. Installation
  3. Quick Start
  4. Database Sync
  5. Core Concepts
  6. API Reference
  7. Examples
  8. Troubleshooting

Overview

The casman.antenna module provides lightweight antenna array management with minimal dependencies, perfect for data analysis, baseline computation, and interferometry applications.


Installation

One-line install from GitHub:

pip install "casman[antenna] @ git+https://github.com/Coherent-All-Sky-Monitor/CAsMan.git"

From local clone:

git clone https://github.com/Coherent-All-Sky-Monitor/CAsMan.git
cd CAsMan
pip install -e ".[antenna]"

Dependencies: PyYAML, numpy, requests, matplotlib (~15 MB)

Option 2: Full CAsMan Installation

For the complete toolkit including web interfaces, CLI, and visualization:

cd CAsMan
pip install -e .

Dependencies: Flask, Pillow, PyYAML, and others (~50 MB)

Option 3: Development Installation

pip install -e ".[dev]"

Quick Start

Basic Loading and Access

from casman.antenna import AntennaArray

# Load antenna array from database
array = AntennaArray.from_database('database/parts.db')
print(f"Loaded {len(array)} antennas")

# Get specific antenna
ant = array.get_antenna('ANT00001')
print(f"Antenna: {ant.antenna_number}")
print(f"Grid Position: {ant.grid_code}")
print(f"Row: {ant.row_offset}, Column: {ant.east_col}")

# Get antenna by grid position
ant = array.get_antenna_at_position('CN021E03')
if ant:
    print(f"Found: {ant.antenna_number}")

Geographic Coordinates

Coordinates are stored in decimal degrees with up to 9 decimal places (WGS84). The generated CSV from scripts/generate_grid_coordinates.py writes database/grid_positions.csv with fixed 9-decimal precision.

# Check if antenna has coordinates
if ant.has_coordinates():
  print(f"Lat: {ant.latitude:.9f}°")
  print(f"Lon: {ant.longitude:.9f}°")
    print(f"Height: {ant.height:.2f}m")
    print(f"System: {ant.coordinate_system}")

# Filter antennas with coordinates
with_coords = array.filter_by_coordinates(has_coords=True)
print(f"{len(with_coords)} antennas have coordinates")

Baseline Calculations

# Get two antennas
ant1 = array.get_antenna('ANT00001')
ant2 = array.get_antenna('ANT00002')

# Compute geodetic baseline (Haversine formula)
distance = array.compute_baseline(ant1, ant2, use_coordinates=True)
print(f"Baseline: {distance:.2f} meters")

# Or use grid spacing (approximate)
distance_grid = array.compute_baseline(ant1, ant2, use_coordinates=False)
print(f"Grid baseline: {distance_grid:.2f} meters")

# Compute all pairwise baselines
baselines = array.compute_all_baselines(use_coordinates=True)
print(f"Total baselines: {len(baselines)}")

for ant1_num, ant2_num, dist in baselines[:10]:
    print(f"{ant1_num} - {ant2_num}: {dist:.3f} m")

SNAP Port Assignments

# Check SNAP port assignments (requires assembled_casm.db)
ant = array.get_antenna('ANT00001')
ports = ant.get_snap_ports()

if ports['p1']:
    print(f"P1 connected: {ant.format_chain_status('P1')}")
    print(f"SNAP: {ports['p1']['snap_part']}")
else:
    print(f"P1 not connected: {ant.format_chain_status('P1')}")

Database Sync

First Time Setup

If you don’t have a local database yet, download it automatically:

from casman.antenna import AntennaArray

# Download and load in one line (uses GitHub Releases sync configuration)
array = AntennaArray.from_database('database/parts.db', sync_first=True)
print(f"Downloaded and loaded {len(array)} antennas")

Manual Sync

from casman.antenna import sync_database

# Option 1: Auto-download using GitHub Releases (no credentials for public repos)
result = sync_database('parts.db')
print(result['message'])

if result['success']:
    array = AntennaArray.from_database('database/parts.db')
    print(f"Loaded {len(array)} antennas")

# Option 2: Download both databases for complete data
sync_database('parts.db')              # Antenna positions
sync_database('assembled_casm.db')     # Assembly chains (for SNAP ports)

Smart Sync Behavior

The sync system intelligently handles updates:

Configuration (in config.yaml):

database:
  sync:
    github_owner: Coherent-All-Sky-Monitor
    github_repo: CAsMan

Note: GitHub Releases provide public download access with no credentials required.

Auto-sync on import is best-effort and controlled by database.sync.auto_sync_on_import.


Core Concepts

Grid Position Format

Grid codes use the format: {array_id}{direction}{row:03d}E{col:02d}

Examples:

Kernel Index Mapping

The core array maps 256 antenna positions to kernel indices (0-255) for correlator processing:

from casman.antenna import grid_to_kernel_index, kernel_index_to_grid

# Convert grid code to kernel index
kernel_idx = grid_to_kernel_index('CN021E01')  # Returns 0
print(f"Kernel index: {kernel_idx}")

# Convert kernel index to grid code
grid_code = kernel_index_to_grid(0)  # Returns 'CN021E01'
print(f"Grid code: {grid_code}")

Mapping details:

Baseline Calculation Methods

Geodetic (Haversine Formula)

Used when use_coordinates=True:

a = sin²(Δlat/2) + cos(lat1) × cos(lat2) × sin²(Δlon/2)
c = 2 × atan2(√a, √(1-a))
d_horizontal = R × c
d_3d = √(d_horizontal² + Δheight²)

Where R = 6,371,000 meters (Earth radius)

Accuracy: ~0.5% for distances < 1000 km

Grid-Based Approximation

Used when use_coordinates=False or coordinates unavailable:

d = √((Δrow × spacing)² + (Δcol × spacing)²)

Where spacing = 0.4 meters (default CASM grid spacing)

Accuracy: Assumes flat grid, no height differences


API Reference

Database Sync Function

sync_database(db_name='parts.db', db_dir=None, public_url=None)

Download/sync database from cloud storage.

Parameters:

Returns: dict with keys:

Priority: explicit public_url → config.yaml GitHub settings → auto-discovery

AntennaArray Class

Main interface for working with antenna collections.

Class Methods

from_database(db_path, array_id='C', sync_first=False, public_url=None)

Load array from CAsMan database.

Parameters:

Returns: AntennaArray object

Instance Methods

Lookup:

Baseline Calculations:

Filtering:

Container Methods:

AntennaPosition Class

Represents a single antenna with all metadata.

Attributes

Properties

Methods

Kernel Index Functions

grid_to_kernel_index(grid_code, array_name='core') → int | None

Convert grid coordinate to kernel index.

Parameters:

Returns: int (0-255) or None if unmapped

kernel_index_to_grid(kernel_idx, array_name='core') → str | None

Convert kernel index to grid coordinate.

Parameters:

Returns: str (grid code) or None if invalid

get_array_index_map(array_name='core', db_dir=None) → KernelIndexArray

Get complete kernel index mapping with antenna data.

Returns: KernelIndexArray object with 43×6 arrays:

Methods:


Examples

Example 1: Download and Load Latest Database

from casman.antenna import sync_database, AntennaArray

# Method 1: One-liner with auto-download
array = AntennaArray.from_database('database/parts.db', sync_first=True)
print(f"Loaded {len(array)} antennas")

# Method 2: Explicit sync then load
result = sync_database('parts.db')
if result['success']:
    print(f"Success: {result['message']}")
    array = AntennaArray.from_database('database/parts.db')
else:
    print(f"Failed: {result['message']}")

# Method 3: Download both databases for complete functionality
sync_database('parts.db')              # Antenna positions
sync_database('assembled_casm.db')     # Assembly chains

array = AntennaArray.from_database('database/parts.db')

# Now you can trace complete chains
ant = array.get_antenna('ANT00001')
chain_p1 = ant.format_chain_status('P1')
print(f"Chain: {chain_p1}")

Example 2: Baseline Statistics

from casman.antenna import AntennaArray

array = AntennaArray.from_database('database/parts.db')
baselines = array.compute_all_baselines(use_coordinates=True)

distances = [dist for _, _, dist in baselines]
print(f"Total baselines: {len(baselines)}")
print(f"Min baseline: {min(distances):.2f}m")
print(f"Max baseline: {max(distances):.2f}m")
print(f"Avg baseline: {sum(distances)/len(distances):.2f}m")

Example 3: Baseline Matrix Computation

from casman.antenna import AntennaArray
import numpy as np

array = AntennaArray.from_database('database/parts.db')

# Get antennas with coordinates
antennas = [ant for ant in array if ant.has_coordinates()]
n = len(antennas)

# Create baseline matrix
baseline_matrix = np.zeros((n, n))

for i, ant1 in enumerate(antennas):
    for j, ant2 in enumerate(antennas):
        if i != j:
            baseline_matrix[i, j] = array.compute_baseline(
                ant1, ant2, use_coordinates=True
            )

# Save results
np.save('baseline_matrix.npy', baseline_matrix)
antenna_numbers = [ant.antenna_number for ant in antennas]
np.save('antenna_order.npy', antenna_numbers)

print(f"Saved {n}×{n} baseline matrix")
print(f"Min: {baseline_matrix[baseline_matrix > 0].min():.2f}m")
print(f"Max: {baseline_matrix.max():.2f}m")

Example 4: Kernel Index Mapping

from casman.antenna import get_array_index_map
import numpy as np

# Get complete mapping with antenna assignments
kernel_data = get_array_index_map()

print(f"Array shape: {kernel_data.shape}")  # (43, 6)
print(f"Mapped positions: {np.sum(kernel_data.kernel_indices >= 0)}")  # 256

# Query by kernel index
info = kernel_data.get_by_kernel_index(0)
print(f"Kernel 0: {info['grid_code']}, {info['antenna_number']}")

# Query by grid code
info = kernel_data.get_by_grid_code('CN021E01')
print(f"Grid CN021E01: kernel index {info['kernel_index']}")

# Create antenna-to-kernel-index mapping
antenna_to_kernel = {}
for row in range(43):
    for col in range(6):
        ant_num = kernel_data.antenna_numbers[row, col]
        kernel_idx = kernel_data.kernel_indices[row, col]
        if ant_num and kernel_idx >= 0:
            antenna_to_kernel[ant_num] = kernel_idx

print(f"Mapped {len(antenna_to_kernel)} antennas to kernel indices")

Example 5: Data Quality Check

from casman.antenna import AntennaArray

array = AntennaArray.from_database('database/parts.db')

# Check data completeness
total = len(array)
with_coords = len(array.filter_by_coordinates(has_coords=True))

print(f"Data completeness:")
print(f"  Total antennas: {total}")
print(f"  With coordinates: {with_coords} ({100*with_coords/total:.1f}%)")

# Check SNAP assignments (requires assembled_casm.db)
connected_p1 = 0
connected_p2 = 0

for ant in array:
    ports = ant.get_snap_ports()
    if ports['p1']:
        connected_p1 += 1
    if ports['p2']:
        connected_p2 += 1

print(f"  SNAP P1 connected: {connected_p1} ({100*connected_p1/total:.1f}%)")
print(f"  SNAP P2 connected: {connected_p2} ({100*connected_p2/total:.1f}%)")

Example 6: Spatial Clustering

from casman.antenna import AntennaArray
from collections import defaultdict

array = AntennaArray.from_database('database/parts.db')

# Group antennas by row
by_row = defaultdict(list)
for ant in array:
    by_row[ant.row_offset].append(ant)

print("Antenna distribution by row:")
for row in sorted(by_row.keys(), reverse=True):
    antennas = by_row[row]
    print(f"  Row {row:+3d}: {len(antennas):3d} antennas")

Database Schema

The antenna module reads from the parts.db database:

Table: antenna_positions

CREATE TABLE antenna_positions (
    id INTEGER PRIMARY KEY,
    antenna_number TEXT UNIQUE NOT NULL,
    grid_code TEXT UNIQUE NOT NULL,
    array_id TEXT NOT NULL,           -- 'C', 'N', 'S', etc.
    row_offset INTEGER NOT NULL,      -- Signed: -21 to +21 for core
    east_col INTEGER NOT NULL,        -- 1-based: 1-6 for core array
    assigned_at TEXT NOT NULL,
    notes TEXT,
    latitude REAL,
    longitude REAL,
    height REAL,
    coordinate_system TEXT
);

Loading Geographic Coordinates

Coordinates can be loaded from CSV using the CLI:

casman database load-coordinates --csv grid_positions.csv

CSV format:

grid_code,latitude,longitude,height,coordinate_system,notes
CN021E01,37.871899,-122.258477,10.5,WGS84,Survey point 1
CN021E02,37.871912,-122.258321,10.6,WGS84,Survey point 2

Configuration

The module uses config.yaml for grid layout and sync configuration:

grid:
  core:
    array_id: "C"
    north_rows: 21
    south_rows: 21
    east_columns: 6
    allow_expansion: true
    
    # Kernel index mapping (core array only)
    kernel_index:
      enabled: true
      max_index: 255        # 256 antennas (0-255)
      start_row: 21         # CN021
      start_column: 1       # E01
  
  # Optional: Additional arrays
  outriggers:
    array_id: "O"
    north_rows: 10
    south_rows: 10
    east_columns: 4
    allow_expansion: false

Note: Configuration file should be in project root or discoverable via CASMAN_CONFIG environment variable.


Troubleshooting

Import Errors

“No module named ‘casman’“

“No module named ‘yaml’“

Database Issues

Database not found

No coordinates loaded

Sync failed

Data Issues

SNAP ports not found

Baseline calculation returns None


Quick Reference

Installation:

pip install "casman[antenna] @ git+https://github.com/Coherent-All-Sky-Monitor/CAsMan.git"

Download and load:

from casman.antenna import AntennaArray
array = AntennaArray.from_database('database/parts.db', sync_first=True)

Compute baselines:

baselines = array.compute_all_baselines(use_coordinates=True)

Kernel index:

from casman.antenna import grid_to_kernel_index
kernel_idx = grid_to_kernel_index('CN021E01')  # Returns 0