{"action":"created","comment":{"url":"https://api.github.com/repos/lsst/meas_astrom/pulls/comments/25553762","id":25553762,"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\n+\n+        @param[in] refCat  catalog of position reference stars that overlap an exposure\n+        @param[in] sourceCat  catalog of sources found on the exposure\n+        @param[in] wcs  estimated WCS of exposure\n+        @param[in] refFluxField  field of refCat to use for flux\n+        @param[in] numCleanSources  number of clean sources (sources with known centroid\n+            that are not near the edge, but may be saturated)\n+        @param[in] minMatchedPairs  minimum number of matches\n+        @param[in] verbose  true to print diagnostic information to std::cout\n+        \"\"\"\n+        numSources = len(sourceCat)\n+        posRefBegInd = numCleanSources - numSources\n+        configMatchDistPix = self.config.maxMatchDistArcSec/wcs.pixelScale().asArcseconds()\n+\n+        matchControl = MatchOptimisticBControl()\n+        matchControl.refFluxField = refFluxField\n+        matchControl.numBrightStars = self.config.numBrightStars\n+        matchControl.minMatchedPairs = self.config.minMatchedPairs\n+        matchControl.maxOffsetPix = self.config.maxOffsetPix\n+        matchControl.numPointsForShape = self.config.numPointsForShape\n+        matchControl.maxDeterminant = self.config.maxDeterminant","path":"python/lsst/meas/astrom/matchOptimisticB.py","position":220,"original_position":220,"commit_id":"9f9e02d838104c4556bbf32cf9076025b70e1b81","original_commit_id":"6eec1fee45170e4a1c2c4f263a876106fb676e00","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":"Since you're using `LSST_CONTROL_FIELD` in C++, I'd expected you to wrap the control object into a Config object in Python.  Even though you have additional fields that aren't in the C++ control class, I think that'd be the way to go here, and it should let you get rid of the above assignment methods.\r\n\r\nTo do that, back in the definition of the config class, you'd use a decorator:\r\n```\r\n@pexConfig.wraps(MatchOptimisticBControl)\r\nclass MatchOptimisticBConfig(pexConfig.Config):\r\n    # only define fields that aren't in the C++ control object\r\n```\r\nThen, here, you can just say:\r\n```\r\nmatchControl = self.config.makeControl()\r\n```","created_at":"2015-02-28T03:27:04Z","updated_at":"2015-02-28T03:27:04Z","html_url":"https://github.com/lsst/meas_astrom/pull/3#discussion_r25553762","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/25553762"},"html":{"href":"https://github.com/lsst/meas_astrom/pull/3#discussion_r25553762"},"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-02-28T03:27:04Z","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"}}}}