{"id":39178177,"url":"https://github.com/observerly/skysolve","last_synced_at":"2026-01-17T22:30:40.973Z","repository":{"id":261393223,"uuid":"878114885","full_name":"observerly/skysolve","owner":"observerly","description":"observerly's zero-dependency pure Go plate solving library for astronomical images adhering to the WCS and SIP standards","archived":false,"fork":false,"pushed_at":"2025-04-06T15:11:27.000Z","size":44790,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-04-06T16:23:45.629Z","etag":null,"topics":["astrometry","astronomy","astrophotography","fits","plate-solving","wcs"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/observerly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-24T20:02:31.000Z","updated_at":"2025-04-06T15:11:29.000Z","dependencies_parsed_at":"2025-04-06T16:22:10.500Z","dependency_job_id":"40a62afa-4f75-4955-a65f-bd66868538b2","html_url":"https://github.com/observerly/skysolve","commit_stats":null,"previous_names":["observerly/skysolve"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/observerly/skysolve","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observerly%2Fskysolve","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observerly%2Fskysolve/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observerly%2Fskysolve/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observerly%2Fskysolve/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/observerly","download_url":"https://codeload.github.com/observerly/skysolve/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observerly%2Fskysolve/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28520641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T22:11:28.393Z","status":"ssl_error","status_checked_at":"2026-01-17T22:11:27.841Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["astrometry","astronomy","astrophotography","fits","plate-solving","wcs"],"created_at":"2026-01-17T22:30:40.209Z","updated_at":"2026-01-17T22:30:40.964Z","avatar_url":"https://github.com/observerly.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"![@observerly:skysolve](./.github/assets/banner.png)\n\n---\n\n![GitHub go.mod Go version (branch \u0026 subdirectory of monorepo)](https://img.shields.io/github/go-mod/go-version/observerly/skysolve/main?filename=go.mod\u0026label=Go)\n[![PkgGoDev](https://pkg.go.dev/badge/github.com/observerly/skysolve)](https://pkg.go.dev/github.com/observerly/skysolve)\n[![Go Report Card](https://goreportcard.com/badge/github.com/observerly/skysolve)](https://goreportcard.com/report/github.com/observerly/skysolve)\n[![SkySolve Actions Status](https://github.com/observerly/skysolve/actions/workflows/ci.yml/badge.svg)](https://github.com/observerly/skysolve/actions/workflows/ci.yml)\n\n### Introduction\n\nskysolve is a high-performance, zero-dependency Go library designed for plate solving astronomical images.\n\nWhile many plate solving algorithms operate under the assumption of having no prior knowledge of an image’s location in the sky, most observatories typically have a rough estimate of where their telescopes are pointed. skysolve leverages this existing pointing information to deliver faster and more accurate plate solving solutions.\n\nWhen provided with the approximate equatorial coordinates of an image and the detector’s field of view, skysolve can compute a World Coordinate System (WCS) for the image in under a second. This efficiency makes it an excellent choice for tasks in astrometry, photometry, and other astronomical image processing applications that demand high performance, including Space Situational Awareness (SSA) and Space Domain Awareness (SDA).\n\n### Prerequisites\n\n- [go](https://go.dev/) (\u003e= 1.21.*)\n\n### Why SkySolve?\n\n#### Exceptional Precision \u0026 Performance\n\nEnhanced Precision: Leverages prior pointing information to improve the accuracy of plate solving. When combined locally with the GAIA DR3 catalog, it can achieve sub-arcsecond precision within ~\u003c100ms on a modern CPU.\n\n#### Zero Dependencies\n\nSeamless Integration: Easily incorporate into your projects *without* the need to manage additional libraries or interoperating with C or C++ modules, third-party dependencies or Linux packages, simplifying usage, deployment and maintenance.\n  \n#### High Performance\n\nOptimised Speed: Facilitates real-time processing and analysis for efficient workflows, for example in Space Situational Awareness (SSA) and Space Domain Awareness (SDA).\n  \n#### Simple Imaging Polynomial (SIP)\n\nCorrects for optical distortions and projection effects, ensuring accurate representation of star positions in a standardized projection system.\n\n#### Adheres To FITS Standards\n\nWCS Compliant: Generates World Coordinate System (WCS) solutions that adhere to the FITS standard, ensuring compatibility with existing astronomical software and tools.\n\n\n### Usage\n\nSkySolve is designed to interoperate between IRIS, a FITs image processing library [observerly/iris](), and the GAIA DR3 catalog. The following example demonstrates how to use SkySolve to plate solve an astronomical image:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/observerly/iris/pkg/fits\"\n\t\"github.com/observerly/skysolve/pkg/astrometry\"\n\t\"github.com/observerly/skysolve/pkg/geometry\"\n\t\"github.com/observerly/skysolve/pkg/solve\"\n)\n\nfunc main() {\n\t// Attempt to open the file from the given filepath:\n\tfile, err := os.Open(\"\u003cPATH_TO_YOUR_IMAGE\u003e.fits\")\n\tif err != nil {\n\t\tfmt.Printf(\"failed to open file: %v\", err)\n\t\treturn\n\t}\n\t// Defer closing the file:\n\tdefer file.Close()\n\n\t// Assume an image of 2x2 pixels with 16-bit depth, and no offset:\n\tfit := fits.NewFITSImage(2, 0, 0, 65535)\n\n\t// Read in our exposure data into the image:\n\terr = fit.Read(file)\n\n\tif err != nil {\n\t\tfmt.Printf(\"failed to read fits file: %v\", err)\n\t}\n\n\t// Attempt to get the RA header from the FITS file:\n\tra, exists := fit.Header.Floats[\"RA\"]\n\tif !exists {\n\t\tfmt.Printf(\"ra header not found\")\n\t\treturn\n\t}\n\n\t// Attempt to get the Dec header from the FITS file:\n\tdec, exists := fit.Header.Floats[\"DEC\"]\n\tif !exists {\n\t\tfmt.Println(\"dec header not found\")\n\t\treturn\n\t}\n\n\t// Get the radius based on the known but approximate pixel scale of the image:\n\tradius := fov.GetRadialExtent(float64(fit.Header.Naxis1), float64(fit.Header.Naxis2), fov.PixelScale{X: pixelScaleX, Y: pixelScaleY})\n\n\t// Create a new GAIA service client:\n\tservice := catalog.NewCatalogService(catalog.GAIA, catalog.Params{\n\t\tLimit:     int(math.Ceil(42 / 0.5 * radius)),\n\t\tThreshold: 16, // Limiting Magntiude, filter out any stars that are magnitude 16 or above (fainter)\n\t})\n\t\n\teq := astrometry.ICRSEquatorialCoordinate{\n\t\tRA:  float64(ra.Value),\n\t\tDec: float64(dec.Value),\n\t}\n\n\t// Perform a radial search with the given center and radius, for all sources with a magnitude less than 10:\n\tsources, err := service.PerformRadialSearch(eq, radius)\n\tif err != nil {\n\t\tfmt.Printf(\"there was an error while performing the radial search: %v\", err)\n\t\treturn\n\t}\n\n\t// Attempt to create a new PlateSolver:\n\tsolver, err := solve.NewPlateSolver(solve.Params{\n\t\tData:                fit.Data,               // The exposure data from the fits image\n\t\tWidth:               int(fit.Header.Naxis1), // The width of the image\n\t\tHeight:              int(fit.Header.Naxis2), // The height of the image\n\t\tPixelScaleX:         pixelScaleX,            // The pixel scale in the x-axis\n\t\tPixelScaleY:         pixelScaleY,            // The pixel scale in the y-axis\n\t\tADU:                 fit.ADU,                // The analog-to-digital unit of the image\n\t\tExtractionThreshold: 16,                     // Extract a minimum of 20 of the brightest stars\n\t\tRadius:              16,                     // 16 pixels radius for the star extraction\n\t\tSigma:               2.5,                    // 8 pixels sigma for the Gaussian kernel\n\t})\n\tif err != nil {\n\t\tfmt.Printf(\"there was an error while creating the plate solver: %v\", err)\n\t\treturn\n\t}\n\n\tsolver.Sources = append(solver.Sources, sources...)\n\n\t// Define the tolerances for the solver, we can adjust these as needed:\n\ttolerance := solve.ToleranceParams{\n\t\tQuadTolerance:           0.02,\n\t\tEuclidianPixelTolerance: 10,\n\t}\n\n\t// Extract the WCS solution, as well as the matches, from the solver:\n\twcs, matches, err := solver.Solve(tolerance, 3)\n\tif err != nil {\n\t\tfmt.Printf(\"an error occured while plate solving: %v\", err)\n\t\treturn\n\t}\n}\n```\n\n### Examples\n\nExamples can be found in the [./examples](./examples) directory:\n\n- [Astrometric Plate Solver](./examples/solve/main.go) - Plate solves a FITs image using the SkySolve library.\n- [Generated GAIA Sky](./examples/sky/main.go) - Generates a sky map of the GAIA DR2 \u0026 DR3 catalog, and returns a ZScale normalized image of the sky for your chosen parameters.\n\n### Algorithm \u0026 Methodology\n\n#### 1. Image Star Extraction\n\nThe process begins with extracting stars from the input image using a sophisticated star detection algorithm. This algorithm identifies bright spots that are potential stars, accurately determining their positions and intensities. These extracted data points form the foundation for subsequent matching and analysis.\n\n#### 2. Catalog Matching\n\nWith the stars extracted, the algorithm matches them against a comprehensive reference catalog containing star positions and magnitudes. By comparing the spatial arrangement and brightness of the detected stars with those in the catalog, the algorithm identifies the best possible match, facilitating accurate plate solving.\n\n#### 3. Invariant Features\n\nInvariant geometric features are crucial for robust image registration and plate solving, particularly when astronomical images inhabit a vast hypothesis space in which exhaustive searches are impractical. Our approach employs a continuous geometric hashing technique, selecting four stars (a “quad”), defining a local coordinate system using the two most widely separated stars, and mapping the remaining two stars’ positions into a four-dimensional hash code. This code is invariant under translation, rotation, and scaling, and it is resilient to small positional noise. Furthermore, because the codes are well-distributed for uniformly scattered stars, storing them in a vp-tree enables efficient retrieval of matching quads. By identifying these invariant features in both the image and the reference catalogue, the algorithm can rapidly and accurately align the datasets, ensuring a precise and consistent plate-solving solution.\n\n#### 4. Affine Transformations\n\nTo align the captured image with the reference catalog, the algorithm employs affine transformations. These transformations include rotation, which adjusts the image orientation to match the catalog; scaling, which normalizes the size differences between the image and reference data; and translation, which shifts the image position to align with the catalog coordinates. Affine transformations preserve points, straight lines, and planes, ensuring that the alignment process maintains the geometric integrity of the image.\n\n#### 5. Simple Imaging Polynomial (SIP) Integration\n\n**Simple Imaging Polynomial (SIP)** is employed to account for optical distortions and projection effects inherent in astronomical imaging systems. SIP uses polynomial coefficients to map pixel coordinates to world coordinates, enabling precise correction of image distortions. By modeling the relationship between pixel positions and celestial coordinates, SIP corrects for lens aberrations and other distortions. Additionally, applying SIP transformations ensures that the star positions are accurately represented in a standardized projection system, facilitating reliable catalog matching.\n\n---\n\n### To Do\n\n- [x] For an approximate `{ ra, dec }` and a known field of view, compute the corresponding HEALPix cell for N side 128 and 256 to ensure fast and efficient index lookups for a given equatorial coordinate space region.\n- [ ] Implement an offline-first index of HEALPix cells for the GAIA DR2 \u0026 DR3 catalog.\n- [ ] Implement an online way to retrieve all notable objects from SIMBAD in an image region.\n- [ ] Improve robustness, accuracy and precision of the plate solving algorithm down to a precision of +/- 2 degrees in RA and Dec from the approximate known coordinates.\n- [ ] Implement a way to retrieve the approximate coordinates of an image from the FITS header.\n\n---\n\n### License\n\nMozilla Public License 2.0\n\nPermissions of this weak copyleft license are conditioned on making available source code of licensed files and modifications of those files under the same license (or in certain cases, one of the GNU licenses). Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. However, a larger work using the licensed work may be distributed under different terms and without source code for files added in the larger work.\n\n### Special Acknowledgements\n\nAs always, human knowledge is built on the shoulders of giants. This project would not be possible without the work of the following individuals and organizations:\n\n- [astrometry.net](https://aa.usno.navy.mil/software/novas/novas_info.php) - The original and best, blind plate solving software that this project is based on.\n- [Twirl](https://github.com/lgrcia/twirl) - Modern, astrometric plate solving package for Python.\n- [The GAIA DR3 Catalog](https://www.cosmos.esa.int/web/gaia/dr3) - The most accurate and precise sky source catalog in existence.\n- [The Sloan Digital Sky Survey](https://www.sdss.org/) - The most comprehensive digital sky survey in existence.\n- [HEALPix](https://healpix.sourceforge.io/) - The Hierarchical Equal Area isoLatitude Pixelization of the sphere, without which, indexing of the night sky would not be possible.\n\n### Standing On The Shoulders Of Giants\n\nTo the Harvard Computers, Annie Jump Cannon, Antonia Maury, Henrietta Leavitt, Evelyn Leland, Anna Winlock, Muriel Mussells Seyfert, Cecilia Payne-Gaposchkin, Williamina Fleming and Florence Cushman.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobserverly%2Fskysolve","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fobserverly%2Fskysolve","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobserverly%2Fskysolve/lists"}