What's In My Sky
Your observing planner for tonight — and the next 365 nights. Enter where and when you'll be observing, pick which catalogs to search (Messier, NGC, IC, Caldwell, Abell, Sharpless, LBN, LDN, PNG/PK, Arp, plus your own), and WIMS computes a prioritized list of what's actually worth your time: altitude, azimuth, minutes to transit, degrees from the Moon, and a 0-100 observability score that weights altitude quality, moon avoidance, and imaging window into a single number. Right-click any object for a visibility plot, SIMBAD details, planet separations, a weekly score chart, or a full-year analysis of its best observing dates.
The observability score. Every object gets a score from 0 to 100 built from three weighted components: 40% altitude quality (fraction of astronomical night above 30°), 35% moon avoidance (combined angular separation and lunar phase), and 25% imaging window (fraction of astronomical night above 20°). Choose "Best Nightly Score" as the sort mode and the top-ranked list is the right order to actually shoot in — a 95-score target at your meridian under a new moon beats a 60-score target low in the murk, even if the latter is "famous". Score badges go ❌ Bad < 35 → 🔴 Poor < 55 → 🟡 Marginal < 75 → 🟢 Good < 90 → ⭐ Excellent.
1. The three-panel layout
- Left panel — Location & Time (lat, lon, date, time, timezone, min altitude), Catalog checkboxes, RA/Dec format toggle (decimal vs H:M:S), Local Sidereal Time readout, and a Moon widget with phase %, phase image, rise, transit, and set times.
- Middle panel — Object Type filter: a scrollable checkbox list populated from the catalog's actual Type column. All/None toggle at the top, selection persists across sessions. Useful for narrowing "every PGC galaxy fainter than mag 15" out of the way.
- Right panel — Toolbar (Add Object, Save CSV, Search Object, Horizon, Field Rotation, Settings), sort-mode row (Closest to Transit vs Best Nightly Score), and the results tree with 14 columns including Score and Community.
Right-click any row. That's where the depth lives. The context menu offers: Altitude/Visibility plot, Weekly Observability Score chart, Full Year Analysis, SIMBAD details, Planet Separations, Finder Chart, Field Rotation (pre-filled with the row's alt/az/RA/Dec/date), Aladin, AstroBin search, and View in Seti Astro Community Atlas.
2. Location & Time inputs
- Latitude — decimal degrees. Accepts suffix N/S (
30N, 45.5S) or signed values.
- Longitude — decimal degrees, East positive, West negative. Accepts suffix E/W (
87W, 45E) or signed values.
- Date — YYYY-MM-DD. Defaults to today.
- Time — HH:MM local time (observing time, not now).
- Time Zone — all IANA timezones (
America/Chicago, Europe/Berlin, Australia/Sydney, etc.).
- Min Altitude — floor cut in degrees; objects below this are dropped from the list. 20° is a reasonable default for imaging, 30° for serious work. Overridden per-azimuth by the custom horizon if one is defined (whichever is higher wins).
Timezone / longitude sanity check. If your timezone's central meridian is more than 45° from your longitude, or they have opposite signs, WIMS warns and offers an auto-correction. The common case: you entered a bare "87" for longitude in the US and your timezone is America/Chicago (UTC-6, central meridian ~90°W) — WIMS flips the sign to -87 for you, since 87 would place you in Pakistan. Always double-check the status bar after Calculate if you entered longitude without an E/W suffix.
3. Catalogs and type filters
Check the catalogs you want to include. SASpro ships with: Messier (110 objects), NGC, IC, Caldwell, Abell (galaxy clusters), Sharpless, LBN (Lynds Bright Nebulae), LDN (Lynds Dark Nebulae), PNG (planetary nebulae including PK cross-reference), Arp (peculiar galaxies), plus User (your own objects).
PK ↔ PN-G cross-reference. WIMS speaks both the modern PN-G galactic-coordinate designation (PN-G 064.6+48.2) and the older Perek-Kohoutek catalog name (PK 064.6+48.2). Type either in the search box or object name field; both resolve to the same object. SIMBAD lookups try both forms too, so a stubborn PN that resolves under PK but not PN-G still gets its details.
The middle-panel Object Types filter further narrows by what the catalog's Type column says — Galaxy, Open Cluster, Globular Cluster, Planetary Nebula, Reflection Nebula, Emission Nebula, Supernova Remnant, Dark Nebula, and so on. The filter is additive with catalogs: checking "Messier" and type "Galaxy" gives you just M31, M51, M63, M64, M65, M66, M74, M77, M81, M82, M83, M84, M85, M86, M87, M88, M89, M90, M91, M94, M95, M96, M98, M99, M100, M101, M102, M104, M105, M106, M108, M109, M110.
4. Sort modes
- Closest to Transit (default) — ordered by absolute minutes to transit. Everything the sky is pointing at right now, roughly. Fast to compute.
- Best Nightly Score — ordered by the full observability score for the full night. More expensive (one astropy coordinate transform per candidate across 144 time samples), but gives you the actually-worth-shooting ordering. The tooltip warns "Filter catalogs/types recommended — can be slow" because scoring every PGC galaxy at every timestep adds up.
Chunked parallel scoring. When Best Nightly Score is active, WIMS splits the candidate list into chunks and runs them across a thread pool sized to your CPU. Each chunk builds its own astropy state (no shared SkyCoord objects, no GIL fighting) and returns a slice of the score array. For a 500-object list on a typical quad-core laptop, that's a few seconds instead of a minute.
5. The results tree
14 columns, sortable by any:
- Name — primary catalog identifier.
- RA — right ascension, decimal degrees or H:M:S depending on the RA/Dec format toggle.
- Dec — declination, decimal degrees or D:M:S.
- Altitude — degrees above horizon at the specified time.
- Azimuth — compass bearing (0 = N, 90 = E, 180 = S, 270 = W).
- Min→Transit — minutes until the object transits the meridian.
- B/A Transit — Before or After. If After, the object is already past meridian for tonight.
- °from Moon — angular separation from the Moon.
- Alt Name — secondary catalog name (e.g. "Andromeda Galaxy" for M31).
- Type — object type (Galaxy, Planetary Nebula, Open Cluster...).
- Magnitude — visual magnitude where known.
- Size (arcmin) — angular size, usually major × minor. Abell clusters show galaxy count instead.
- Score — 0-100 observability score for tonight.
- Community — ✓ (green) when the object falls inside a published footprint on the Seti Astro Community Atlas (fetched in the background from the atlas CDN; the mark appears a few seconds after Calculate finishes).
Sort by any column (click the header). Size column is smart: 12.0 x 8.5 sorts by the larger dimension, Num Galaxies: 51 sorts by galaxy count, plain numbers sort numerically.
6. The Weekly Score chart (right-click → Weekly Observability Score…)
A ±7-day chart of the object's observability score centered on your target date. Each day gets a blue bar whose height is the score (0-100), overlaid with the score as a yellow line + markers, with the moon-phase % drawn as a semi-transparent grey backdrop behind each day (new moon = transparent, full = opaque). The target date is marked with a red dashed vertical line. The subtitle names the weighting breakdown — 40% altitude quality · 35% moon avoidance · 25% imaging window — so you can see at a glance whether a bad night is bad because the moon is up or because the object is too low.
7. Full Year Analysis (right-click → Full Year Analysis…)
The deep one. A 365-day score sweep starting from your current date, split across up to 12 parallel threads (one per month, roughly). The result opens in a 4-tab dialog:
- Monthly Overview — bar chart of per-month average score, colored by score (red = poor, green = good), with a yellow dashed line marking the best month. A faint daily-score line overlays the whole year to show within-month variation. Best night and best month are called out in the header with badges.
- Best Night (date) — altitude curve for the single best scoring date, with twilight bands, moon trace, horizon limit overlay (if a custom horizon is set), peak-altitude marker, azimuth compass labels along the track, and the imaging-window hours.
- Tonight (date) — the same curve for tonight, for comparison.
- SIMBAD Details — main ID, object type, V/B magnitude, spectral type, angular size, radial velocity, redshift, coordinates.
Bottom buttons give you Full Visibility Dialog, Open in Aladin, Finder Chart, and Field Rotation (all pre-filled from the best night so the rotation night-curve plots immediately).
8. The Visibility dialog (right-click → Altitude / Visibility Plot…)
Full altitude curve for the selected object on the selected date, in local time with the plot running noon → midnight → noon (so a transit at 02:00 shows cleanly). Four tabs:
- Altitude & Visibility — the plot itself. The sky background fades through five brightness bands at the actual sun-altitude crossings: daylight → civil twilight (sun > -6°) → nautical twilight (-6° to -12°) → astronomical twilight (-12° to -18°) → astronomical night (-18°). Linear interpolation between adjacent samples places the band edges exactly on the crossings, so twilight bands line up with the real transition times. Object altitude is yellow, moon altitude dashed grey. If a custom horizon is defined, the blocked zone shows as a red translucent fill overlay. Compass labels (N, NE, ENE, E…) are drawn along the object track at ~2-hour intervals.
- Rise / Transit / Set — rise/transit/set times, peak altitude, imaging window in hours (above 20° during astro night), moon interference hours during the imaging window, and the night's astronomical and nautical twilight boundaries. Circumpolar objects say so; always-below-horizon objects say so.
- SIMBAD Details — background query, cached. Returns main ID, type, V/B magnitude, spectral type, angular size, radial velocity, redshift. Tries multiple name variants (bare, no-space, catalog-prefix-normalized, PK↔PN-G, Abell→ACO expansion) plus a TAP ADQL fallback for anything query_object misses.
- Planet Separations — angular distance from each major planet (Mars, Jupiter, Saturn, Venus, Mercury, Uranus, Neptune) at local midnight. A ⚠ close! flag appears when separation < 5°.
Field Rotation integration. Right-click an object → Field Rotation opens the Alt/Az Field Rotation calculator pre-filled with that object's current alt/az, RA/Dec, date, and your location/timezone. In the Full Year dialog, Field Rotation uses the best night at the moment of peak altitude (transit) — the worst-case single-point rotation rate for the night, which is the right number to budget your subs against.
9. The Custom Horizon Editor (🏔 Horizon…)
Where WIMS stops being generic and starts knowing your actual site. Opens an interactive azimuth-vs-altitude plot:
Interaction. Left-click empty area to add a point · drag a point to move it · right-click to delete · the 0° and 360° (north) endpoints stay in sync automatically · a point placed within 5° of north snaps to 0° exactly. Buttons: Reset to Flat (clear everything), Clear All, Load from File…, Save to File…, Apply & Close.
The horizon profile linearly interpolates between your points, with wrap-around from 360° back to 0° handled correctly. Once defined, the horizon:
- Replaces the single Min Altitude threshold in Calculate — each object is only kept if its altitude at its azimuth exceeds max(horizon(az), min_alt).
- Draws as a red filled overlay in every visibility and best-night altitude plot.
- Changes the scoring: the "above 30°" quality bar becomes "above max(horizon(az), 30°)" at each timestep, and the "above 20°" imaging window becomes "above max(horizon(az), 20°)". A trailhead you can only see while it clears your east-facing treeline gets a lower score than a northern-circumpolar one you can see all night.
- Shows an "Effective above horizon" hours figure alongside the "Imaging window" figure in the Full Year altitude plots.
File formats. WIMS loads and saves two formats: Stellarium horizon (.txt or .hrz, space-separated, # comments allowed) and WIMS CSV (.csv, comma-separated with azimuth/altitude headers). If your site has duplicate-azimuth points (a chimney edge at az 30° with heights 30° and 33°), the loader keeps the maximum altitude at that azimuth — because that's the blocking height the object actually has to clear.
10. The Object Search dialog (toolbar → Search Object…)
Full-catalog search by name, with smart matching:
- Catalog prefix normalization —
M42, M 42, m42 all match the Orion Nebula. Same for NGC, IC, UGC, PGC, ACO, LBN, LDN, SH2, PNG/PN-G, PK, Abell, Arp.
- Alt-name search — matches on both the primary name and the alt-name column, so
Andromeda Galaxy finds M31, Pleiades finds M45, Squid Nebula finds OU4 (if in your catalog).
- Partial match —
abell 1242 matches the full "Abell 1242" entry even if the catalog spells it "ACO 1242".
- Manual RA/Dec… button for objects not in any catalog. Accepts decimal degrees,
5h26m31.2s / 60d03m00s, or 5:26:31.2 / 60:03:00 colon-separated; Dec prefix − for south.
Double-click a search result (or hit Analyse Selected Object…) to run the full-year sweep on it. This is the backdoor to full-year analysis for objects that aren't in your tree — you don't need to Calculate first, just search and go.
11. Workflow recipes
Tonight's plan. Fill in location + current date and time. Check Messier, NGC, IC, Caldwell. Set Min Altitude to 30. Pick Best Nightly Score as the sort mode. Click Calculate. The top 10 rows are your target list, ordered by observability. Right-click the top one → Altitude / Visibility Plot to see when it transits and what the moon is doing.
When is X best this year? Click Search Object, type the name, double-click the result. The full-year sweep runs and the Monthly Overview tab tells you which month to plan the trip for. If the best month is a lunar month away from now, go check the Weekly Score chart on a few candidate dates to pick the actual night.
Dark-sky trip planning. At your home site, run Calculate for the Saturday you're leaving. Note the top five scored targets. Open each in Full Year Analysis — if "Best Month" is a different one, that target isn't the right pick for this trip. Keep the ones whose Best Month matches the trip.
Narrowband-friendly fallback list. If the Moon is up, score plummets for most broadband targets but barely moves for narrowband (the moon-separation weight hits hard, phase weight less so). Keep a second catalog filter list of just Sharpless + LBN + LDN + bright PNe. When tonight's score sheet is dominated by moon penalties, switch to that list — the top scores there will be your narrowband picks.
Site survey — before buying property. Draw up a custom horizon from a candidate observing site using the Horizon Editor (walk a 360° circle noting treeline heights). Run Full Year Analysis on your usual targets. The "Effective above horizon" figure on the Best Night plot tells you how much usable time each one actually gets at that site. Compare across sites.
User-object round-trip. Add Object → enter a name, RA, Dec. It saves to ~/saspro_user_objects.csv and shows up under the User catalog filter forever. Share that CSV with a friend and they can drop it into their own saspro_user_objects.csv to inherit your target list.
Imaging ethics: avoid stepping on recent work. After Calculate finishes, wait a few seconds for the Community column to populate. A ✓ there means the Seti Astro Community Atlas already has a published image at that location. Treat it as a signal, not a stop — but it's useful context when picking what to shoot.
12. Credits
Written by Franklin Marek as part of
SetiAstro Suite. All celestial-mechanics calculations use
astropy (Astropy Collaboration et al.). Timezones via
pytz. SIMBAD lookups via
astroquery.simbad, with a TAP ADQL fallback for catalog identifiers
query_object doesn't resolve. Plots via
pyqtgraph. Catalog sources: NGC/IC from Dreyer / Steinicke, Caldwell from Moore, Abell / ACO from Abell & Corwin, Sharpless from Sharpless (1959), LBN from Lynds (1965), LDN from Lynds (1962), PNG from the
Strasbourg-ESO Catalogue of Galactic Planetary Nebulae with Perek-Kohoutek (1967) cross-reference. The Seti Astro Community Atlas is maintained at
setiastro-atlas on Backblaze B2. Lunar phase images from the SetiAstro Suite icon set.
Technical details
13.1. The observability score
Three weighted components, each clamped to [0, 1], combined and scaled to [0, 100]:
S = 100 \cdot (0.40 \cdot s_{\mathrm{alt}} + 0.35 \cdot s_{\mathrm{moon}} + 0.25 \cdot s_{\mathrm{win}})
Each component is computed from a 144-sample grid spanning noon to the following noon (10-minute resolution). The altitude quality and imaging window components are both fractions of astronomical night where the object clears a given altitude:
s_{\mathrm{alt}} = |\{t : \mathrm{sun}(t) < -18^\circ \wedge \mathrm{alt}(t) \geq 30^\circ\}| \; / \; |\{t : \mathrm{sun}(t) < -18^\circ\}|
s_{\mathrm{win}} = |\{t : \mathrm{sun}(t) < -18^\circ \wedge \mathrm{alt}(t) \geq 20^\circ\}| \; / \; |\{t : \mathrm{sun}(t) < -18^\circ\}|
If no astronomical night exists at the given latitude on the given date (polar summer), both components are 0 and the score is 0.
13.2. The moon component
Combines angular separation and lunar phase so a full moon 30° away is worse than a crescent 30° away, and a new moon anywhere is near-perfect:
\sigma = \min(1,\, \Delta_{\mathrm{moon}} / 90^\circ)
\phi = 1 - P/100
s_{\mathrm{moon}} = \sigma \cdot (0.5 + 0.5 \phi)
where Δmoon is the angular separation of the target from the moon (clamped at 90°; farther doesn't help) and P is lunar phase as a percentage of illumination. The (0.5 + 0.5·φ) term floors the moon contribution at half-value even for a full moon — because angular separation alone still buys you something — and ramps linearly to full value at new moon.
13.3. Lunar phase from solar elongation
Phase percentage is derived from the Moon-Sun elongation using the standard illumination formula:
P = \mathrm{round}\left( \frac{1 - \cos(E)}{2} \cdot 100 \right)
where E is the angular separation of the Moon and Sun as seen from Earth. Waxing vs waning is determined by whether the elongation is increasing or decreasing six hours later; this picks the correct phase image from the 23-image set (new, waxing/waning crescent 1-5, first/last quarter, waxing/waning gibbous 1-4, full).
13.4. Local sidereal time and minutes to transit
LST is computed from astropy's Time.sidereal_time("apparent", lon), which accounts for nutation and precession:
\mathrm{LST} = \mathrm{GMST} + \lambda_{\mathrm{obs}}
An object transits (crosses the meridian) when its RA equals the LST. The time to transit, in hours, is:
\Delta t_{\mathrm{transit}} = ((\alpha / 15^\circ) - \mathrm{LST}_{\mathrm{h}}) \;\mathrm{mod}\; 24
WIMS converts to minutes and splits "Before/After": if Δt > 12 h, the object transited earlier and the displayed value becomes 24 − Δt (as a positive "minutes since transit"), with the B/A column marked "After":
\Delta t > 12\ \mathrm{h}\;\Rightarrow\;\mathrm{After\ transit},\ \mathrm{display} = 24 - \Delta t
13.5. The custom horizon
Internally stored as a sorted list of (azimuth, altitude) control points. The horizon altitude at an arbitrary azimuth is linear interpolation between the two adjacent points, with wrap-around from 360° back to 0° handled by injecting an implicit point at (360°, h(0°)) if the user's rightmost point sits below 360°, and (0°, h(360°)) if the leftmost sits above 0°:
h(\mathrm{az}) = \mathrm{interp}(\mathrm{az};\, \{(a_i, h_i)\})
The effective minimum altitude at each azimuth is the elementwise max of the horizon and the configured min_alt floor:
\mathrm{alt}_{\mathrm{eff}}(\mathrm{az}) = \max(h(\mathrm{az}),\, \mathrm{alt}_{\min})
When scoring with a custom horizon, the quality and window masks become per-timestep horizon-aware:
q(t) = \mathrm{alt}(t) \geq \max(h(\mathrm{az}(t)),\, 30^\circ)
w(t) = \mathrm{alt}(t) \geq \max(h(\mathrm{az}(t)),\, 20^\circ)
so a target that peaks at 45° altitude but spends most of its time behind an eastern treeline gets a lower score than its naive altitude curve would suggest.
13.6. Twilight thresholds
Standard astronomical definitions, used throughout the plot bands and the scoring night mask:
\mathrm{sun} < -6^\circ\ \mathrm{(civil)}\quad -12^\circ\ \mathrm{(nautical)}\quad -18^\circ\ \mathrm{(astro)}
Visibility plots show four distinct bands (daylight, civil, nautical, astro night) with their edges placed at the exact linear-interpolated crossing times — not sample-grid-rounded — so the bands align with the real transition moments.
13.7. Azimuth-to-compass labeling
Compass labels along the altitude plot's object track use 16-point rose spacing:
i = \lfloor (\mathrm{az} + 11.25^\circ) / 22.5^\circ \rfloor \;\mathrm{mod}\; 16
with the +11.25° shift centering each 22.5° arc on its label (so az = 0° reads "N", not "NNE"). Labels are placed every ~2 hours along the track, with a 1.5-hour minimum horizontal spacing to prevent crowding, and skipped when altitude is below −5° (object below horizon).
13.8. Timezone vs longitude consistency
WIMS compares your timezone's UTC offset ΔtUTC against your longitude λ. The timezone's "central meridian" is:
\lambda_c = 15^\circ \cdot \Delta t_{\mathrm{UTC}}
A warning fires if the signs disagree or if |λ − λc| > 45°. For bare-sign-mismatch cases (user entered a bare "87" longitude without E/W suffix and the sign is wrong for their timezone), WIMS auto-flips the sign — this handles the common US-user "I put in positive 87 but I'm in Chicago" case. For other inconsistencies the warning is informational and the calculation proceeds with the entered values.
13.9. SIMBAD name resolution
The SIMBAD lookup tries each of these name forms until one hits:
- Primary name as-is.
- Primary name with whitespace removed.
- Primary name with catalog-prefix space-normalized (
NGC891 → NGC 891).
- Alt name variants (same three transforms).
- PK ↔ PN-G cross-references:
PN-G 064.6+48.2 is tried as PK 064.6+48.2 and PK064.6+48.2, and vice versa.
- Abell → ACO expansion for Abell cluster names.
- If all
query_object attempts fail, a direct TAP ADQL query against SIMBAD's basic table joined on ident catches identifiers query_object doesn't surface.
Returns main_id, otype, V/B magnitude, radial velocity, redshift, spectral type, and major/minor galdim axes where available. Missing fields render as — rather than failing the whole lookup.
13.10. Chunked parallel scoring
For both the tonight-ranking (Best Nightly Score sort) and the full-year analysis, scoring is split across a thread pool. Chunk sizes are computed as:
- Workers:
max(4, min(cpu_count // 2, N)) — half the CPUs by default, floored at 4, capped at the candidate count.
- Chunk size:
max(10, ⌈N / workers⌉) — big enough that per-chunk setup cost (astropy state construction) is amortized.
- For the 365-day sweep, chunks are per-month (31-day slices), run in up to 12 parallel threads.
Each worker constructs its own pytz, Time, EarthLocation, and AltAz frame locally — no sharing of astropy objects between threads, which would be undefined behavior under the GIL for the C-extension bits. Results are stitched back together in deterministic order.
13.11. Catalog loading
Two independent sources:
- Bundled catalog at
data/catalogs/celestial_catalog.csv, loaded read-only. ISO-8859-1 encoding (handles accented author names in alt-name fields).
- User catalog at
~/saspro_user_objects.csv, UTF-8, created on first Add Object…. A one-time migration from an older in-home-dir celestial_catalog.csv (where user objects used to be appended into the bundled catalog) extracts rows with Catalog == USER and seeds this file.
The two frames are concatenated at load time with user rows always marked Catalog = "User", so the User checkbox in the UI exclusively shows your own objects regardless of what the row data said.
13.12. The Community Atlas footprint test
The atlas catalog.json is fetched in a background thread with a 3-attempt retry (1.5 s between tries, 8 s per-request timeout). Each entry carries either four corner RA/Dec points or a center + FOV width/height. For the corner form, a point (RA, Dec) is "inside" when:
min(dec_corners) ≤ dec ≤ max(dec_corners), and
- the RA bracket contains the point, with wraparound at 0°/360° handled by checking whether the corner-span exceeds 180° (which signals a wrap-crossing footprint, where "inside" means
ra ≥ ra_min OR ra ≤ ra_max).
Fast enough to test thousands of catalog rows against a small atlas index per Calculate. The tick appears in the Community column a few seconds after Calculate finishes.