Complete guide to the antenna positioning module for array management, baseline calculations, and interferometry applications.
The casman.antenna module provides lightweight antenna array management with minimal dependencies, perfect for data analysis, baseline computation, and interferometry applications.
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)
For the complete toolkit including web interfaces, CLI, and visualization:
cd CAsMan
pip install -e .
Dependencies: Flask, Pillow, PyYAML, and others (~50 MB)
pip install -e ".[dev]"
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}")
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")
# 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")
# 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')}")
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")
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)
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.
Grid codes use the format: {array_id}{direction}{row:03d}E{col:02d}
C (core), N (north), S (south)N (positive rows) or S (negative rows)002 for row +2)01 for first column)Examples:
CN021E01 - Core array, North row +21, East column 1CS021E06 - Core array, South row -21, East column 6CC000E03 - Core array, Center row 0, East column 3The 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:
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
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
sync_database(db_name='parts.db', db_dir=None, public_url=None)
Download/sync database from cloud storage.
Parameters:
db_name (str): Database filename (default: ‘parts.db’)db_dir (str): Database directory (default: ‘database/’)public_url (str): Public URL for download (optional - uses config.yaml if not provided)Returns: dict with keys:
success (bool): Whether operation succeededsynced (bool): Whether database was updatedmessage (str): Status messagePriority: explicit public_url → config.yaml GitHub settings → auto-discovery
Main interface for working with antenna collections.
from_database(db_path, array_id='C', sync_first=False, public_url=None)
Load array from CAsMan database.
Parameters:
db_path (str): Path to parts.db filearray_id (str): Array identifier (default: ‘C’ for core)sync_first (bool): If True, download database before loading (default: False)public_url (str): Public URL for credential-free downloadReturns: AntennaArray object
Lookup:
get_antenna(antenna_number) → AntennaPosition | None
get_antenna_at_position(grid_code) → AntennaPosition | None
Baseline Calculations:
compute_baseline(ant1, ant2, use_coordinates=True) → float
use_coordinates=True: Use geodetic (Haversine) formulause_coordinates=False: Use grid spacing approximationcompute_all_baselines(use_coordinates=True, include_self=False) → list
(ant1_number, ant2_number, distance) tuplesFiltering:
filter_by_coordinates(has_coords=True) → list[AntennaPosition]
Container Methods:
__len__(): Number of antennas__iter__(): Iterate over all antennas__repr__(): String representationRepresents a single antenna with all metadata.
antenna_number (str): Base antenna number (e.g., ‘ANT00001’)grid_position (AntennaGridPosition): Parsed grid position objectlatitude (float |
None): Latitude in decimal degrees |
longitude (float |
None): Longitude in decimal degrees |
height (float |
None): Height in meters |
coordinate_system (str |
None): Coordinate system ID |
assigned_at (str |
None): ISO timestamp |
notes (str |
None): Field notes |
grid_code (str): Grid code string (e.g., ‘CN002E03’)row_offset (int): Signed row offseteast_col (int): 1-based east column indexhas_coordinates() → bool
get_snap_ports() → dict
format_chain_status(polarization='P1') → str
grid_to_kernel_index(grid_code, array_name='core') → int | None
Convert grid coordinate to kernel index.
Parameters:
grid_code (str): Grid position (e.g., ‘CN021E01’)array_name (str): Array name in config (default: ‘core’)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:
kernel_idx (int): Kernel index (0-255)array_name (str): Array name in config (default: ‘core’)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:
kernel_indices: int array (-1 for unmapped)grid_codes: str arrayantenna_numbers: str arraysnap_ports: tuple array (chassis, slot, port)snap_board_info: dict array with SNAP board configurationcoordinates: 3D float array [latitude, longitude, height]Methods:
get_by_kernel_index(kernel_idx): Query by kernel indexget_by_grid_code(grid_code): Query by grid codefrom 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}")
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")
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")
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")
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}%)")
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")
The antenna module reads from the parts.db database:
antenna_positionsCREATE 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
);
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
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.
“No module named ‘casman’“
pip install -e . or pip install -e ".[antenna]" from repo root“No module named ‘yaml’“
pip install PyYAMLpip install -r requirements.txtDatabase not found
database/parts.dbsync_database('parts.db')No coordinates loaded
casman database load-coordinates --csv grid_positions.csvSync failed
sync_database('parts.db', public_url='...')SNAP ports not found
sync_database('assembled_casm.db')Baseline calculation returns None
array.get_antenna('ANT00001')ant.has_coordinates()compute_baseline(..., use_coordinates=False)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