The command line
Five console scripts, installed by pip install oroscope, and the complete option
reference for the search itself at the bottom of this page.
The command line is a convenience, not the primary interface. Everything here is a thin wrapper over a function, and Quickstart shows the same work done from Python, which is how most of it gets used.
Everything here can be done from code
There is no CLI-only behaviour. Every command below is a thin wrapper over a function, and the wrapper’s job is argument parsing and file placement, nothing else. That is a deliberate property, not a coincidence: a parameter sweep, a notebook or a service driving the pipeline in a loop should not have to shell out, and anything the command line can reach that the library cannot is a bug.
Command line |
Code |
|---|---|
|
|
|
|
reading that file back |
|
the defaults it contains |
|
|
|
|
|
the summary a run prints |
|
|
|
|
|
|
|
|
|
The pipeline returns its results dictionary, so a caller never has to find and re-read the JSON it has just written:
import oroscope
config = oroscope.load_config("config/tambo_colca_config.json")
results = oroscope.find_grand_regions_interactive(
dem_path="input/dem/colca.tif", run_output_dir="output/scan",
**{k: v for k, v in config.items()
if not k.startswith("_")
and k not in ("dem_path", "print_info",
"output_directory_base_with_given_json")})
print(results["results"]["total_capacity"])
print(results["explanation"])
Note
A parameter’s default is the same wherever you read it — off the function
signature, off oroscope --help, or out of oroscope.default_config(). They
disagreed on ten parameters once, so omitting one meant different things depending
on which door you came in by; a test now pins all three together.
oroscope — run a search
oroscope --config_path config/grand_colca_config.json
An explicitly typed option beats the configuration file, and says so when it does:
oroscope --config_path config/grand_colca_config.json --min_slope_deg 5
Generate a template naming every key, then edit it:
oroscope --generate_config arequipa.json --config_preset arequipa
oroscope --config_path arequipa.json
Resume a run that died after the expensive part:
oroscope --config_path run.json --resume --resume_dir output/run
Every run prints a plain-language summary of itself and saves it as
explanation.txt; --no_explain suppresses that.
oroscope-crop — cut a window out of a DEM
oroscope-crop input/dem/arequipa_SRTMGL1.tif input/dem/colca.tif \
--north -15.30 --south -15.85 --west -72.40 --east -71.55
The crop carries its own north-west corner, so it stands alone as a georeferenced file. The window is the smallest pixel-aligned box containing what you asked for: the start is floored and the stop ceiled, so you never get less than you requested.
oroscope-combine — overlay two or more searches
oroscope-combine output/grand_colca_config output/tambo_colca_config \
--labels GRAND TAMBO --out output/combined_colca
Reports terrain viable for each, for both (co-location) and for either, with a
membership raster and an overview map — and an account of why the joint area is the
size it is, saved as combination_explanation.txt. Co-location is usually decided by
slope: a pixel has one slope and both experiments must accept it.
The inputs must be pixel-aligned — same shape, same pixel size, same corner. That is checked and refused rather than resampled, because two runs on differently-cropped DEMs would silently compare the wrong ground.
oroscope-sensitivity — how firm is the answer?
oroscope-sensitivity config/tambo_colca_config.json \
--sweep min_score 0.0 0.2 0.35 0.5 \
--sweep min_target_slope_deg 0 15 25 35
Varies one parameter at a time about a baseline and tabulates what moves. Each point runs in its own subprocess, so memory is reclaimed between them and one failed point reports a failed row instead of ending the sweep.
Read this before quoting a capacity. Several criteria sit near cliffs.
oroscope-fetch-dem — download the elevation models
oroscope-fetch-dem --region arequipa --open_topography_api_key YOUR_KEY
oroscope-fetch-dem --region peru # key from the environment
oroscope-fetch-dem # all four regions
Fetches a bundled region into input/dem/ and writes a ready-to-run configuration
for it.
|
dataset |
size |
|
|---|---|---|---|
|
SRTMGL1, 30 m |
148 MB |
The department. Every published number in this project comes from it or from a crop of it. |
|
SRTMGL1, 30 m |
95 MB |
The Cordillera Blanca and the Callejón de Huaylas. Run at the same resolution and criteria as Arequipa, so the two are directly comparable. |
|
SRTMGL1, 30 m |
110 MB |
Coastal, and a useful contrast: desert shelf rising to the western Andean flank. SRTMGL1, not the AW3D30 it used to fetch — the three department runs are compared against one another, and a dataset difference would sit inside every comparison as a confound. |
|
SRTMGL3, 90 m |
302 MB |
The whole country. 3 arc-seconds, not by preference — see
|
Getting a key. It is free and takes a minute.
Register at OpenTopography and sign in.
Open myOpenTopo Authorizations and API Key from the account menu.
Copy the key.
Pass it as --open_topography_api_key, or set OPENTOPOGRAPHY_API_KEY in the
environment — which keeps it out of your shell history, and out of any file that might
be committed by accident.
Warning
Requests are capped by area, per dataset: 450,000 km² for every 30 m dataset and
4,050,000 km² for the 90 m ones. Peru’s bounding box is about 2.86 million km², six
times over the 30 m limit, which is one of the two reasons peru is 3 arc-seconds.
The other is memory.
Run it from src/: it writes to ../input/dem/ and ../config/ by default, both
relative to the working directory. Pass --output_dir and --config_dir to run it
from anywhere else. Unlike the search, this one is not config-relative — there is no
configuration file to be relative to.
The configuration it writes is a default template pointed at the DEM. The tuned
configurations that produced this project’s numbers are committed in config/
alongside it, and those are the ones to run.
oroscope-fetch-roads — road geometry for the map
oroscope-fetch-roads --dem input/dem/arequipa_SRTMGL1.tif --places
oroscope --config_path config/grand_colca_config.json \
--roads_geojson input/roads/arequipa_SRTMGL1.geojson \
--settlements input/roads/arequipa_SRTMGL1_places.geojson
Downloads roads from OpenStreetMap through Overpass and writes a GeoJSON, so a map can show what a site count cannot: whether the good ground is reachable. Over the Arequipa DEM that is 8,780 roads of motorway through tertiary class.
Drawing, not screening. This changes no result. The searcher has a separate and
older facility for access as a criterion — --road_map_path takes an aligned
distance-to-road raster and --max_road_dist_km cuts on it, appearing in the funnel
as road distance — which nothing yet produces and no bundled configuration uses.
--places fetches populated places — city, town, village — into a sibling
_places.geojson, which --settlements then marks on the map. This is how to get
real town coordinates. The bundled arequipa and lima presets are the handful of
named places already curated as RFI zones; anything beyond them should be sourced, not
written from memory, because a town in the wrong valley looks exactly like a town in the
right one. Over the Arequipa DEM that is 1,268 places, and over the Colca crop it puts
Chivay where OSM says it is.
Markers scale with the place class and only the most important few are labelled — every
place inside the map gets a marker, but sixty village names over one canyon is not a
map. Raise max_labels on oroscope.add_settlements() if you want more.
The bounding box is tiled and fetched a piece at a time, with a pause between requests and a fallback to a second mirror. A single query over the 3.5-degree Arequipa box answers with a gateway timeout; nine smaller ones succeed. Overpass is a shared free service, and the pacing is not optional politeness.
Data is © OpenStreetMap contributors, ODbL. The loader carries that attribution inside the file and both maps print it beneath the axes, so it cannot be separated from the data by copying the picture.
tools/make_animations.py — animations of the mechanism
python tools/make_animations.py # all eight, MP4 and GIF
python tools/make_animations.py --only the_walk
python tools/make_animations.py --format gif --out docs/source/_static
Eight animations, each showing a process whose intermediate states are the point —
anything that is a single state is already a figure in oroscope.figures.
Notebook 9 builds all of them and explains what each is for.
the_walkOne backward ray sweeping down through the elevation window: the first intersection sliding along the profile and the column depth accumulating behind it. This is the mechanism the whole search rests on.
the_azimuth_fanThe other half of that mechanism.
the_walksweeps elevation at one bearing; this sweeps the bearing, and reports whether each one finds a wall at the accepted range, the candidate’s own hillside a few hundred metres off, or sky.the_funnelThe map draining stage by stage. The funnel table says where the candidates went; this shows where on the ground they went.
stride_and_closingWhy a closing element smaller than the stride gap loses the mask. Shown on the Assumptions and limitations page.
product_collapseWhy a threshold on a product is treacherous, and why the reason is dynamic: each component multiplied in walks the population toward zero while the cut stays put. Measured on Colca, 100% of candidates survive the cut before any component and 32% after six — but the fall is not evenly shared, and one component contributes nothing at all.
slope_criterionWhere a criterion bites, as opposed to how much it costs. The accepted mask over Colca as
min_target_slope_degclimbs through the wall-slope distribution.tau_in_rockMore rock is not better. A tau’s energy and survival falling as it burrows, against the depth that maximises production and escape together — 5.7×10⁶ g/cm², about 22 km of standard rock, at 1 EeV.
energy_windowThe arrival window narrowing as Earth absorption bites, its lower edge climbing from −4.4° at 100 PeV to −0.9° at 10 EeV.
MP4 needs ffmpeg; GIF falls back to pillow. Outputs land in output/animations/,
which is gitignored.
Five of the eight are built from committed code and synthetic terrain and reproduce on
any clone. The three that are about what happens on real ground —
the_azimuth_fan, product_collapse and slope_criterion — read
input/dem/colca.tif when it is present and fall back to synthetic terrain when it
is not, saying on the figure which they used.
tools/run_full_dem.py — the full-DEM runs
Not a console script — it lives in the repository, because it is about this project’s own runs rather than about searching in general. For one region it runs GRAND, then TAMBO, then the combination over the whole DEM, and stores the small artefacts for that region’s notebook to read.
Regions come from a table in the file, each needing a DEM in input/dem/ and a
config/{grand,tambo}_<region>_full.json pair:
|
grid |
|
|---|---|---|
|
129 Mpx, 1 arc-sec |
The default. GRAND takes about 25 minutes, TAMBO about one. |
|
69 Mpx, 1 arc-sec |
The Cordillera Blanca. Roughly half the pixels, so roughly half the time. |
This was run_arequipa_full.py, which hard-coded one DEM, one store and one pair of
configurations. The second region asked for was not a reason to copy two hundred lines.
python tools/run_full_dem.py --dry-run
python tools/run_full_dem.py --region ancash --dry-run
What ``–dry-run`` does. It reports what the real run would cost and then stops, without starting a search, writing a file or touching the store:
DEM: input/dem/arequipa_SRTMGL1.tif
estimate: 5.08 GiB at downsample_factor 4
available: 6.4 GiB
would run: grand, tambo, then combine
expected: ~25 min for grand, ~1 min for tambo
store: results/arequipa_full
Each line answers a question worth answering before committing an hour:
DEMwhich file will be searched, and whether it is present at all. A missing DEM is reported here rather than after the first search has started.
estimateandavailablethe pre-flight memory estimate against what the system reports free. This is the number that decides
downsample_factorandcandidate_stride: the same DEM needs 7.2 GiB at 1 and 5.1 GiB at 4. Downsampling scales the labelling arrays as its inverse square but leaves the candidates untouched – they are taken on the native grid – and at this scale the candidates dominate, so striding is the stronger lever. The estimate deliberately excludes the memory-mapped DEM, which is file-backed and evictable.would runwhich searches, honouring
--only.--only grandruns one and skips the combination.expectedthe wall time to expect per search, so a run is not started ten minutes before you need the machine.
storewhere the artefacts will land — the results JSON, provenance and explanation, a few hundred kilobytes that notebook 8 reads.
In a dry run no memory cap is applied, since nothing is allocated. The real run
caps the process’s address space so a search that outgrows the machine fails with
MemoryError naming itself rather than letting the kernel’s OOM killer choose a
victim.
Then, for real:
python tools/run_full_dem.py # arequipa, the default
python tools/run_full_dem.py --region ancash
or one experiment at a time:
python tools/run_full_dem.py --region ancash --only grand
Afterwards, refresh the cross-region table:
python tools/compare_regions.py
Run it when a configuration changes, not otherwise. The store carries a manifest naming the configurations and the time, so a stale one is detectable rather than merely suspected — which matters, because the whole premise of storing rather than recomputing is that nobody looks again.
Where things are resolved from
Four sources, first match wins:
An option you actually typed. This beats everything, and announces itself when it overrides a configuration file.
The configuration file given with
--config_path.``config/fallbacks.json``, if present. Every value taken from here is announced, because a fallback is the least visible input the tool has.
The built-in default.
Only the DEM is genuinely required: origin_lat/origin_lon are read from the
file’s own GeoTIFF tiepoint, and a supplied origin disagreeing with the file by more
than ~100 m is reported rather than silently honoured.
Note
Paths in a configuration are relative to the configuration file, not to the
working directory, so a search runs the same from anywhere. "dem_path":
"../input/dem/colca.tif" in config/ means input/dem/colca.tif in the
repository whether you are standing in the root, in src/ or elsewhere; the
outputs follow the same rule. Absolute paths are left alone, and a path that
resolves only against the working directory still works, with a warning — the old
behaviour, kept so this does not break a setup that relied on it.
This replaces the long-standing requirement to cd src first.
Every option, in full
The search accepts 87 options. Every one is also a parameter of
oroscope.find_grand_regions_interactive under the same name, except the five
negative-form flags — --require_sky, --nearest_sampling, --no_geomagnetic,
--include_near_field, --no_print_info, --no_explain — whose positive forms
(require_terrain, bilinear_sampling, use_geomagnetic,
exclude_near_field, print_info, explain) are what the function takes.
Generated from the parser, so it cannot drift from the code: a test asserts that every option below exists and that every option that exists appears below.
Option |
Type |
Default |
What it does |
|---|---|---|---|
|
string |
|
Path to the Digital Elevation Model (.tif) file. |
|
float |
|
Reference origin latitude (e.g., -10.228). |
|
float |
|
Reference origin longitude (e.g., -78.076). |
|
int |
|
Total target capacity for the array (default: 10000). |
|
float |
|
Minimum acceptable width of the array site in km (default: 2.0). |
|
float |
|
Minimum allowable altitude in meters (optional). |
|
float |
|
Maximum allowable altitude in meters (optional). |
|
float |
|
Distance between antennas in km (default: 1.0). |
|
float |
|
Minimum required distance to target mountain in km (default: 10.0). |
|
float |
|
Maximum required distance to target mountain in km (default: 80.0). |
|
square / hex |
|
Antenna layout grid type (default: ‘hex’). |
|
float |
|
Minimum terrain steepness in degrees (default: 3.0). |
|
float |
|
Maximum terrain steepness in degrees (default: 25.0). |
|
int |
|
Internal capacity mask downsampling factor for processing speed (default: 4). |
|
float |
|
Map resolution in degrees per pixel. Defaults to reading the DEM’s GeoTIFF tags. |
|
float |
|
Ground distance in metres over which slope is measured. Default: the DEM’s native resolution. |
|
int |
|
Keep every Nth candidate pixel before ray tracing (default: 5). Use 1 for no thinning. |
|
int |
|
Size of the square memory chunk for RAM management (default: 2048). |
|
int |
|
Number of CPU cores to use. Set to -1 to use all available cores (default: -1). |
|
float |
|
Lower tau energy in PeV. With –energy_max_pev, derives the decay-baseline distance window. |
|
float |
|
Upper tau energy in PeV. |
|
int |
|
Azimuths scanned per candidate in scan mode (default: 9). |
|
float |
|
Half-width of the azimuth fan about the aspect. Use -1 for a full 360 sweep (default: 60). |
|
float |
|
Lower edge of the accepted arrival elevation window (default: -3). |
|
float |
|
Upper edge of the accepted arrival elevation window (default: +3). |
|
int |
|
Elevation bins across the window (default: 12). Nearly free: cost scales with azimuths. |
|
float |
|
Column depth a direction must have to count, in g/cm2 (default: 0). |
|
flag |
|
Invert the test: accept directions that reach clear sky, for cosmic-ray style channels. |
|
float |
|
Radio band for the Fresnel clearance measurement, e.g. 50. Omitted skips the second pass. |
|
float |
|
Antenna height above ground, for the Fresnel measurement (default: 2). |
|
flag |
|
Measure Fresnel clearance from the antenna outward instead of skipping the near field. Included for study: the result is then dominated by ground beside the antenna rather than by intervening terrain. |
|
float |
|
Skip this much of the path when measuring Fresnel clearance (default: 500). Below ~500 m the measure is dominated by ground beside the antenna rather than by intervening terrain. |
|
flag |
|
Sample terrain profiles at pixel centres instead of interpolating. Faster, but treats terrain as blocky, which over-estimates how much it blocks a ray. |
|
float |
|
Rock overburden required along the arrival direction to reject atmospheric muons, in km (TAMBO quotes >4). A floor on column depth, not a band. |
|
float |
|
Geomagnetic declination, degrees east of north. Defaults to the Arequipa IGRF 2026 value (-6.9); supply the IGRF value for other regions. |
|
float |
|
Geomagnetic inclination, degrees, positive downward. Defaults to a centered-dipole estimate at the DEM’s own centre, so it follows the site automatically. |
|
flag |
|
Ignore the geomagnetic angle and weight all directions equally. |
|
radio / particle |
|
How atmospheric depth is scored. ‘radio’ is a maturity threshold, since emission comes from shower maximum and then propagates through transparent air. ‘particle’ is a band, since particle content dies after maximum (default: radio). |
|
float ×2 |
|
Atmospheric depth band scoring 1 in ‘particle’ mode, in g/cm2. Defaults to (X_max, 4*X_max) = (700, 2800), which suits a long path to a distant target. A short crossing gives far less: Colca supplies about 170 g/cm2, so a detector there sees a shower that is still developing and this band must be lowered or nothing scores. |
|
float |
|
Atmospheric depth at which the ‘radio’ maturity ramp reaches 1, in g/cm2 (default: X_max = 700). |
|
float |
|
Tau energy, in PeV, at which to score the probability that it decays in the gap with room left for a shower. Left out by default because the probability is strongly energy-dependent and one number cannot stand in for a spectrum. Matters most across a canyon: at 1 EeV the decay length is ~49 km against a ~3 km crossing. |
|
float |
|
How far to walk each profile, in km. Defaults to max_dist_km. Worth setting larger for a short-range search: column depth accumulates over the whole walk, so tying the two makes the reported depth a property of where the walk stopped rather than of the target’s thickness. |
|
str |
|
GeoJSON of road geometry to draw on the map, from |
|
str |
|
Named places to mark on the map. |
|
str |
|
What weights the spectrum-folded decay probability. |
|
str |
|
Two-column CSV of energy in PeV against relative detector response A(E), for the acceptance weightings. |
|
float |
|
Keep this percentage of viable candidates, ranked by score, instead of cutting at an absolute –min_score. Preferred: the default score is a product whose distribution piles up near zero, so an absolute threshold sits on a cliff, while a percentile is scale-free. |
|
flag |
|
In distributed mode, stop selecting sites once target_antennas is reached. Sites are ranked by capacity, so this reports the best sites for the array actually wanted rather than every patch of qualifying ground. |
|
float |
|
Ceiling on this process’s address space, in GiB. Defaults to 80%% of what the system reports available, so a search that outgrows the machine fails with MemoryError instead of inviting the OOM killer to choose a victim. 0 disables the cap. |
|
float |
|
Lower end of the tau energy range for the decay term. With –decay_energy_max_pev this folds the decay probability over a power-law spectrum, which is the defensible form: the probability runs over three decades across one experiment’s reach, so a single energy chooses the answer rather than approximating it. |
|
float |
|
Upper end of that range, in PeV. |
|
float ×1–2 |
|
Spectral index gamma in dN/dE ~ E^-gamma for the folded decay term (default: 2.0). Give one value to pin the spectrum, or two to marginalise uniformly over that range when the index is not known – a flat prior says so rather than pretending to a value. A softer spectrum weights low energies, where the tau decays readily, so it drives the term toward 1. |
|
float |
|
Path the shower needs after the tau decays, in metres (default: 3000). Used both by the decay term and as the far endpoint of the Fresnel clearance measurement. |
|
float |
|
Size of the morphological closing element that fills gaps between accepted pixels, in km. Defaults to antenna_spacing_km, which couples two unrelated things. Closing more than doubles the reported area on real terrain (measured 2.35x at Colca), so this is worth setting deliberately; 0 disables it. |
|
float |
|
Require the terrain a ray strikes to be at least this steep, measured along the arrival azimuth. Unset by default, which asks only that rock is present – true almost everywhere in the Andes. TAMBO’s tau exits a canyon wall, so this is what separates a canyon from a hillside. |
|
float |
|
Upper bound on the struck terrain’s slope along the arrival azimuth. Unset by default. Note a ceiling does not empty the result: a flat valley floor passes any ceiling, so this removes walls rather than everything. |
|
float |
|
When the shower band is derived from an energy range, the fraction of peak particle content that still counts as a usable shower (default: 0.1). Lower admits younger and older showers, so it widens the band and accepts narrower canyons. |
|
float |
|
How much deeper shower maximum sits per decade of primary energy, in g/cm2 (default: 55, the usual hadronic value; a purely electromagnetic cascade is nearer 85). |
|
float |
|
Gaisser-Hillas interaction length setting how fast the shower profile rises and falls, in g/cm2 (default: 70). |
|
float |
|
Fraction of the sky the azimuth fan and arrival window could accept that scores 0.5 (default: 0.076). Dimensionless and therefore portable: unlike |
|
float |
|
Accepted solid angle scoring 0.5, in steradians (default: 0.0167). This is a GRAND-scale value: an experiment looking across a canyon sees far more sky, and leaving it at the default saturates the term so it stops discriminating. It is calibrated against the reported solid angle, which depends on azimuth_half_width_deg and the elevation window – change either and this wants re-checking. |
|
float ×2 |
|
Exit-point distance band scoring 1, in metres. Defaults to the configured decay-baseline window. |
|
float |
|
Fresnel clearance ratio, in first-Fresnel radii, that scores 1 (default: 1.0). |
|
string |
|
Per-component weights for –score_composition weighted, as name=value pairs, e.g. ‘shower=2,solid_angle=1,depth=0.5’. Components not named default to weight 1; a weight of 0 excludes a component. Names are checked against the score components and a misspelling is refused, because a dropped weight changes every number in the run and leaves no trace in the output. |
|
float |
|
Neutrino interaction length for the Earth-chord attenuation term, g/cm2 (order 1e8 near an EeV). Omitted reports the chord without weighting by it. |
|
float |
|
Refraction k-factor for the RADIO path only (default: 4/3). Particle trajectories always use the true Earth radius, since neutrinos and taus are not refracted. |
|
float ×2 |
|
Column depth band scoring 1, in g/cm2. The tau must be produced and must escape, so this is a band, not a floor. |
|
product / mean / min |
|
How component scores combine (default: product). |
|
float |
|
Discard candidates scoring below this (default: 0, keep all). |
|
string |
|
Can be preset (‘lima’, ‘arequipa’) or a valid JSON string outlining custom exclusion zones. |
|
string |
|
Path to a raster mapping distance-to-roads (optional). |
|
float |
|
Maximum distance allowed from a road in km (default: 20.0). |
|
single / distributed |
|
‘single’ finds one monolithic site, ‘distributed’ allows sub-arrays. |
|
int |
|
Minimum required capacity for a sub-array to be considered valid (default: 500). |
|
float |
|
Minimum bound for site facing direction in degrees (0-360). |
|
float |
|
Maximum bound for site facing direction in degrees (0-360). |
|
string |
|
Cosmetic region name to print on the final visualization chart. |
|
flag |
|
Include this flag to generate a Google Earth KML file of the findings. |
|
flag |
|
Include this flag to skip printing the detailed explanatory text. |
|
flag |
|
Skip the plain-language summary of the run. It is printed by default, and saved as explanation.txt beside the results: what was found, which constraint set the size of the answer, what held the surviving sites back, and which of the numbers are assumptions rather than measurements. A results file can be re-explained at any time with explain.explain_results(). |
|
string |
|
Path to external JSON configuration file. |
|
string |
|
Base directory for outputs when a JSON config is supplied (default: ../output/). |
|
string |
|
Format of the saved map visual, e.g., png, pdf, svg (default: png). |
|
flag |
|
Include this flag to resume a previous run from the ray-tracing checkpoint. |
|
string |
|
Path to an output folder from a previously failed run to resume from the ray-tracing checkpoint. |
|
string |
|
Supply a filepath to generate a default JSON config template and exit. |
|
default / lima / arequipa |
|
Optional presets to inject when using –generate_config. |