{"action":"created","comment":{"url":"https://api.github.com/repos/lsst/validate_drp/pulls/comments/52239943","id":52239943,"diff_hunk":"@@ -0,0 +1,514 @@\n+#!/usr/bin/env python\n+\n+# LSST Data Management System\n+# Copyright 2008-2016 AURA/LSST.\n+#\n+# This product includes software developed by the\n+# LSST Project (http://www.lsst.org/).\n+#\n+# This program is free software: you can redistribute it and/or modify\n+# it under the terms of the GNU General Public License as published by\n+# the Free Software Foundation, either version 3 of the License, or\n+# (at your option) any later version.\n+#\n+# This program is distributed in the hope that it will be useful,\n+# but WITHOUT ANY WARRANTY; without even the implied warranty of\n+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+# GNU General Public License for more details.\n+#\n+# You should have received a copy of the LSST License Statement and\n+# the GNU General Public License along with this program.  If not,\n+# see <https://www.lsstcorp.org/LegalNotices/>.\n+\n+from __future__ import print_function, division\n+\n+import os.path\n+import sys\n+\n+import numpy as np\n+\n+import lsst.daf.persistence as dafPersist\n+import lsst.pipe.base as pipeBase\n+from lsst.afw.table import SourceCatalog, SchemaMapper, Field\n+from lsst.afw.table import MultiMatch, SimpleRecord, GroupView\n+\n+import lsst.afw.geom as afwGeom\n+import lsst.afw.image as afwImage\n+# import lsst.afw.coord as afwCoord\n+\n+from .plotAstrometryPhotometry import plotAstrometry, plotPhotometry, plotPA1\n+from .calcSrd import computeWidths, getRandomDiff, calcPA1, calcPA2\n+\n+def getCcdKeyName(dataid):\n+    \"\"\"Return the key in a dataId that's referring to the CCD or moral equivalent.\n+\n+    Inputs\n+    ------\n+    dataid : dict\n+        A dictionary that will be searched for a key that matches\n+        an entry in the hardcoded list of possible names for the CCD field.\n+\n+    Notes\n+    -----\n+    Motiviation: Different camera mappings use different keys to indicate \n+      the different amps/ccds in the same exposure.  This function looks \n+      through the reference dataId to locate a field that could be the one.\n+    \"\"\"\n+    possibleCcdFieldNames = ['ccd', 'ccdnum', 'camcol']\n+\n+    for name in possibleCcdFieldNames:\n+        if name in dataid:\n+            return name\n+    else:\n+        return None\n+\n+\n+def isExtended(source, extendedKey, extendedThreshold=1.0):\n+    \"\"\"Is the source extended attribute above the threshold.\n+\n+    Inputs\n+    ------\n+    cat : collection with a .get method \n+        for `extendedKey`\n+    extendedKey\n+        key to look up the extended object parameter from a schema.\n+\n+    Higher values of extendedness indicate a resolved object\n+    that is larger than a point source.\n+    \"\"\"\n+    return source.get(extendedKey) >= extendedThreshold\n+\n+\n+def averageRaDec(cat):\n+    \"\"\"Calculate the RMS for RA, Dec for a set of observations an object.\"\"\"\n+    ra = np.mean(cat.get('coord_ra'))\n+    dec = np.mean(cat.get('coord_dec'))\n+    return ra, dec\n+\n+\n+def magNormDiff(cat):\n+    \"\"\"Calculate the normalized mag/mag_err difference from the mean for a \n+    set of observations of an objection.\n+\n+    Inputs\n+    ------\n+    cat : collection with a .get method \n+         for flux, flux+\"-\" \n+\n+    Returns\n+    -------\n+    pos_median : float\n+        median diff of positions in milliarcsec.\n+    \"\"\"\n+    mag = cat.get('base_PsfFlux_mag')\n+    magerr = cat.get('base_PsfFlux_magerr')\n+    mag_avg = np.mean(mag)\n+    N = len(mag)\n+    normDiff = (mag - mag_avg) / magerr\n+    \n+\n+def positionRms(cat):\n+    \"\"\"Calculate the RMS for RA, Dec for a set of observations an object.\n+\n+    Inputs\n+    ------\n+    cat -- collection with a .get method \n+         for 'coord_ra', 'coord_dec' that returns radians.\n+\n+    Returns\n+    -------\n+    pos_rms -- RMS of positions in milliarcsecond.  Float.\n+\n+    This routine doesn't handle wrap-around\n+    \"\"\"\n+    ra_avg, dec_avg = averageRaDec(cat)\n+    ra, dec = cat.get('coord_ra'), cat.get('coord_dec')\n+    # Approximating that the cos(dec) term is roughly the same\n+    #   for all observations of this object.\n+    ra_var = np.var(ra) * np.cos(dec_avg)**2\n+    dec_var = np.var(dec)\n+    pos_rms = np.sqrt(ra_var + dec_var)  # radians\n+    pos_rms = afwGeom.radToMas(pos_rms)  # milliarcsec\n+\n+    return pos_rms\n+\n+\n+def loadAndMatchData(repo, visitDataIds,\n+                     matchRadius=afwGeom.Angle(1, afwGeom.arcseconds)):\n+    \"\"\"Load data from specific visit.  Match with reference.\n+\n+    Inputs\n+    ------\n+    repo : string\n+        The repository.  This is generally the directory on disk\n+        that contains the repository and mapper.\n+    visitDataIds : list of dict\n+        List of `butler` data IDs of Image catalogs to compare to reference.\n+        The `calexp` cpixel image is needed for the photometric calibration.\n+    matchRadius :  afwGeom.Angle().\n+        Radius for matching. \n+\n+    Returns\n+    -------\n+    afw.table.GroupView\n+        An object of matched catalog.\n+    \"\"\"\n+\n+    # Following \n+    # https://github.com/lsst/afw/blob/tickets/DM-3896/examples/repeatability.ipynb\n+    butler = dafPersist.Butler(repo)\n+    dataset = 'src'\n+\n+    dataRefs = [dafPersist.ButlerDataRef(butler, vId) for vId in visitDataIds]\n+\n+    ccdKeyName = getCcdKeyName(visitDataIds[0])\n+\n+    schema = butler.get(dataset + \"_schema\", immediate=True).schema\n+    mapper = SchemaMapper(schema)\n+    mapper.addMinimalSchema(schema)\n+    mapper.addOutputField(Field[float]('base_PsfFlux_mag', \"PSF magnitude\"))\n+    mapper.addOutputField(Field[float]('base_PsfFlux_magerr', \"PSF magnitude uncertainty\"))\n+    newSchema = mapper.getOutputSchema()\n+\n+    # Create an object that can match multiple catalogs with the same schema\n+    mmatch = MultiMatch(newSchema, \n+                        dataIdFormat = {'visit': int, ccdKeyName: int},\n+                        radius=matchRadius,\n+                        RecordClass=SimpleRecord)\n+\n+    # create the new extented source catalog\n+    srcVis = SourceCatalog(newSchema)\n+\n+    for vId in visitDataIds:\n+        calib = afwImage.Calib(butler.get(\"calexp_md\", vId, immediate=True))\n+        calib.setThrowOnNegativeFlux(False)\n+        oldSrc = butler.get('src', vId, immediate=True)\n+        print(len(oldSrc), \"sources in ccd: \", vId[ccdKeyName])\n+\n+        # create temporary catalog\n+        tmpCat = SourceCatalog(SourceCatalog(newSchema).table)\n+        tmpCat.extend(oldSrc, mapper=mapper)\n+        (tmpCat['base_PsfFlux_mag'][:],\n+         tmpCat['base_PsfFlux_magerr'][:]) = \\\n+             calib.getMagnitude(tmpCat['base_PsfFlux_flux'],\n+                                tmpCat['base_PsfFlux_fluxSigma'])\n+        srcVis.extend(tmpCat, False)\n+        mmatch.add(catalog=tmpCat, dataId=vId)\n+\n+    # Complete the match, returning a catalog that includes all matched sources with object IDs that can be used to group them.\n+    matchCat = mmatch.finish()\n+\n+    # Create a mapping object that allows the matches to be manipulated as a mapping of object ID to catalog of sources.\n+    allMatches = GroupView.build(matchCat)\n+    \n+    return allMatches\n+\n+\n+def analyzeData(allMatches, good_mag_limit=19.5):\n+    \"\"\"Calculate summary statistics for each star.\n+\n+    Inputs\n+    ------\n+    allMatches : afw.table.GroupView \n+        GroupView object with matches.\n+    good_mag_limit : float, optional\n+        Minimum average brightness (in magnitudes) for a star to be considered.\n+\n+    Returns\n+    ------- \n+    pipeBase.Struct \n+        Containing mag, magerr, magrms, dist, and number of matches.\n+    \"\"\"\n+\n+    # Filter down to matches with at least 2 sources and good flags\n+    flagKeys = [allMatches.schema.find(\"base_PixelFlags_flag_%s\" % flag).key\n+                for flag in (\"saturated\", \"cr\", \"bad\", \"edge\")]\n+    nMatchesRequired = 2\n+\n+    psfMagKey = allMatches.schema.find(\"base_PsfFlux_mag\").key\n+    psfMagErrKey = allMatches.schema.find(\"base_PsfFlux_magerr\").key\n+    extendedKey = allMatches.schema.find(\"base_ClassificationExtendedness_value\").key\n+\n+    def goodFilter(cat):\n+        if len(cat) < nMatchesRequired:\n+            return False\n+        for flagKey in flagKeys:\n+            if cat.get(flagKey).any():\n+                return False\n+        if not np.isfinite(cat.get(psfMagKey)).all():\n+            return False\n+        return True\n+\n+    goodMatches = allMatches.where(goodFilter)\n+\n+    # Filter further to a limited range in magnitude and extendedness \n+    # to select bright stars.  \n+    safeMaxMag = good_mag_limit\n+    safeMaxExtended = 1.0\n+\n+    def safeFilter(cat):\n+        psfMag = np.mean(cat.get(psfMagKey))\n+        extended = np.max(cat.get(extendedKey))\n+        return psfMag <= safeMaxMag and extended < safeMaxExtended\n+\n+    safeMatches = goodMatches.where(safeFilter)\n+\n+    # Pass field=psfMagKey so np.mean just gets that as its input\n+    goodPsfMag = goodMatches.aggregate(np.mean, field=psfMagKey)  # mag\n+    goodPsfMagRms = goodMatches.aggregate(np.std, field=psfMagKey)  # mag\n+    goodPsfMagErr = goodMatches.aggregate(np.median, field=psfMagErrKey)\n+    goodPsfMagNormDiff = goodMatches.aggregate(magNormDiff)\n+    # positionRms knows how to query a group so we give it the whole thing\n+    #   by going with the default `field=None`.\n+    dist = goodMatches.aggregate(positionRms)\n+\n+    # We calculate differences for 50 different random realizations of the measurement pairs, \n+    # to provide some estimate of the uncertainty on our RMS estimates due to the random shuffling \n+    # This estimate could be stated and calculated from a more formally derived motivation\n+    #   but in practice 50 should be sufficient.\n+    numRandomShuffles = 50\n+    pa1_samples = [calcPA1(safeMatches, psfMagKey) \n+                   for n in range(numRandomShuffles)]\n+    rmsPA1 = np.array([pa1.rms for pa1 in pa1_samples])\n+    iqrPA1 = np.array([pa1.iqr for pa1 in pa1_samples])\n+\n+    print(\"PA1(RMS) = %4.2f+-%4.2f mmag\" % (rmsPA1.mean(), rmsPA1.std()))\n+    print(\"PA1(IQR) = %4.2f+-%4.2f mmag\" % (iqrPA1.mean(), iqrPA1.std()))\n+\n+    info_struct = pipeBase.Struct(\n+        mag = goodPsfMag,\n+        magerr = goodPsfMagErr,\n+        magrms = goodPsfMagRms,\n+        dist = dist,\n+        match = len(dist)\n+    )\n+\n+    return info_struct, safeMatches\n+\n+\n+def checkAstrometry(mag, mmagrms, dist, match,\n+                    good_mag_limit=19.5,\n+                    medianRef=100, matchRef=500):\n+    \"\"\"Print out the astrometric scatter for all stars, and for good stars.\n+\n+    Inputs\n+    ------\n+    mag : list or numpy.array\n+        Average magnitudes of each star\n+    mmagrms ; list or numpy.array\n+        Magnitude RMS of the multiple observation of each star.\n+    dist : list or numpy.array\n+        Distances between successive measurements of one star\n+    match : int\n+        Number of stars matched.\n+\n+    good_mag_limit : float, optional\n+        Minimum average brightness (in magnitudes) for a star to be considered.\n+    medianRef : float, optional\n+        Median reference astrometric scatter in milliarcseconds.\n+    matchRef : int, optional\n+        Should match at least matchRef stars.\n+\n+    Returns\n+    -------\n+    float\n+        The astrometric scatter (RMS, milliarcsec) for all good stars.\n+\n+    Notes\n+    -----\n+    The scatter and match defaults are appropriate to SDSS are the defaults\n+      for `medianRef` and `matchRef`\n+    For SDSS, stars with mag < 19.5 should be completely well measured.\n+    \"\"\"\n+\n+    print(\"Median value of the astrometric scatter - all magnitudes: %.3f %s\" %\n+          (np.median(dist), \"mas\"))\n+\n+    bright = np.where(np.asarray(mag) < good_mag_limit)\n+    astromScatter = np.median(np.asarray(dist)[bright])\n+    print(\"Astrometric scatter (median) - mag < %.1f : %.1f %s\" %\n+          (good_mag_limit, astromScatter, \"mas\"))\n+\n+    if astromScatter > medianRef:\n+        print(\"Median astrometric scatter %.1f %s is larger than reference : %.1f %s \" %\n+              (astromScatter, \"mas\", medianRef, \"mas\"))\n+    if match < matchRef:\n+        print(\"Number of matched sources %d is too small (shoud be > %d)\" % (match, matchRef))\n+\n+    return astromScatter\n+\n+\n+def checkPhotometry(mag, mmagrms, dist, match,\n+                    good_mag_limit=19.5,\n+                    medianRef=100, matchRef=500):\n+    \"\"\"Print out the astrometric scatter for all stars, and for good stars.\n+\n+    Inputs\n+    ------\n+    mag : list or numpy.array\n+        Average magnitudes of each star\n+    mmagrms ; list or numpy.array\n+        Magnitude RMS of the multiple observation of each star.\n+    dist : list or numpy.array\n+        Distances between successive measurements of one star\n+    match : int\n+        Number of stars matched.\n+\n+    good_mag_limit : float, optional\n+        Minimum average brightness (in magnitudes) for a star to be considered.\n+    medianRef : float, optional\n+        Median reference astrometric scatter in millimagnitudes\n+    matchRef : int, optional\n+        Should match at least matchRef stars.\n+\n+    Returns\n+    -------\n+    float\n+        The photometry scatter (RMS, millimag) for all good stars.\n+\n+    Notes\n+    -----\n+    The scatter and match defaults are appropriate to SDSS are stored here.\n+    For SDSS, stars with mag < 19.5 should be completely well measured.\n+    This limit is a band-dependent statement most appropriate to r.\n+    \"\"\"\n+\n+    print(\"Median value of the photometric scatter - all magnitudes: %.3f %s\" %\n+          (np.median(mmagrms), \"mmag\"))\n+\n+    bright = np.where(np.asarray(mag) < good_mag_limit)\n+    photoScatter = np.median(np.asarray(mmagrms)[bright])\n+    print(\"Photometric scatter (median) - mag < %.1f : %.1f %s\" %\n+          (good_mag_limit, photoScatter, \"mmag\"))\n+\n+    if photoScatter > medianRef:\n+        print(\"Median photometric scatter %.3f %s is larger than reference : %.3f %s \" % (photoScatter, \"mmag\", medianRef, \"mag\"))\n+    if match < matchRef:\n+        print(\"Number of matched sources %d is too small (shoud be > %d)\" % (match, matchRef))\n+\n+    return photoScatter\n+\n+\n+def printPA2(gv, magKey):\n+    \"\"\"Calculate and print the calculated PA2 from the LSST SRD from a groupView.\"\"\"\n+\n+    pa2 = calcPA2(gv, magKey)\n+    print(\"minimum: PF1=%2d%% of diffs more than PA2 = %4.2f mmag (target is PA2 < 15 mmag)\" % \n+          (pa2.PF1['minimum'], pa2.minimum))\n+    print(\"design:  PF1=%2d%% of diffs more than PA2 = %4.2f mmag (target is PA2 < 15 mmag)\" % \n+          (pa2.PF1['design'], pa2.design))\n+    print(\"stretch: PF1=%2d%% of diffs more than PA2 = %4.2f mmag (target is PA2 < 10 mmag)\" % \n+          (pa2.PF1['stretch'], pa2.stretch))\n+\n+def repoNameToPrefix(repo):\n+    \"\"\"Generate a base prefix for plots based on the repo name.\n+\n+    Examples\n+    --------\n+    >>> repoNameToPrefix(\"a/b/c\")\n+    \"a_b_c\"\n+    >>> repoNameToPrefix(\"/bar/foo/\")\n+    \"bar_foo\"\n+    >>> repoNameToPrefix(\"CFHT/output\")\n+    \"CFHT_output\"\n+    \"\"\"\n+\n+    dirnames = []\n+    remaining_path = repo\n+    while os.path.split(remaining_path):\n+        remaining_path, tail = os.path.split(remaining_path)\n+        dirnames.append(tail)\n+        if remaining_path in [\"\", \"/\"]: \n+            break\n+\n+    base = \"_\".join(dirnames[::-1])  # reverse to get order right\n+    return base\n+\n+####\n+def run(repo, visitDataIds, good_mag_limit, \n+        medianAstromscatterRef=25, medianPhotoscatterRef=25, matchRef=500):\n+    \"\"\"Main executable.\n+\n+    Inputs\n+    ------\n+    repo : string\n+        The repository.  This is generally the directory on disk\n+        that contains the repository and mapper.\n+    visitDataIds : list of dict\n+        List of `butler` data IDs of Image catalogs to compare to reference.\n+        The `calexp` cpixel image is needed for the photometric calibration.\n+    good_mag_limit : float\n+        Minimum average brightness (in magnitudes) for a star to be considered.\n+    \"\"\"\n+\n+    plotbase = repoNameToPrefix(repo)","path":"python/lsst/validate/drp/checkAstrometryPhotometry.py","position":444,"original_position":444,"commit_id":"d1f96e6a62c6f9b08b64230088c0c05c57f11c1c","original_commit_id":"d1f96e6a62c6f9b08b64230088c0c05c57f11c1c","user":{"login":"jdswinbank","id":460854,"avatar_url":"https://avatars.githubusercontent.com/u/460854?v=3","gravatar_id":"","url":"https://api.github.com/users/jdswinbank","html_url":"https://github.com/jdswinbank","followers_url":"https://api.github.com/users/jdswinbank/followers","following_url":"https://api.github.com/users/jdswinbank/following{/other_user}","gists_url":"https://api.github.com/users/jdswinbank/gists{/gist_id}","starred_url":"https://api.github.com/users/jdswinbank/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdswinbank/subscriptions","organizations_url":"https://api.github.com/users/jdswinbank/orgs","repos_url":"https://api.github.com/users/jdswinbank/repos","events_url":"https://api.github.com/users/jdswinbank/events{/privacy}","received_events_url":"https://api.github.com/users/jdswinbank/received_events","type":"User","site_admin":false},"body":"I ran `python bin/validateDecam.py ./DECam/output/`. I got a bunch of plots saved with names like `._DECam_output_PA1.png` -- at first, I didn't even realise they existed. This probably isn't what you want.\r\n\r\nAlso, I think the variable name should be `plotBase` to follow standards.","created_at":"2016-02-08T22:47:04Z","updated_at":"2016-02-08T22:47:04Z","html_url":"https://github.com/lsst/validate_drp/pull/3#discussion_r52239943","pull_request_url":"https://api.github.com/repos/lsst/validate_drp/pulls/3","_links":{"self":{"href":"https://api.github.com/repos/lsst/validate_drp/pulls/comments/52239943"},"html":{"href":"https://github.com/lsst/validate_drp/pull/3#discussion_r52239943"},"pull_request":{"href":"https://api.github.com/repos/lsst/validate_drp/pulls/3"}}},"pull_request":{"url":"https://api.github.com/repos/lsst/validate_drp/pulls/3","id":57698179,"html_url":"https://github.com/lsst/validate_drp/pull/3","diff_url":"https://github.com/lsst/validate_drp/pull/3.diff","patch_url":"https://github.com/lsst/validate_drp/pull/3.patch","issue_url":"https://api.github.com/repos/lsst/validate_drp/issues/3","number":3,"state":"open","locked":false,"title":"DM-4848: Measure photometric repeatability and correctness of reported errors","user":{"login":"wmwv","id":3201257,"avatar_url":"https://avatars.githubusercontent.com/u/3201257?v=3","gravatar_id":"","url":"https://api.github.com/users/wmwv","html_url":"https://github.com/wmwv","followers_url":"https://api.github.com/users/wmwv/followers","following_url":"https://api.github.com/users/wmwv/following{/other_user}","gists_url":"https://api.github.com/users/wmwv/gists{/gist_id}","starred_url":"https://api.github.com/users/wmwv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wmwv/subscriptions","organizations_url":"https://api.github.com/users/wmwv/orgs","repos_url":"https://api.github.com/users/wmwv/repos","events_url":"https://api.github.com/users/wmwv/events{/privacy}","received_events_url":"https://api.github.com/users/wmwv/received_events","type":"User","site_admin":false},"body":"Adds plots and calculation on photometric repeatability.  Calculate simple RMS and more formal SRD-motivated quantities.  Output to STDOUT and PNG plots in working directory.","created_at":"2016-01-29T20:26:07Z","updated_at":"2016-02-08T22:47:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":"786baac8e6bb603ed7df219c7d3551d383f24973","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/lsst/validate_drp/pulls/3/commits","review_comments_url":"https://api.github.com/repos/lsst/validate_drp/pulls/3/comments","review_comment_url":"https://api.github.com/repos/lsst/validate_drp/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lsst/validate_drp/issues/3/comments","statuses_url":"https://api.github.com/repos/lsst/validate_drp/statuses/d1f96e6a62c6f9b08b64230088c0c05c57f11c1c","head":{"label":"lsst:tickets/DM-4848","ref":"tickets/DM-4848","sha":"d1f96e6a62c6f9b08b64230088c0c05c57f11c1c","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":49601522,"name":"validate_drp","full_name":"lsst/validate_drp","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/validate_drp","description":"","fork":false,"url":"https://api.github.com/repos/lsst/validate_drp","forks_url":"https://api.github.com/repos/lsst/validate_drp/forks","keys_url":"https://api.github.com/repos/lsst/validate_drp/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lsst/validate_drp/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lsst/validate_drp/teams","hooks_url":"https://api.github.com/repos/lsst/validate_drp/hooks","issue_events_url":"https://api.github.com/repos/lsst/validate_drp/issues/events{/number}","events_url":"https://api.github.com/repos/lsst/validate_drp/events","assignees_url":"https://api.github.com/repos/lsst/validate_drp/assignees{/user}","branches_url":"https://api.github.com/repos/lsst/validate_drp/branches{/branch}","tags_url":"https://api.github.com/repos/lsst/validate_drp/tags","blobs_url":"https://api.github.com/repos/lsst/validate_drp/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lsst/validate_drp/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lsst/validate_drp/git/refs{/sha}","trees_url":"https://api.github.com/repos/lsst/validate_drp/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lsst/validate_drp/statuses/{sha}","languages_url":"https://api.github.com/repos/lsst/validate_drp/languages","stargazers_url":"https://api.github.com/repos/lsst/validate_drp/stargazers","contributors_url":"https://api.github.com/repos/lsst/validate_drp/contributors","subscribers_url":"https://api.github.com/repos/lsst/validate_drp/subscribers","subscription_url":"https://api.github.com/repos/lsst/validate_drp/subscription","commits_url":"https://api.github.com/repos/lsst/validate_drp/commits{/sha}","git_commits_url":"https://api.github.com/repos/lsst/validate_drp/git/commits{/sha}","comments_url":"https://api.github.com/repos/lsst/validate_drp/comments{/number}","issue_comment_url":"https://api.github.com/repos/lsst/validate_drp/issues/comments{/number}","contents_url":"https://api.github.com/repos/lsst/validate_drp/contents/{+path}","compare_url":"https://api.github.com/repos/lsst/validate_drp/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lsst/validate_drp/merges","archive_url":"https://api.github.com/repos/lsst/validate_drp/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lsst/validate_drp/downloads","issues_url":"https://api.github.com/repos/lsst/validate_drp/issues{/number}","pulls_url":"https://api.github.com/repos/lsst/validate_drp/pulls{/number}","milestones_url":"https://api.github.com/repos/lsst/validate_drp/milestones{/number}","notifications_url":"https://api.github.com/repos/lsst/validate_drp/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lsst/validate_drp/labels{/name}","releases_url":"https://api.github.com/repos/lsst/validate_drp/releases{/id}","deployments_url":"https://api.github.com/repos/lsst/validate_drp/deployments","created_at":"2016-01-13T21:01:10Z","updated_at":"2016-01-15T19:33:12Z","pushed_at":"2016-02-08T18:30:55Z","git_url":"git://github.com/lsst/validate_drp.git","ssh_url":"git@github.com:lsst/validate_drp.git","clone_url":"https://github.com/lsst/validate_drp.git","svn_url":"https://github.com/lsst/validate_drp","homepage":null,"size":179,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"lsst:master","ref":"master","sha":"6a47ca481be0a7b59b83ef037b5069190cd718d4","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":49601522,"name":"validate_drp","full_name":"lsst/validate_drp","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/validate_drp","description":"","fork":false,"url":"https://api.github.com/repos/lsst/validate_drp","forks_url":"https://api.github.com/repos/lsst/validate_drp/forks","keys_url":"https://api.github.com/repos/lsst/validate_drp/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lsst/validate_drp/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lsst/validate_drp/teams","hooks_url":"https://api.github.com/repos/lsst/validate_drp/hooks","issue_events_url":"https://api.github.com/repos/lsst/validate_drp/issues/events{/number}","events_url":"https://api.github.com/repos/lsst/validate_drp/events","assignees_url":"https://api.github.com/repos/lsst/validate_drp/assignees{/user}","branches_url":"https://api.github.com/repos/lsst/validate_drp/branches{/branch}","tags_url":"https://api.github.com/repos/lsst/validate_drp/tags","blobs_url":"https://api.github.com/repos/lsst/validate_drp/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lsst/validate_drp/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lsst/validate_drp/git/refs{/sha}","trees_url":"https://api.github.com/repos/lsst/validate_drp/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lsst/validate_drp/statuses/{sha}","languages_url":"https://api.github.com/repos/lsst/validate_drp/languages","stargazers_url":"https://api.github.com/repos/lsst/validate_drp/stargazers","contributors_url":"https://api.github.com/repos/lsst/validate_drp/contributors","subscribers_url":"https://api.github.com/repos/lsst/validate_drp/subscribers","subscription_url":"https://api.github.com/repos/lsst/validate_drp/subscription","commits_url":"https://api.github.com/repos/lsst/validate_drp/commits{/sha}","git_commits_url":"https://api.github.com/repos/lsst/validate_drp/git/commits{/sha}","comments_url":"https://api.github.com/repos/lsst/validate_drp/comments{/number}","issue_comment_url":"https://api.github.com/repos/lsst/validate_drp/issues/comments{/number}","contents_url":"https://api.github.com/repos/lsst/validate_drp/contents/{+path}","compare_url":"https://api.github.com/repos/lsst/validate_drp/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lsst/validate_drp/merges","archive_url":"https://api.github.com/repos/lsst/validate_drp/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lsst/validate_drp/downloads","issues_url":"https://api.github.com/repos/lsst/validate_drp/issues{/number}","pulls_url":"https://api.github.com/repos/lsst/validate_drp/pulls{/number}","milestones_url":"https://api.github.com/repos/lsst/validate_drp/milestones{/number}","notifications_url":"https://api.github.com/repos/lsst/validate_drp/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lsst/validate_drp/labels{/name}","releases_url":"https://api.github.com/repos/lsst/validate_drp/releases{/id}","deployments_url":"https://api.github.com/repos/lsst/validate_drp/deployments","created_at":"2016-01-13T21:01:10Z","updated_at":"2016-01-15T19:33:12Z","pushed_at":"2016-02-08T18:30:55Z","git_url":"git://github.com/lsst/validate_drp.git","ssh_url":"git@github.com:lsst/validate_drp.git","clone_url":"https://github.com/lsst/validate_drp.git","svn_url":"https://github.com/lsst/validate_drp","homepage":null,"size":179,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/lsst/validate_drp/pulls/3"},"html":{"href":"https://github.com/lsst/validate_drp/pull/3"},"issue":{"href":"https://api.github.com/repos/lsst/validate_drp/issues/3"},"comments":{"href":"https://api.github.com/repos/lsst/validate_drp/issues/3/comments"},"review_comments":{"href":"https://api.github.com/repos/lsst/validate_drp/pulls/3/comments"},"review_comment":{"href":"https://api.github.com/repos/lsst/validate_drp/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lsst/validate_drp/pulls/3/commits"},"statuses":{"href":"https://api.github.com/repos/lsst/validate_drp/statuses/d1f96e6a62c6f9b08b64230088c0c05c57f11c1c"}}}}