{"action":"created","comment":{"url":"https://api.github.com/repos/lsst/meas_astrom/pulls/comments/96900497","pull_request_review_id":17498230,"id":96900497,"diff_hunk":"@@ -0,0 +1,598 @@\n+// -*- lsst-C++ -*-\n+\n+// Astrometry.net include files...\n+extern \"C\" {\n+#include \"astrometry/solver.h\"\n+#include \"astrometry/index.h\"\n+#include \"astrometry/multiindex.h\"\n+#include \"astrometry/starkd.h\"\n+#include \"astrometry/fitsioutils.h\"\n+#include \"astrometry/fitstable.h\"\n+#include \"astrometry/log.h\"\n+#include \"astrometry/tic.h\"\n+#include \"astrometry/healpix.h\"\n+\n+#undef ATTRIB_FORMAT\n+#undef FALSE\n+#undef TRUE\n+\n+#undef logdebug\n+#undef debug\n+}\n+\n+#include <memory>\n+#include <set>\n+#include <vector>\n+#include <string>\n+#include <sstream>\n+\n+#include <pybind11/pybind11.h>\n+#include <pybind11/stl.h>\n+#include \"boost/format.hpp\"\n+\n+#include \"lsst/utils/pybind11.h\"\n+#include \"lsst/meas/astrom/detail/utils.h\"\n+#include \"lsst/base.h\"\n+#include \"lsst/log/Log.h\"\n+#include \"lsst/daf/persistence.h\"\n+#include \"lsst/daf/base/Persistable.h\"\n+#include \"lsst/daf/base/PropertyList.h\"\n+#include \"lsst/pex/exceptions.h\"\n+#include \"lsst/afw/table.h\"\n+#include \"lsst/afw/image/Wcs.h\"\n+#include \"lsst/afw/image/TanWcs.h\"\n+#include \"lsst/afw/geom.h\"\n+#include \"lsst/afw/coord.h\"\n+\n+namespace py = pybind11;\n+using namespace pybind11::literals;\n+\n+namespace lsst {\n+namespace meas {\n+namespace astrom {\n+\n+namespace {\n+\n+LOG_LOGGER _log = LOG_GET(\"meas.astrom.astrometry_net\");\n+\n+struct multiindex_deleter {\n+    void operator()(multiindex_t* m) {\n+        multiindex_free(m);\n+    }\n+};\n+\n+struct solver_deleter {\n+    void operator()(solver_t* m) {\n+        solver_free(m);\n+    }\n+};\n+\n+struct timer_baton {\n+    solver_t* s;\n+    double timelimit;\n+};\n+\n+static time_t timer_callback(void* baton) {\n+    struct timer_baton* tt = static_cast<struct timer_baton*>(baton);\n+    solver_t* solver = tt->s;\n+    if (solver->timeused > tt->timelimit)\n+        solver->quit_now = 1;\n+    return 1;\n+}\n+\n+/*\n+ * A thin C++ wrapper around astrometry_net's multiindex_t struct.\n+ *\n+ * This provide memory management and a few methods used by LSST.\n+ */\n+class MultiIndex {\n+public:\n+    /**\n+     * Construct a MultiIndex from an astrometry.net multi-index file\n+     */\n+    MultiIndex(std::string const & filepath) : _multiindex(multiindex_new(filepath.c_str())) {\n+        if (!_multiindex) {\n+            std::ostringstream os;\n+            os << \"Could not read multi-index star file \" << filepath;\n+            throw LSST_EXCEPT(lsst::pex::exceptions::RuntimeError, os.str());\n+        }\n+    }\n+\n+    ~MultiIndex() {}\n+\n+    index_t * operator[](std::ptrdiff_t i) {\n+        auto cind = lsst::utils::cppIndex(multiindex_n(_multiindex.get()), i);\n+        return multiindex_get(_multiindex.get(), cind);\n+    }\n+\n+    /**\n+     * Add an index read from a file\n+     */\n+    void addIndex(std::string const & filepath, bool metadataOnly) {\n+        int const flags = metadataOnly ? INDEX_ONLY_LOAD_METADATA : 0;\n+        if (multiindex_add_index(_multiindex.get(), filepath.c_str(), flags)) {\n+            std::ostringstream os;\n+            os << \"Failed to read multiindex from \\\"\" << filepath << \"\\\"\"\n+                << \" with meatadaOnly=\" << metadataOnly;\n+            throw LSST_EXCEPT(lsst::pex::exceptions::RuntimeError, os.str());\n+        }\n+    }\n+\n+    // An index being within range is a property of the star kd-tree, hence of\n+    // the multi-index as a whole\n+    int isWithinRange(double ra, double dec, double radius) {\n+        return index_is_within_range(multiindex_get(_multiindex.get(), 0), ra, dec, radius);\n+    }\n+\n+    void unload() {\n+        multiindex_unload(_multiindex.get());\n+    }\n+\n+    std::string getName() {\n+        return _multiindex->fits->filename;\n+    }\n+\n+    int getLength() {\n+        return multiindex_n(_multiindex.get());\n+    }\n+\n+    void reload() {\n+        if (multiindex_reload_starkd(_multiindex.get())) {\n+            std::ostringstream os;\n+            os << \"Failed to reload multi-index star file \" << getName();\n+            throw LSST_EXCEPT(lsst::pex::exceptions::RuntimeError, os.str());\n+        }\n+    }\n+\n+private:\n+    std::unique_ptr<multiindex_t, multiindex_deleter> _multiindex;\n+};\n+\n+/**\n+ * A thin C++ wrapper around astrometry.net's solver_t struct.\n+ *\n+ * This provide memory management and methods used by LSST.\n+ */\n+class Solver {\n+public:\n+    Solver() : _solver(solver_new()) {}\n+\n+    ~Solver() {\n+        // Working around a bug in Astrometry.net: doesn't take ownership of the field.\n+        // unseemly familiarity with the innards... but valgrind-clean.\n+        starxy_free(_solver->fieldxy);\n+        _solver->fieldxy = NULL;\n+    }\n+\n+    /**\n+    Load reference objects in a region of the sky described by a center coordinate and a radius\n+\n+    @param[in] inds  list of star kd-trees from astrometry.net\n+    @param[in] ctrCoord  center of search region\n+    @param[in] radius  search radius\n+    @param[in] idCol  name of ID column in astrometry.net data\n+    @param[in] filterNameList  names of filters in astrometry.net data\n+    @param[in] magColList  names of magnitude columns in astrometry.net data\n+    @param[in] magErrColList  names of magnitude uncertainty (sigma) columns in astrometry.net data\n+    @param[in] starGalCol  name of \"starGal\" column (true if object is a star) in astrometry.net data\n+    @param[in] varCol  name of \"var\" column (true if brightness is variable) in astrometry.net data\n+    @param[in] uniqueIds  if true then only return unique IDs (the first of each seen)\n+\n+    Returned schema:\n+    - id\n+    - coord: sky position (an lsst::afw::coord::IcrsCoord)\n+    - centroid: centroid on some exposure, if relevant (an lsst::afw::geom::Point2D); returned value is not set\n+    - hasCentroid: if true then centroid has been set; returned value is false\n+    - <filterName>_flux: flux in the specified filter (double)\n+    - <filterName>_fluxSigma: flux uncertainty in the specified filter (double)\n+    - resolved (if starGalCol specified): true if object is not resolved\n+    - variable (if varCol specified): true if brightness is variable\n+    - photometric: true if not resolved (or starGalCol blank) and not variable (or varCol blank);\n+        note that if starGalCol and varCol both blank then all objects are claimed to be photometric\n+    */\n+    lsst::afw::table::SimpleCatalog getCatalog(\n+        std::vector<index_t*> inds,\n+        lsst::afw::coord::Coord const &ctrCoord,\n+        lsst::afw::geom::Angle const &radius,\n+        const char* idCol,\n+        std::vector<std::string> const& filterNameList,\n+        std::vector<std::string> const& magColList,\n+        std::vector<std::string> const& magErrColList,\n+        const char* starGalCol,\n+        const char* varCol,\n+        bool uniqueIds=true)\n+    {\n+        if ((filterNameList.size() != magColList.size()) || (filterNameList.size() != magErrColList.size())) {\n+            throw LSST_EXCEPT(lsst::pex::exceptions::InvalidParameterError,\n+                \"Filter name, mag column, and mag error column vectors must be the same length.\");\n+        }\n+        std::vector<lsst::meas::astrom::detail::MagColInfo> magColInfoList;\n+        for (size_t i=0; i<filterNameList.size(); ++i) {\n+            lsst::meas::astrom::detail::MagColInfo mc;\n+            mc.filterName = filterNameList[i];\n+            mc.magCol = magColList[i];\n+            mc.magErrCol = magErrColList[i];\n+            magColInfoList.push_back(mc);\n+        }\n+        return lsst::meas::astrom::detail::getCatalogImpl(inds, ctrCoord, radius,\n+            idCol, magColInfoList, starGalCol, varCol, uniqueIds);\n+    }\n+\n+    std::shared_ptr<lsst::daf::base::PropertyList> getSolveStats() {\n+        // Gather solve stats...\n+        auto qa = std::make_shared<daf::base::PropertyList>();\n+        // FIXME -- Ticket #1875 prevents dotted-names from working with toString().\n+        qa->set(\"meas_astrom*an*n_tried\", _solver->numtries);\n+        qa->set(\"meas_astrom*an*n_matched\", _solver->nummatches);\n+        qa->set(\"meas_astrom*an*n_scaleok\", _solver->numscaleok);\n+        qa->set(\"meas_astrom*an*n_cxdxcut\", _solver->num_cxdx_skipped);\n+        qa->set(\"meas_astrom*an*n_meanxcut\", _solver->num_meanx_skipped);\n+        qa->set(\"meas_astrom*an*n_radeccut\", _solver->num_radec_skipped);\n+        qa->set(\"meas_astrom*an*n_scalecut\", _solver->num_abscale_skipped);\n+        qa->set(\"meas_astrom*an*n_verified\", _solver->num_verified);\n+        qa->set(\"meas_astrom*an*time_used\", _solver->timeused);\n+        qa->set(\"meas_astrom*an*best_logodds\", _solver->best_logodds);\n+        if (_solver->best_index) {\n+            index_t* ind = _solver->best_index;\n+            qa->set(\"meas_astrom*an*best_index*id\", ind->indexid);\n+            qa->set(\"meas_astrom*an*best_index*hp\", ind->healpix);\n+            qa->set(\"meas_astrom*an*best_index*nside\", ind->hpnside);\n+            qa->set(\"meas_astrom*an*best_index*name\", std::string(ind->indexname));\n+        }\n+        if (_solver->have_best_match) {\n+            MatchObj* mo = &(_solver->best_match);\n+            std::string s = boost::str(boost::format(\"%i\") % mo->star[0]);\n+            for (int i=1; i<mo->dimquads; i++)\n+                s = s + boost::str(boost::format(\", %i\") % mo->star[i]);\n+            qa->set(\"meas_astrom*an*best_match*starinds\", s);\n+            qa->set(\"meas_astrom*an*best_match*coderr\", std::sqrt(mo->code_err));\n+            qa->set(\"meas_astrom*an*best_match*nmatch\", mo->nmatch);\n+            qa->set(\"meas_astrom*an*best_match*ndistract\", mo->ndistractor);\n+            qa->set(\"meas_astrom*an*best_match*nconflict\", mo->nconflict);\n+            qa->set(\"meas_astrom*an*best_match*nfield\", mo->nfield);\n+            qa->set(\"meas_astrom*an*best_match*nindex\", mo->nindex);\n+            qa->set(\"meas_astrom*an*best_match*nbest\", mo->nbest);\n+            qa->set(\"meas_astrom*an*best_match*logodds\", mo->logodds);\n+            qa->set(\"meas_astrom*an*best_match*parity\", mo->parity ? 0 : 1);\n+            qa->set(\"meas_astrom*an*best_match*nobjs\", mo->objs_tried);\n+        }\n+        return qa;\n+    }\n+\n+    std::shared_ptr<lsst::afw::image::Wcs> getWcs() {\n+        MatchObj* match = solver_get_best_match(_solver.get());\n+        if (!match)\n+            return std::shared_ptr<afw::image::Wcs>();\n+        tan_t* wcs = &(match->wcstan);\n+\n+        afw::geom::Point2D crpix(wcs->crpix[0], wcs->crpix[1]);\n+        std::shared_ptr<afw::coord::Coord const> crval\n+            (new afw::coord::Coord(wcs->crval[0] * afw::geom::degrees,\n+                                 wcs->crval[1] * afw::geom::degrees));\n+        return afw::image::makeWcs(*crval, crpix,\n+                                 wcs->cd[0][0], wcs->cd[0][1],\n+                                 wcs->cd[1][0], wcs->cd[1][1]);\n+    }\n+\n+    bool didSolve() {\n+        return solver_did_solve(_solver.get());\n+    }\n+\n+    void run(double cpulimit) {\n+        solver_log_params(_solver.get());\n+        struct timer_baton tt;\n+        if (cpulimit > 0.) {\n+            tt.s = _solver.get();\n+            tt.timelimit = cpulimit;\n+            _solver->userdata = &tt;\n+            _solver->timer_callback = timer_callback;\n+        }\n+        solver_run(_solver.get());\n+        if (cpulimit > 0.) {\n+            _solver->timer_callback = NULL;\n+            _solver->userdata = NULL;\n+        }\n+    }\n+\n+    double getQuadSizeLow() {\n+        double qlo,qhi;\n+        solver_get_quad_size_range_arcsec(_solver.get(), &qlo, &qhi);\n+        return qlo;\n+    }\n+    double getQuadSizeHigh() {\n+        double qlo,qhi;\n+        solver_get_quad_size_range_arcsec(_solver.get(), &qlo, &qhi);\n+        return qhi;\n+    }\n+\n+    /**\n+     * Add indices to the solver\n+     *\n+     * The indices are bare pointers whose memory is managed by the caller.\n+     * Typically the indices are owned by a MultiIndex object owned by the caller.\n+     */\n+    void addIndices(std::vector<index_t*> inds) {\n+        for (std::vector<index_t*>::iterator pind = inds.begin();\n+             pind != inds.end(); ++pind) {\n+            lsst::meas::astrom::detail::IndexManager man(*pind);\n+//            printf(\"Checking index \\\"%s\\\"\\n\", man.index->indexname);\n+            if (_solver->use_radec) {\n+                double ra,dec,radius;\n+                xyzarr2radecdeg(_solver->centerxyz, &ra, &dec);\n+                radius = distsq2deg(_solver->r2);\n+                if (!index_is_within_range(man.index, ra, dec, radius)) {\n+                                     //printf(\"Not within RA,Dec range\\n\");\n+                    continue;\n+                }\n+            }\n+            // qlo,qhi in arcsec\n+            double qlo, qhi;\n+            solver_get_quad_size_range_arcsec(_solver.get(), &qlo, &qhi);\n+            if (!index_overlaps_scale_range(man.index, qlo, qhi)) {\n+//                printf(\"Not within quad scale range\\n\");\n+                continue;\n+            }\n+//            printf(\"Adding index.\\n\");\n+            if (index_reload(man.index)) {\n+                throw LSST_EXCEPT(lsst::pex::exceptions::IoError,\n+                                  \"Failed to index_reload() an astrometry_net_data index file -- out of file descriptors?\");\n+            }\n+\n+            solver_add_index(_solver.get(), man.index);\n+        }\n+    }\n+\n+    /**\n+     * Set parity to flipped (if true) or normal (if false)\n+     */\n+    void setParity(bool p) {\n+        if (p)\n+            _solver->parity = PARITY_FLIP;\n+        else\n+            _solver->parity = PARITY_NORMAL;\n+    }\n+\n+    void setMatchThreshold(double t) {\n+        solver_set_keep_logodds(_solver.get(), t);\n+    }\n+\n+    void setPixelScaleRange(double lo, double hi) {\n+        _solver->funits_lower = lo;\n+        _solver->funits_upper = hi;\n+    }\n+\n+    void setRaDecRadius(double ra, double dec, double rad) {\n+        solver_set_radec(_solver.get(), ra, dec, rad);\n+    }\n+\n+    void setImageSize(int W, int H) {\n+        solver_set_field_bounds(_solver.get(), 0, W, 0, H);\n+        double hi = hypot(W,H);\n+        double lo = 0.1 * std::min(W,H);\n+        solver_set_quad_size_range(_solver.get(), lo, hi);\n+    }\n+\n+    void setMaxStars(int N) {\n+        _solver->endobj = N;\n+    }\n+\n+    void setStars(lsst::afw::table::SourceCatalog const & srcs, int x0, int y0) {\n+        // convert to Astrometry.net \"starxy_t\"\n+        starxy_free(_solver->fieldxy);\n+        const size_t N = srcs.size();\n+        starxy_t *starxy = starxy_new(N, true, false);\n+        for (size_t i=0; i<N; ++i) {\n+            double const x    = srcs[i].getX();\n+            double const y    = srcs[i].getY();\n+            double const flux = srcs[i].getPsfFlux();\n+            starxy_set(starxy, i, x - x0, y - y0);\n+            starxy_set_flux(starxy, i, flux);\n+        }\n+        // Sort the array\n+        starxy_sort_by_flux(starxy);\n+\n+        starxy_free(solver_get_field(_solver.get()));\n+        solver_free_field(_solver.get());\n+        solver_set_field(_solver.get(), starxy);\n+        solver_reset_field_size(_solver.get());\n+        // Find field boundaries and precompute kdtree\n+        solver_preprocess_field(_solver.get());\n+    }\n+\n+private:\n+    std::unique_ptr<solver_t, solver_deleter> _solver;\n+};\n+\n+/*\n+ * Wrap index_t\n+ *\n+ * This class has no constructor; the wrapper is only used for objects returned by MultiIndex::operator[]\n+ */\n+void declareIndex(py::module & mod) {\n+    py::class_<index_t> cls(mod, \"index_t\");\n+\n+    cls.def(\"overlapsScaleRange\", [](index_t & self, double qlo, double qhi) {\n+        return index_overlaps_scale_range(&self, qlo, qhi);\n+    }, \"qlow\"_a, \"qhigh\"_a);\n+    cls.def(\"reload\", [](index_t & self) {\n+        if(index_reload(&self)) {\n+            std::ostringstream os;\n+            os << \"Failed to reload multi-index file \" << self.indexname;\n+            throw LSST_EXCEPT(lsst::pex::exceptions::RuntimeError, os.str());\n+        }\n+    });\n+\n+    cls.def_readonly(\"indexid\", &index_t::indexid);\n+    cls.def_readonly(\"healpix\", &index_t::healpix);\n+    cls.def_readonly(\"hpnside\", &index_t::hpnside);\n+    cls.def_readonly(\"nstars\", &index_t::nstars);\n+    cls.def_readonly(\"nquads\", &index_t::nquads);\n+}\n+\n+/**\n+ * Wrap MultiIndex, a thin shim around multiindex_t\n+ */\n+void declareMultiIndex(py::module & mod) {\n+    py::class_<MultiIndex> cls(mod, \"MultiIndex\");\n+\n+    cls.def(py::init<std::string const &>(), \"filepath\"_a);\n+\n+    cls.def(\"__getitem__\", &MultiIndex::operator[], \"i\"_a, py::return_value_policy::reference_internal,\n+            py::is_operator());\n+\n+    cls.def(\"addIndex\", &MultiIndex::addIndex, \"filepath\"_a, \"metadataOnly\"_a);\n+    cls.def(\"isWithinRange\", &MultiIndex::isWithinRange, \"ra\"_a, \"dec\"_a, \"radius\"_a);\n+    cls.def(\"unload\", &MultiIndex::unload);\n+    cls.def_property_readonly(\"name\", &MultiIndex::getName);\n+    cls.def(\"__len__\", &MultiIndex::getLength);\n+    cls.def(\"reload\", &MultiIndex::reload);\n+}\n+\n+/**\n+ * Wrap Solver, a thin shim around solver_t\n+ */\n+void declareSolver(py::module & mod) {\n+    py::class_<Solver> cls(mod, \"Solver\");\n+\n+    cls.def(py::init<>());\n+\n+    /**\n+    Load reference objects in a region of the sky described by a center coordinate and a radius\n+\n+    @param[in] inds  list of star kd-trees from astrometry.net\n+    @param[in] ctrCoord  center of search region\n+    @param[in] radius  search radius\n+    @param[in] idCol  name of ID column in astrometry.net data\n+    @param[in] filterNameList  names of filters in astrometry.net data\n+    @param[in] magColList  names of magnitude columns in astrometry.net data\n+    @param[in] magErrColList  names of magnitude uncertainty (sigma) columns in astrometry.net data\n+    @param[in] starGalCol  name of \"starGal\" column (true if object is a star) in astrometry.net data\n+    @param[in] varCol  name of \"var\" column (true if brightness is variable) in astrometry.net data\n+    @param[in] uniqueIds  if true then only return unique IDs (the first of each seen)\n+\n+    Returned schema:\n+    - id\n+    - coord: sky position (an lsst::afw::coord::IcrsCoord)\n+    - centroid: centroid on some exposure, if relevant (an lsst::afw::geom::Point2D); returned value is not set\n+    - hasCentroid: if true then centroid has been set; returned value is false\n+    - <filterName>_flux: flux in the specified filter (double)\n+    - <filterName>_fluxSigma: flux uncertainty in the specified filter (double)\n+    - resolved (if starGalCol specified): true if object is not resolved\n+    - variable (if varCol specified): true if brightness is variable\n+    - photometric: true if not resolved (or starGalCol blank) and not variable (or varCol blank);\n+        note that if starGalCol and varCol both blank then all objects are claimed to be photometric\n+    */\n+    cls.def(\"getCatalog\", &Solver::getCatalog,\n+            \"inds\"_a, \"ctrCoord\"_a, \"radius\"_a, \"idCol\"_a, \"filterNameList\"_a, \"magColList\"_a,\n+            \"magErrColList\"_a, \"starGalCol\"_a, \"varCol\"_a, \"uniqueIds\"_a=true);\n+    cls.def(\"getSolveStats\", &Solver::getSolveStats);\n+    cls.def(\"getWcs\", &Solver::getWcs);\n+    cls.def(\"didSolve\", &Solver::didSolve);\n+    cls.def(\"run\", &Solver::run, \"cpulimit\"_a);\n+    cls.def(\"getQuadSizeLow\", &Solver::getQuadSizeLow);\n+    cls.def(\"getQuadSizeHigh\", &Solver::getQuadSizeHigh);\n+    cls.def(\"addIndices\", &Solver::addIndices, \"indices\"_a);\n+    cls.def(\"setParity\", &Solver::setParity, \"setParity\", \"parity\"_a);\n+    cls.def(\"setMatchThreshold\", &Solver::setMatchThreshold, \"threshold\"_a);\n+    cls.def(\"setPixelScaleRange\", &Solver::setPixelScaleRange, \"low\"_a, \"high\"_a);\n+    cls.def(\"setRaDecRadius\", &Solver::setRaDecRadius, \"ra\"_a, \"dec\"_a, \"rad\"_a);\n+    cls.def(\"setImageSize\", &Solver::setImageSize, \"width\"_a, \"height\"_a);\n+    cls.def(\"setMaxStars\", &Solver::setMaxStars, \"maxStars\"_a);\n+    cls.def(\"setStars\", &Solver::setStars, \"sourceCat\"_a, \"x0\"_a, \"y0\"_a);\n+}\n+\n+static void an_log_callback(void* baton, enum log_level level,\n+                            const char* file,\n+                            int line, const char* func, const char* format,\n+                            va_list va) {\n+    // translate between logging levels\n+    int levelmap[5];\n+    levelmap[LOG_NONE ] = LOG_LVL_FATAL;\n+    levelmap[LOG_ERROR] = LOG_LVL_FATAL;\n+    levelmap[LOG_MSG  ] = LOG_LVL_INFO;\n+    levelmap[LOG_VERB ] = LOG_LVL_DEBUG;\n+    levelmap[LOG_ALL  ] = LOG_LVL_DEBUG;\n+    int lsstlevel = levelmap[level];\n+\n+    va_list vb;\n+    // find out how long the formatted string will be\n+    va_copy(vb, va);\n+    const int len = vsnprintf(NULL, 0, format, va) + 1; // \"+ 1\" for the '\\0'\n+    va_end(va);\n+    // allocate a string of the appropriate length\n+    char msg[len];\n+    (void)vsnprintf(msg, len, format, vb);\n+    va_end(vb);\n+\n+    // trim trailing \\n\n+    if (msg[len-2] == '\\n') {\n+        msg[len-2] = '\\0';\n+    }\n+\n+    // not using the LOGS macro because the original location info is wanted\n+    if (_log.isEnabledFor(lsstlevel)) {\n+        _log.logMsg(log4cxx::Level::toLevel(lsstlevel),\n+                    log4cxx::spi::LocationInfo(file, func, line), msg);\n+    }\n+}\n+\n+static void start_an_logging() {\n+    // NOTE, this has to happen before the log_use_function!\n+    log_init(LOG_VERB);\n+    log_use_function(an_log_callback, NULL);\n+    log_to(NULL);\n+}\n+\n+static void stop_an_logging() {\n+    log_use_function(NULL, NULL);\n+    log_to(stdout);\n+}\n+\n+void finalize() {\n+    stop_an_logging();\n+}\n+\n+/**\n+ * Calculate the distance from coordinates to a healpix\n+ *\n+ * Note that this assumes that the astrometry.net catalog reference system is ICRS.\n+ */\n+lsst::afw::geom::Angle healpixDistance(int hp, int nside, lsst::afw::coord::Coord const& coord) {\n+    lsst::afw::coord::IcrsCoord icrs = coord.toIcrs();\n+    return lsst::afw::geom::Angle(healpix_distance_to_radec(hp, nside, icrs.getLongitude().asDegrees(),\n+                                                            icrs.getLatitude().asDegrees(), NULL),\n+                                  lsst::afw::geom::degrees);\n+}\n+\n+}  // namespace lsst::meas::astrom::<anonymous>\n+\n+PYBIND11_PLUGIN(_astrometry_net) {\n+    py::module mod(\"_astrometry_net\",\n+                   \"Python wrapper for astrometry_net code needed by meas_astrom\");\n+\n+    // code that is run at import time\n+    fits_use_error_system();\n+    start_an_logging();\n+\n+    mod.def(\"healpixDistance\", &healpixDistance, \"hp\"_a, \"nside\"_a, \"coord\"_a);\n+\n+    mod.def(\"an_log_init\", [](int level) {\n+        log_init((log_level)level);\n+    }, \"level\"_a);\n+\n+    mod.def(\"an_log_get_level\", []() {\n+        return (int)log_get_level();\n+    });\n+    mod.def(\"an_log_set_level\", [](int level) {\n+        log_set_level((log_level) level);","path":"python/lsst/meas/astrom/astrometry_net.cc","position":587,"original_position":587,"commit_id":"37b6f6d84b415d9ed8b7ca60ff1d0a12515ffd69","original_commit_id":"1e012b5ae40aa500576e20bf350ddd4293cd1178","user":{"login":"pschella","id":211955,"avatar_url":"https://avatars.githubusercontent.com/u/211955?v=3","gravatar_id":"","url":"https://api.github.com/users/pschella","html_url":"https://github.com/pschella","followers_url":"https://api.github.com/users/pschella/followers","following_url":"https://api.github.com/users/pschella/following{/other_user}","gists_url":"https://api.github.com/users/pschella/gists{/gist_id}","starred_url":"https://api.github.com/users/pschella/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pschella/subscriptions","organizations_url":"https://api.github.com/users/pschella/orgs","repos_url":"https://api.github.com/users/pschella/repos","events_url":"https://api.github.com/users/pschella/events{/privacy}","received_events_url":"https://api.github.com/users/pschella/received_events","type":"User","site_admin":false},"body":"`static_cast`","created_at":"2017-01-19T16:36:07Z","updated_at":"2017-01-19T16:36:07Z","html_url":"https://github.com/lsst/meas_astrom/pull/52#discussion_r96900497","pull_request_url":"https://api.github.com/repos/lsst/meas_astrom/pulls/52","_links":{"self":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/comments/96900497"},"html":{"href":"https://github.com/lsst/meas_astrom/pull/52#discussion_r96900497"},"pull_request":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/52"}}},"pull_request":{"url":"https://api.github.com/repos/lsst/meas_astrom/pulls/52","id":100221663,"html_url":"https://github.com/lsst/meas_astrom/pull/52","diff_url":"https://github.com/lsst/meas_astrom/pull/52.diff","patch_url":"https://github.com/lsst/meas_astrom/pull/52.patch","issue_url":"https://api.github.com/repos/lsst/meas_astrom/issues/52","number":52,"state":"open","locked":false,"title":"Wrap meas_astrom with pybind11: DM-8458","user":{"login":"r-owen","id":874176,"avatar_url":"https://avatars.githubusercontent.com/u/874176?v=3","gravatar_id":"","url":"https://api.github.com/users/r-owen","html_url":"https://github.com/r-owen","followers_url":"https://api.github.com/users/r-owen/followers","following_url":"https://api.github.com/users/r-owen/following{/other_user}","gists_url":"https://api.github.com/users/r-owen/gists{/gist_id}","starred_url":"https://api.github.com/users/r-owen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/r-owen/subscriptions","organizations_url":"https://api.github.com/users/r-owen/orgs","repos_url":"https://api.github.com/users/r-owen/repos","events_url":"https://api.github.com/users/r-owen/events{/privacy}","received_events_url":"https://api.github.com/users/r-owen/received_events","type":"User","site_admin":false},"body":"","created_at":"2017-01-05T01:50:40Z","updated_at":"2017-01-19T16:36:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"841bb7d0a3e877b3ad4c116dd6f0923eaa468244","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/lsst/meas_astrom/pulls/52/commits","review_comments_url":"https://api.github.com/repos/lsst/meas_astrom/pulls/52/comments","review_comment_url":"https://api.github.com/repos/lsst/meas_astrom/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lsst/meas_astrom/issues/52/comments","statuses_url":"https://api.github.com/repos/lsst/meas_astrom/statuses/37b6f6d84b415d9ed8b7ca60ff1d0a12515ffd69","head":{"label":"lsst:tickets/DM-8458","ref":"tickets/DM-8458","sha":"37b6f6d84b415d9ed8b7ca60ff1d0a12515ffd69","user":{"login":"lsst","id":1134504,"avatar_url":"https://avatars.githubusercontent.com/u/1134504?v=3","gravatar_id":"","url":"https://api.github.com/users/lsst","html_url":"https://github.com/lsst","followers_url":"https://api.github.com/users/lsst/followers","following_url":"https://api.github.com/users/lsst/following{/other_user}","gists_url":"https://api.github.com/users/lsst/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst/subscriptions","organizations_url":"https://api.github.com/users/lsst/orgs","repos_url":"https://api.github.com/users/lsst/repos","events_url":"https://api.github.com/users/lsst/events{/privacy}","received_events_url":"https://api.github.com/users/lsst/received_events","type":"Organization","site_admin":false},"repo":{"id":23013353,"name":"meas_astrom","full_name":"lsst/meas_astrom","owner":{"login":"lsst","id":1134504,"avatar_url":"https://avatars.githubusercontent.com/u/1134504?v=3","gravatar_id":"","url":"https://api.github.com/users/lsst","html_url":"https://github.com/lsst","followers_url":"https://api.github.com/users/lsst/followers","following_url":"https://api.github.com/users/lsst/following{/other_user}","gists_url":"https://api.github.com/users/lsst/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst/subscriptions","organizations_url":"https://api.github.com/users/lsst/orgs","repos_url":"https://api.github.com/users/lsst/repos","events_url":"https://api.github.com/users/lsst/events{/privacy}","received_events_url":"https://api.github.com/users/lsst/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lsst/meas_astrom","description":"LSST Data Management: astrometric measurement algorithms","fork":false,"url":"https://api.github.com/repos/lsst/meas_astrom","forks_url":"https://api.github.com/repos/lsst/meas_astrom/forks","keys_url":"https://api.github.com/repos/lsst/meas_astrom/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lsst/meas_astrom/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lsst/meas_astrom/teams","hooks_url":"https://api.github.com/repos/lsst/meas_astrom/hooks","issue_events_url":"https://api.github.com/repos/lsst/meas_astrom/issues/events{/number}","events_url":"https://api.github.com/repos/lsst/meas_astrom/events","assignees_url":"https://api.github.com/repos/lsst/meas_astrom/assignees{/user}","branches_url":"https://api.github.com/repos/lsst/meas_astrom/branches{/branch}","tags_url":"https://api.github.com/repos/lsst/meas_astrom/tags","blobs_url":"https://api.github.com/repos/lsst/meas_astrom/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lsst/meas_astrom/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lsst/meas_astrom/git/refs{/sha}","trees_url":"https://api.github.com/repos/lsst/meas_astrom/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lsst/meas_astrom/statuses/{sha}","languages_url":"https://api.github.com/repos/lsst/meas_astrom/languages","stargazers_url":"https://api.github.com/repos/lsst/meas_astrom/stargazers","contributors_url":"https://api.github.com/repos/lsst/meas_astrom/contributors","subscribers_url":"https://api.github.com/repos/lsst/meas_astrom/subscribers","subscription_url":"https://api.github.com/repos/lsst/meas_astrom/subscription","commits_url":"https://api.github.com/repos/lsst/meas_astrom/commits{/sha}","git_commits_url":"https://api.github.com/repos/lsst/meas_astrom/git/commits{/sha}","comments_url":"https://api.github.com/repos/lsst/meas_astrom/comments{/number}","issue_comment_url":"https://api.github.com/repos/lsst/meas_astrom/issues/comments{/number}","contents_url":"https://api.github.com/repos/lsst/meas_astrom/contents/{+path}","compare_url":"https://api.github.com/repos/lsst/meas_astrom/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lsst/meas_astrom/merges","archive_url":"https://api.github.com/repos/lsst/meas_astrom/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lsst/meas_astrom/downloads","issues_url":"https://api.github.com/repos/lsst/meas_astrom/issues{/number}","pulls_url":"https://api.github.com/repos/lsst/meas_astrom/pulls{/number}","milestones_url":"https://api.github.com/repos/lsst/meas_astrom/milestones{/number}","notifications_url":"https://api.github.com/repos/lsst/meas_astrom/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lsst/meas_astrom/labels{/name}","releases_url":"https://api.github.com/repos/lsst/meas_astrom/releases{/id}","deployments_url":"https://api.github.com/repos/lsst/meas_astrom/deployments","created_at":"2014-08-16T07:45:25Z","updated_at":"2016-10-26T22:14:35Z","pushed_at":"2017-01-19T01:48:40Z","git_url":"git://github.com/lsst/meas_astrom.git","ssh_url":"git@github.com:lsst/meas_astrom.git","clone_url":"https://github.com/lsst/meas_astrom.git","svn_url":"https://github.com/lsst/meas_astrom","homepage":"http://dm.lsst.org/","size":26059,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"lsst:tickets/DM-8467","ref":"tickets/DM-8467","sha":"ac3adf1525407e5a0f8c3538f2c463469c894f56","user":{"login":"lsst","id":1134504,"avatar_url":"https://avatars.githubusercontent.com/u/1134504?v=3","gravatar_id":"","url":"https://api.github.com/users/lsst","html_url":"https://github.com/lsst","followers_url":"https://api.github.com/users/lsst/followers","following_url":"https://api.github.com/users/lsst/following{/other_user}","gists_url":"https://api.github.com/users/lsst/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst/subscriptions","organizations_url":"https://api.github.com/users/lsst/orgs","repos_url":"https://api.github.com/users/lsst/repos","events_url":"https://api.github.com/users/lsst/events{/privacy}","received_events_url":"https://api.github.com/users/lsst/received_events","type":"Organization","site_admin":false},"repo":{"id":23013353,"name":"meas_astrom","full_name":"lsst/meas_astrom","owner":{"login":"lsst","id":1134504,"avatar_url":"https://avatars.githubusercontent.com/u/1134504?v=3","gravatar_id":"","url":"https://api.github.com/users/lsst","html_url":"https://github.com/lsst","followers_url":"https://api.github.com/users/lsst/followers","following_url":"https://api.github.com/users/lsst/following{/other_user}","gists_url":"https://api.github.com/users/lsst/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst/subscriptions","organizations_url":"https://api.github.com/users/lsst/orgs","repos_url":"https://api.github.com/users/lsst/repos","events_url":"https://api.github.com/users/lsst/events{/privacy}","received_events_url":"https://api.github.com/users/lsst/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lsst/meas_astrom","description":"LSST Data Management: astrometric measurement algorithms","fork":false,"url":"https://api.github.com/repos/lsst/meas_astrom","forks_url":"https://api.github.com/repos/lsst/meas_astrom/forks","keys_url":"https://api.github.com/repos/lsst/meas_astrom/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lsst/meas_astrom/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lsst/meas_astrom/teams","hooks_url":"https://api.github.com/repos/lsst/meas_astrom/hooks","issue_events_url":"https://api.github.com/repos/lsst/meas_astrom/issues/events{/number}","events_url":"https://api.github.com/repos/lsst/meas_astrom/events","assignees_url":"https://api.github.com/repos/lsst/meas_astrom/assignees{/user}","branches_url":"https://api.github.com/repos/lsst/meas_astrom/branches{/branch}","tags_url":"https://api.github.com/repos/lsst/meas_astrom/tags","blobs_url":"https://api.github.com/repos/lsst/meas_astrom/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lsst/meas_astrom/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lsst/meas_astrom/git/refs{/sha}","trees_url":"https://api.github.com/repos/lsst/meas_astrom/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lsst/meas_astrom/statuses/{sha}","languages_url":"https://api.github.com/repos/lsst/meas_astrom/languages","stargazers_url":"https://api.github.com/repos/lsst/meas_astrom/stargazers","contributors_url":"https://api.github.com/repos/lsst/meas_astrom/contributors","subscribers_url":"https://api.github.com/repos/lsst/meas_astrom/subscribers","subscription_url":"https://api.github.com/repos/lsst/meas_astrom/subscription","commits_url":"https://api.github.com/repos/lsst/meas_astrom/commits{/sha}","git_commits_url":"https://api.github.com/repos/lsst/meas_astrom/git/commits{/sha}","comments_url":"https://api.github.com/repos/lsst/meas_astrom/comments{/number}","issue_comment_url":"https://api.github.com/repos/lsst/meas_astrom/issues/comments{/number}","contents_url":"https://api.github.com/repos/lsst/meas_astrom/contents/{+path}","compare_url":"https://api.github.com/repos/lsst/meas_astrom/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lsst/meas_astrom/merges","archive_url":"https://api.github.com/repos/lsst/meas_astrom/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lsst/meas_astrom/downloads","issues_url":"https://api.github.com/repos/lsst/meas_astrom/issues{/number}","pulls_url":"https://api.github.com/repos/lsst/meas_astrom/pulls{/number}","milestones_url":"https://api.github.com/repos/lsst/meas_astrom/milestones{/number}","notifications_url":"https://api.github.com/repos/lsst/meas_astrom/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lsst/meas_astrom/labels{/name}","releases_url":"https://api.github.com/repos/lsst/meas_astrom/releases{/id}","deployments_url":"https://api.github.com/repos/lsst/meas_astrom/deployments","created_at":"2014-08-16T07:45:25Z","updated_at":"2016-10-26T22:14:35Z","pushed_at":"2017-01-19T01:48:40Z","git_url":"git://github.com/lsst/meas_astrom.git","ssh_url":"git@github.com:lsst/meas_astrom.git","clone_url":"https://github.com/lsst/meas_astrom.git","svn_url":"https://github.com/lsst/meas_astrom","homepage":"http://dm.lsst.org/","size":26059,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/52"},"html":{"href":"https://github.com/lsst/meas_astrom/pull/52"},"issue":{"href":"https://api.github.com/repos/lsst/meas_astrom/issues/52"},"comments":{"href":"https://api.github.com/repos/lsst/meas_astrom/issues/52/comments"},"review_comments":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/52/comments"},"review_comment":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/52/commits"},"statuses":{"href":"https://api.github.com/repos/lsst/meas_astrom/statuses/37b6f6d84b415d9ed8b7ca60ff1d0a12515ffd69"}}}}
{"actor":{"display_login":"pschella"}}