{"action":"created","comment":{"url":"https://api.github.com/repos/lsst/meas_astrom/pulls/comments/25624353","id":25624353,"diff_hunk":"@@ -0,0 +1,241 @@\n+from __future__ import absolute_import, division, print_function\n+import math\n+import numpy\n+\n+import lsst.afw.coord as afwCoord\n+import lsst.afw.geom as afwGeom\n+import lsst.afw.table as afwTable\n+import lsst.pex.config as pexConfig\n+import lsst.pipe.base as pipeBase\n+from .astromLib import matchOptimisticB, MatchOptimisticBControl\n+\n+__all__ = [\"MatchOptimisticBTask\", \"MatchOptimisticBConfig\"]\n+\n+class MatchOptimisticBConfig(pexConfig.Config):\n+    maxMatchDistArcSec = pexConfig.RangeField(\n+        doc = \"Maximum radius for matching sources to reference objects (arcsec)\",\n+        dtype = float,\n+        default = 1,\n+        min = 0,\n+    )\n+    numBrightStars = pexConfig.RangeField(\n+        doc = \"Number of bright stars to use\",\n+        dtype = int,\n+        default = 50,\n+        min = 2,\n+    )\n+    minMatchedPairs = pexConfig.RangeField(\n+        doc = \"Minimum number of matched pairs; see also minFracMatchedPairs\",\n+        dtype = int,\n+        default = 30,\n+        min = 2,\n+    )\n+    minFracMatchedPairs = pexConfig.RangeField(\n+        doc = \"Minimum number of matched pairs as a fraction of the smaller of \"\n+            \"the number of reference stars or the number of good sources; \"\n+            \"the actual minimum is the smaller of this value or minMatchedPairs\",\n+        dtype = float,\n+        default = 0.3,\n+        min = 0,\n+        max = 1,\n+    )\n+    maxOffsetPix = pexConfig.RangeField(\n+        doc = \"Maximum offset between sources and position reference objects (pixel)\",\n+        dtype = int,\n+        default = 300,\n+        max = 4000,\n+    )\n+    rotationAllowedInRad = pexConfig.RangeField(\n+        doc = \"Rotation angle allowed between sources and position reference objects (radian)\",\n+        dtype = float,\n+        default = 0.02,\n+        max = 0.1,\n+    )\n+    angleDiffFrom90 = pexConfig.RangeField(\n+        doc = \"Difference of angle between x and y from 90 degree allowed (degree)\",\n+        dtype = float,\n+        default = 0.2,\n+        max = 45.0,\n+    )\n+    numPointsForShape = pexConfig.Field(\n+        doc = \"number of points to define a shape for matching\",\n+        dtype = int,\n+        default = 6,\n+    )\n+    maxDeterminant = pexConfig.Field(\n+        doc = \"maximum determinant of linear transformation matrix for a usable solution\",\n+        dtype = float,\n+        default = 0.02,\n+    )\n+\n+def isCleanSource(s):\n+    \"\"\"!Return True if star has known centroid and not at the edge\n+    \"\"\"\n+    return (numpy.isfinite(s.getX()) and numpy.isfinite(s.getY()) and not s.get(\"base_PixelFlags_flag_edge\"))\n+\n+def isGoodSource(s):\n+    \"\"\"!Return True if star is \"clean\" (see isCleanSource) and not saturated\n+    \"\"\"\n+    # FIXME: should use Key to get flag (but then we'd need schema in advance)\n+    return (isCleanSource(s) and not s.getCentroidFlag() and not s.get(\"base_PixelFlags_flag_saturated\"))\n+\n+\n+class MatchOptimisticBTask(pipeBase.Task):\n+    \"\"\"!Match sources to position reference objects using optimistic pattern matching\n+\n+    See the matchOptimisticB.h for information about the matching algorithm.\n+    \"\"\"\n+    ConfigClass = MatchOptimisticBConfig\n+    _DefaultName = \"matchObjectsToSources\"\n+\n+    @staticmethod\n+    def _purgeM31M32Stars(starCat):\n+        \"\"\"!Remove stars from a catalog that are too near the center of M31 or M32\n+        \"\"\"\n+        retStarCat = type(starCat)(starCat.table)\n+        m31 = afwCoord.Coord(afwGeom.Point2D(10.681, 41.269))\n+        m32 = afwCoord.Coord(afwGeom.Point2D(10.675, 40.864))\n+        for star in starCat:\n+            if (star.getCoord().angularSeparation(m31).asArcminutes() > 2.2 and\n+                star.getCoord().angularSeparation(m32).asArcminutes() > 1.0):\n+                retStarCat.append(star)\n+        return retStarCat\n+\n+    @pipeBase.timeMethod\n+    def matchObjectsToSources(self, refCat, sourceCat, wcs, refFluxField):\n+        \"\"\"!Match sources to position reference stars\n+\n+        @param[in] refCat  catalog of reference objects that overlap the exposure\n+        @param[in] sourceCat  catalog of sources found on an exposure\n+        @param[in] wcs  estimated WCS\n+        @param[in] refFluxField  field of refCat to use for flux\n+        @return an lsst.pipe.base.Struct with fields:\n+        - matchList  a list of matches, an instance of lsst.afw.table.ReferenceMatch\n+        \"\"\"\n+        import lsstDebug\n+        debug = lsstDebug.Info(__name__)\n+\n+        preNumObj = len(refCat)\n+        refCat = self._purgeM31M32Stars(refCat)\n+        numRefObj = len(refCat)\n+\n+        if self.log: self.log.info(\"Purged %d reference stars too near M31/M32, leaving %d stars\" % \\\n+            (preNumObj - numRefObj, numRefObj))\n+\n+        # cleanSourceCat: sources that are good but may be saturated\n+        numSources = len(sourceCat)\n+        cleanSourceCat = afwTable.SourceCatalog(sourceCat.table)\n+        cleanSourceCat.extend(s for s in sourceCat if isCleanSource(s))\n+        numCleanSources = len(cleanSourceCat)\n+        self.log.info(\"Purged %d unsuable sources, leaving %d clean sources\" % \\\n+            (numSources - numCleanSources, numCleanSources))\n+\n+        # goodSourceCat: sources that are good and not saturated\n+        goodSources = afwTable.SourceCatalog(sourceCat.table)\n+        goodSources.extend(s for s in cleanSourceCat if isGoodSource(s))\n+        numGoodSources = len(goodSources)\n+        self.log.info(\"Purged %d saturated sources, leaving %d good sources\" % \\\n+            (numCleanSources - numGoodSources, numGoodSources))\n+\n+        del sourceCat # avoid accidentally using sourceCat; use cleanSourceCat or goodSources from now on\n+        \n+        self.log.info(\"Matching to %d/%d good input sources\" % (len(goodSources), len(cleanSourceCat)))\n+\n+        minMatchedPairs = min(self.config.minMatchedPairs,\n+                            int(self.config.minFracMatchedPairs * min([len(refCat), len(goodSources)])))\n+\n+        # match clean (possibly saturated) sources and then purge the saturated sources from the match list\n+        matchList0 = self.doMatch(\n+            refCat = refCat,\n+            sourceCat = cleanSourceCat,\n+            wcs = wcs,\n+            refFluxField = refFluxField,\n+            numCleanSources = numCleanSources,\n+            minMatchedPairs = minMatchedPairs,\n+            verbose = debug.verbose,\n+        )\n+        if matchList0 is not None:\n+            matchList0 = [m for m in matchList0 \n+                           if (not m.second.getCentroidFlag() and \n+                               not m.second.get(\"base_PixelFlags_flag_saturated\"))]\n+        else:\n+            matchList0 = []\n+\n+        # match good (unsaturated) sources\n+        if len(refCat) > len(cleanSourceCat) - len(goodSources):\n+            matchList1 = self.doMatch(\n+                refCat = refCat,\n+                sourceCat = goodSources,\n+                wcs = wcs,\n+                refFluxField = refFluxField,\n+                numCleanSources = numCleanSources,\n+                minMatchedPairs = minMatchedPairs,\n+                verbose = debug.verbose,\n+            )\n+        else:\n+            matchList1 = []\n+        if matchList1 == None:\n+            matchList1 = []\n+\n+        if len(matchList0) == 0 and len(matchList1) == 0:\n+            raise RuntimeError(\"Unable to match sources\")\n+\n+        # Adopt matchList with more matches\n+        if len(matchList0) > len(matchList1):\n+            matchList = matchList0\n+        else:\n+            matchList = matchList1\n+\n+        if self.log: self.log.info(\"Matched %d sources\" % len(matchList))\n+        if len(matchList) < minMatchedPairs:\n+            self.log.warn(\"Number of matches is smaller than request\")\n+\n+        return pipeBase.Struct(\n+            matchList = matchList,\n+        )\n+\n+    @pipeBase.timeMethod\n+    def doMatch(self, refCat, sourceCat, wcs, refFluxField, numCleanSources, minMatchedPairs, verbose):\n+        \"\"\"!Match sources to position reference stars","path":"python/lsst/meas/astrom/matchOptimisticB.py","position":199,"original_position":199,"commit_id":"9f9e02d838104c4556bbf32cf9076025b70e1b81","original_commit_id":"6eec1fee45170e4a1c2c4f263a876106fb676e00","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":"Yes. I'll make it so and try to clarify the documentation.","created_at":"2015-03-02T18:39:31Z","updated_at":"2015-03-02T18:39:31Z","html_url":"https://github.com/lsst/meas_astrom/pull/3#discussion_r25624353","pull_request_url":"https://api.github.com/repos/lsst/meas_astrom/pulls/3","_links":{"self":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/comments/25624353"},"html":{"href":"https://github.com/lsst/meas_astrom/pull/3#discussion_r25624353"},"pull_request":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/3"}}},"pull_request":{"url":"https://api.github.com/repos/lsst/meas_astrom/pulls/3","id":30181919,"html_url":"https://github.com/lsst/meas_astrom/pull/3","diff_url":"https://github.com/lsst/meas_astrom/pull/3.diff","patch_url":"https://github.com/lsst/meas_astrom/pull/3.patch","issue_url":"https://api.github.com/repos/lsst/meas_astrom/issues/3","number":3,"state":"open","locked":false,"title":"Review for DM-1576","user":{"login":"TallJimbo","id":1330720,"avatar_url":"https://avatars.githubusercontent.com/u/1330720?v=3","gravatar_id":"","url":"https://api.github.com/users/TallJimbo","html_url":"https://github.com/TallJimbo","followers_url":"https://api.github.com/users/TallJimbo/followers","following_url":"https://api.github.com/users/TallJimbo/following{/other_user}","gists_url":"https://api.github.com/users/TallJimbo/gists{/gist_id}","starred_url":"https://api.github.com/users/TallJimbo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TallJimbo/subscriptions","organizations_url":"https://api.github.com/users/TallJimbo/orgs","repos_url":"https://api.github.com/users/TallJimbo/repos","events_url":"https://api.github.com/users/TallJimbo/events{/privacy}","received_events_url":"https://api.github.com/users/TallJimbo/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-02-27T16:58:05Z","updated_at":"2015-03-02T18:39:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"77b99ff20af52283b5d8cbd397110c0cf83ec247","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/lsst/meas_astrom/pulls/3/commits","review_comments_url":"https://api.github.com/repos/lsst/meas_astrom/pulls/3/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/3/comments","statuses_url":"https://api.github.com/repos/lsst/meas_astrom/statuses/9f9e02d838104c4556bbf32cf9076025b70e1b81","head":{"label":"lsst:tickets/DM-1576","ref":"tickets/DM-1576","sha":"9f9e02d838104c4556bbf32cf9076025b70e1b81","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}","created_at":"2014-08-16T07:45:25Z","updated_at":"2015-02-25T18:41:31Z","pushed_at":"2015-02-28T01:55:30Z","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":31947,"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:master","ref":"master","sha":"39cd4d7075a7020cc8ee266e6c877ff88558d77b","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}","created_at":"2014-08-16T07:45:25Z","updated_at":"2015-02-25T18:41:31Z","pushed_at":"2015-02-28T01:55:30Z","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":31947,"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/3"},"html":{"href":"https://github.com/lsst/meas_astrom/pull/3"},"issue":{"href":"https://api.github.com/repos/lsst/meas_astrom/issues/3"},"comments":{"href":"https://api.github.com/repos/lsst/meas_astrom/issues/3/comments"},"review_comments":{"href":"https://api.github.com/repos/lsst/meas_astrom/pulls/3/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/3/commits"},"statuses":{"href":"https://api.github.com/repos/lsst/meas_astrom/statuses/9f9e02d838104c4556bbf32cf9076025b70e1b81"}}}}