Gallery for DR5

The purpose of this notebook is to build the gallery for the fifth Legacy Survey data release, DR5. The theme of this gallery is "galaxy groups."

The parent sample is a diameter-limited (D25>5 arcsec) sample defined and documented as part of the Legacy Survey Large Galaxy Atlas. Here, we use the group catalog constructed in this notebook.

Imports and paths


In [38]:
import os
import numpy as np

import fitsio
import matplotlib.pyplot as plt
from astropy.table import Table, Column, vstack
from PIL import Image, ImageDraw, ImageFont

from astrometry.util.util import Tan
from astrometry.util.fits import merge_tables
from legacypipe.survey import ccds_touching_wcs, LegacySurveyData

In [2]:
import multiprocessing
nproc = multiprocessing.cpu_count() // 2
nproc = 1 # hack because multiprocessing doesn't seem to be working in jupyter-dev

In [3]:
%matplotlib inline

Preliminaries

Define the data release and specify the desired angular radius range (in arcmin) for the galaxies (and galaxy groups) that will end up in the gallery.


In [4]:
dr = 'dr5'
if dr == 'dr5':
    PIXSCALE = 0.262
else:
    raise NotImplementedError

In [5]:
suffix = '0.05'

In [6]:
mindiameter, maxdiameter, d25min = 1.0, 10.0, 0.4 # [arcmin]
minnmembers = 3

In [7]:
GALAXY_DIAMFACTOR = 2.0
GROUP_DIAMFACTOR = 2.0

In [8]:
gallerydir = os.path.join( os.getenv('SCRATCH'), dr, 'gallery' )
galleryfile = os.path.join(gallerydir, 'gallery-{}.fits'.format(dr))

Instantiate the LegacySurveyData class and read the full set of CCDs.


In [9]:
survey = LegacySurveyData()

In [10]:
bricks = survey.get_bricks()
allccds = survey.get_annotated_ccds()
cut = survey.photometric_ccds(allccds)
if cut is not None:
    allccds.cut(cut)
cut = survey.ccd_cuts(allccds)
allccds.cut(cut == 0)
print('Read {} CCDs.'.format(len(allccds)))


Converted brickname from |S8 to <U8
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-run19.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S51 to <U51
Converted plver from |S6 to <U6
Got 73860 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-decals.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S61 to <U61
Converted plver from |S6 to <U6
Got 493440 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-run28.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S51 to <U51
Converted plver from |S6 to <U6
Got 86986 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-extra.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S61 to <U61
Converted plver from |S6 to <U6
Got 487556 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-nondecals.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S61 to <U61
Converted plver from |S6 to <U6
Got 670810 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-run21.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S51 to <U51
Converted plver from |S6 to <U6
Got 39720 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-run27.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S51 to <U51
Converted plver from |S6 to <U6
Got 91866 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-nondecals-dr5.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted temp from |S32 to <U32
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S65 to <U65
Converted plver from |S6 to <U6
Got 551580 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-run14.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S51 to <U51
Converted plver from |S6 to <U6
Got 85020 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-run16.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S51 to <U51
Converted plver from |S6 to <U6
Got 40200 CCDs
Reading annotated CCDs from /global/cscratch1/sd/desiproc/dr5/ccds-annotated-run25.fits.gz
Converted object from |S37 to <U37
Converted filter from |S1 to <U1
Converted date_obs from |S10 to <U10
Converted ut from |S15 to <U15
Converted ha from |S13 to <U13
Converted propid from |S10 to <U10
Converted ccdname from |S3 to <U3
Converted camera from |S5 to <U5
Converted expid from |S12 to <U12
Converted image_filename from |S51 to <U51
Converted plver from |S6 to <U6
Got 121085 CCDs
Total of 2742123 CCDs
Finding photometric CCDs.  Cameras: ['decam']
Checking 2742123 images from camera decam
Flagged 8007 more non-photometric using criterion: exptime < 30 s
Flagged 91271 more non-photometric using criterion: ccdnmatch < 20
Flagged 15502 more non-photometric using criterion: abs(zpt - ccdzpt) > 0.1
Flagged 114979 more non-photometric using criterion: zpt < 0.5 mag of nominal
Flagged 0 more non-photometric using criterion: zpt > 0.25 mag of nominal
Keeping 2512364 photometric CCDs from camera decam
Computing CCD cuts.  Cameras: ['decam']
Checking 2512364 images from camera decam
Read 2512364 CCDs.

Define the parent sample based on cuts to D(25).


In [11]:
def read_groupcat(gallerydir='.', mindiameter=0.0, maxdiameter=1000.0, d25min=0.5,
                  decmin=-90.0, decmax=+90.0, ramin=0.0, ramax=360.0, 
                  minnmembers=1):
    """Read the LEDA group catalog.  Look for individual galaxies or groups with 
    diameters between [mindiameter, maxdiameter] (arcminutes) but also require the
    smallest member of each group to have D(25)>d25min (arcminute).  These cuts
    ensure that we don't build up mosaics of lots of small separated galaxies -- 
    we require at least one "big" galaxy in each mosaic.
    
    In addition, there are optional cuts on (ra,dec) which are useful for testing.
    
    minnmembers is the minimum number of members in each group.
    
    """
    groupfile = os.path.join(gallerydir, 'leda-logd25-{}-groupcat.fits'.format(suffix))
    print('Reading {}'.format(groupfile))
    cat = Table.read(groupfile)

    these = np.where((cat['DIAMETER'] / 60.0 <= maxdiameter) *
                     (cat['DIAMETER'] / 60.0 >= mindiameter) *
                     (cat['D25MIN'] / 60.0 >= d25min) *
                     (cat['NMEMBERS'] >= minnmembers) *
                     (cat['DEC'] <= decmax) *
                     (cat['DEC'] >= decmin) *
                     (cat['RA'] <= ramax) *
                     (cat['RA'] >= ramin)
                     )[0]
    outcat = cat[these] 
    outcat.add_column(Column(name='BRICKNAME', dtype='S17', shape=(6,), length=len(outcat)))

    print('Found {}/{} groups with N>{} members, each with D(25)>{}, and in the diameter range={}-{} arcmin.'.format(
            len(outcat), len(cat), minnmembers, d25min, mindiameter, maxdiameter))

    return outcat

In [12]:
parent = read_groupcat(gallerydir=gallerydir, mindiameter=mindiameter, 
                       maxdiameter=maxdiameter, d25min=d25min, 
                       minnmembers=minnmembers)
parent


Reading /global/cscratch1/sd/ioannis/dr5/gallery/leda-logd25-0.05-groupcat.fits
Found 1300/713903 groups with N>3 members, each with D(25)>0.4, and in the diameter range=1.0-10.0 arcmin.
Out[12]:
<Table length=1300>
GROUPIDGALAXYNMEMBERSRADECDIAMETERD25MAXD25MINBRICKNAME [6]
int32str1000int32float64float64float32float32float32bytes17
611510NGC7805,NGC7806,PGC00011130.37731.4395333333109.65484.752332.9725..
611520PGC000113,PGC671782,PGC67174530.4157-33.76655185.91331.488528.0641..
611701PGC2452515,PGC2453273,PGC245324330.810353.8627164.57365.788725.5948..
611744PGC472503,PGC129167,PGC07321930.9184-49.8610366667246.57639.641628.7178..
611800PGC087701,PGC737465,PGC737031,PGC736578,PGC73745050.98631-28.806118370.23930.071225.0122..
611872PGC427313,PGC143280,PGC14328831.18915-53.6730533333239.53626.80125.5948..
611909PGC595015,PGC595115,PGC59509731.23565-39.7075533333151.03447.659726.801..
611921PGC000346,PGC073229,ESO193-01731.2675-50.826456666790.727260.027.4253..
611934PGC773329,PGC198124,PGC77382531.2831-25.9208633333185.91536.995726.1909..
611960PGC000369,PGC000368,PGC201968131.328832.9796262.883442.476733.7405..
...........................
713079PGC229251,PGC229255,ESO012-0093358.3678-79.8156566667175.52949.905839.6416..
713130PGC072755,NGC7764A,NGC7764E3358.3474-40.809013333384.807457.299640.565..
713181PGC095007,PGC095008,PGC4216023358.46235-54.2050533333183.5137.857424.4428..
713306ESO538-004,PGC877190,PGC8777893358.7116-17.7879166667215.2248.769825.5948..
713340PGC522664,PGC130021,PGC0928223358.7826-45.7799666667189.42238.739330.0712..
713447UGC12850,PGC072938,PGC0729393359.0540529.3917766667208.67457.299635.3306..
713583ESO078-010,PGC320062,PGC0953633359.3218-64.69111103.84551.068326.1909..
713616PGC280463,PGC280390,PGC2804083359.41175-69.2368966667150.50831.488526.1909..
713681PGC703203,PGC703226,PGC7029773359.49505-31.61732193.89430.771725.0122..
713768PGC215015,UGC12874,PGC2150163359.6745527.15513166.48936.995728.7178..

In [13]:
if False:
    these = (parent['RA'] > 200) * (parent['RA'] < 210) * (parent['DEC'] > 5) * (parent['DEC'] < 10.0)
    parent = parent[these]
    print(np.sum(these))

Build the parent sample of galaxies in the DR5 footprint.


In [14]:
def _uniqccds(ccds):
    '''Get the unique set of CCD files.'''
    ccdfile = []
    [ccdfile.append('{}-{}'.format(expnum, ccdname)) for expnum,
     ccdname in zip(ccds.expnum, ccds.ccdname)]
    _, indx = np.unique(ccdfile, return_index=True)
    return ccds[indx]

In [15]:
def _galwcs(gal, factor=2.0):
    '''Build a simple WCS object for a single galaxy.
    
    '''
    diam = factor*np.ceil(gal['DIAMETER']/PIXSCALE).astype('int16') # [pixels]
    galwcs = Tan(gal['RA'], gal['DEC'], diam/2+0.5, diam/2+0.5,
                 -PIXSCALE/3600.0, 0.0, 0.0, PIXSCALE/3600.0, 
                 float(diam), float(diam))
    return galwcs

In [16]:
def _build_sample_onegalaxy(args):
    """Filler function for the multiprocessing."""
    return build_sample_onegalaxy(*args)

In [17]:
def build_sample_onegalaxy(gal, verbose=False):
    """Wrapper function to find overlapping CCDs for a given galaxy.

    First generously find the nearest set of CCDs that are near the galaxy and
    then demand that there's 3-band coverage in a much smaller region centered
    on the galaxy.

    """
    #print('Working on {}...'.format(gal['GALAXY'].strip()))
    if gal['NMEMBERS'] == 1:
        galwcs = _galwcs(gal, factor=GALAXY_DIAMFACTOR)
    else:
        galwcs = _galwcs(gal, factor=GROUP_DIAMFACTOR)
    these = ccds_touching_wcs(galwcs, allccds)

    if len(these) > 0:
        ccds1 = _uniqccds( allccds[these] )

        # Is there 3-band coverage?
        galwcs_small = _galwcs(gal, factor=1.0)
        these_small = ccds_touching_wcs(galwcs_small, ccds1)
        ccds1_small = _uniqccds( ccds1[these_small] )

        if 'g' in ccds1_small.filter and 'r' in ccds1_small.filter and 'z' in ccds1_small.filter:
            if verbose:
                print('For {} found {} CCDs, RA = {:.5f}, Dec = {:.5f}, Diameter={:.4f} arcsec'.format(
                        gal['GALAXY'].strip(), len(ccds1), gal['RA'], gal['DEC'], gal['DIAMETER']))
            
            # Also get the set of bricks touching this footprint.
            rad = gal['DIAMETER'] / 3600 # [degree]
            brickindx = survey.bricks_touching_radec_box(bricks,
                                                         gal['RA']-rad, gal['RA']+rad,
                                                         gal['DEC']-rad, gal['DEC']+rad)
            if len(brickindx) == 0 or len(brickindx) > len(gal['BRICKNAME']):
                print('This should not happen!')
                import pdb ; pdb.set_trace()
            gal['BRICKNAME'][:len(brickindx)] = bricks.brickname[brickindx]

            return [gal, ccds1]

    return None

In [18]:
def build_sample():
    """Build the sample for all galaxies with the option of multiprocessing
    (although it doesn't work on jupyter-dev)."""
    sampleargs = list()
    for cc in parent:
        sampleargs.append( (cc, False) ) # the False refers to verbose=False

    if nproc > 1:
        p = multiprocessing.Pool(nproc)
        result = p.map(_build_sample_onegalaxy, sampleargs)
        p.close()
    else:
        result = list()
        for args in sampleargs:
            result.append(_build_sample_onegalaxy(args))

    # Remove non-matching galaxies and write out the sample
    result = list(filter(None, result))
    result = list(zip(*result))

    outcat = vstack(result[0])
    outccds = merge_tables(result[1])
    print('Found {}/{} galaxies and groups in the DR5 footprint.'.format(len(outcat), len(parent)))
    
    # Add a fracmasked column.
    outcat.add_column(Column(name='FRACMASKED', shape=(3,), length=len(outcat), dtype='f2'))
    
    return outcat

In [19]:
%time sample = build_sample()
sample


Found 271/1300 galaxies and groups in the DR5 footprint.
CPU times: user 28.7 s, sys: 164 ms, total: 28.9 s
Wall time: 28.9 s
Out[19]:
<Table length=271>
GROUPIDGALAXYNMEMBERSRADECDIAMETERD25MAXD25MINBRICKNAME [6]FRACMASKED [3]
int32str1000int32float64float64float32float32float32bytes20float16
614652NGC0127,NGC0128,NGC013037.31392.86903333333189.737189.73742.47670073p027 ..0.0 .. 0.0
615005PGC1036047,PGC1036361,PGC103659338.2232-6.13836333333201.95153.475128.06410081m062 ..0.0 .. 0.0
615052PGC1028413,PGC143577,PGC14357938.3796-6.82209333333151.88527.425324.44280084m067 ..0.0 .. 0.0
615465PGC1101085,PGC1100823,PGC110045039.30705-2.13501666667141.45237.857425.01220093m022 ..0.0 .. 0.0
615699NGC0192,NGC0196,NGC019739.81950.889566666667212.301122.50455.99530098p007 ..0.0 .. 0.0
616634PGC842319,NGC0247B,ESO540-024,ESO540-025,PGC002798511.89698-20.456582480.29562.827731.48850118m205 ..0.0 .. 0.0
616934PGC090503,PGC1244573,PGC1244244312.656253.07869666667151.24939.641628.06410126p030 ..0.0 .. 0.0
617056IC0055,PGC1328252,PGC1328284312.955657.69844333333258.55547.659726.19090129p077 ..0.0 .. 0.0
617185PGC173062,PGC173063,PGC173070313.28545-3.63262196.09747.659730.77170131m037 ..0.0 .. 0.0
618099PGC003597,PGC914550,PGC173260315.12245-15.18764219.21648.769825.01220151m152 ..0.0 .. 0.0
..............................
702612PGC1016425,PGC1016359,PGC1016757,PGC31037164328.2866625-7.585455188.89852.257825.59483281m077 ..0.0 .. 0.0
702871PGC214792,UGC11853,PGC11251253329.0723-1.16768113.97855.995325.01223291m012 ..0.0 .. 0.0
704272PGC1137098,PGC1137118,PGC11370403333.18625-0.691466666667186.90128.717824.44283331m007 ..0.0 .. 0.0
706306PGC193192,PGC193199,PGC1932013339.05731.4075366666782.929643.466225.59483391p015 ..0.0 .. 0.0
706866PGC1188417,PGC1187491,PGC11874163340.60671.20671333333157.13532.221925.59483406p012 ..0.0 .. 0.0
708496NGC7463,NGC7464,NGC74653345.4815515.9734866667170.70295.093630.77173453p160 ..0.0 .. 0.0
709687NGC7562,PGC197664,NGC7562A3348.997356.6563235.432131.26625.01223488p065 ..0.0 .. 0.0
710495PGC1479636,PGC1476973,UGC12590,PGC165923,PGC14783845351.301815.215332402.3252.257824.44283510p150 ..0.0 .. 0.0
712332PGC196423,PGC1087348,PGC10877793356.436-2.61394333333233.65844.478626.19093563m027 ..0.0 .. 0.0
712466IC5351,PGC072405,IC53573356.83595-2.29861333333131.781101.89526.8013568m022 ..0.0 .. 0.0

Retrieve FITS cutouts of each galaxy in each band.

We also compute the fraction of masked pixels in each band (so we can do some quality cuts, below) and then write out the final sample.


In [20]:
fitsdir = os.path.join(gallerydir, 'fits')
if not os.path.isdir(fitsdir):
    os.mkdir(fitsdir)

In [21]:
def galaxyname(gal, html=False):
    """We want to prevent the output FITS/png filenames from arbitrarily long
    so we use the following convention:
    
    1 member - use the galaxy name
    2-3 members - join the individual galaxy names with a hyphen
    >3 members - use the groupid with format :08d
    
    """
    if gal['NMEMBERS'] > 500:
        galaxy = 'group{:08d}'.format(gal['GROUPID'])
    else:
        if html:
            galaxy = gal['GALAXY'].strip().lower().replace(',', ' ')
        else:
            galaxy = gal['GALAXY'].strip().lower().replace(',', '-')
    
    return galaxy

In [22]:
def fits_cutouts(verbose=False):
    """Grab cutouts of every galaxy and also quickly compute the fraction of 
    pixels that are masked in each image (in fracmasked).
    
    """
    for jj, gal in enumerate(sample):
        galaxy = galaxyname(gal)
        if gal['NMEMBERS'] == 1:
            size = np.ceil(GALAXY_DIAMFACTOR*gal['DIAMETER']/PIXSCALE).astype('int16') # [pixels]
        else:
            size = np.ceil(GROUP_DIAMFACTOR*gal['DIAMETER']/PIXSCALE).astype('int16') # [pixels]

        # Get a FITS cutout and then split the file into three individual bands.
        baseurl = 'http://legacysurvey.org/viewer-dev/fits-cutout/'
        fitsurl = '{}?ra={:.6f}&dec={:.6f}&pixscale={:.3f}&size={:g}&layer=decals-{}'.format(
            baseurl, gal['RA'], gal['DEC'], PIXSCALE, size, dr)
        fitsfile = os.path.join(fitsdir, '{}.fits'.format(galaxy))
        cmd = 'wget --continue -O {:s} "{:s}"' .format(fitsfile, fitsurl)
        if os.path.isfile(os.path.join(fitsdir, '{}-{}.fits'.format(galaxy, 'g'))):
            if verbose:
                print('Skipping galaxy {}'.format(galaxy))
        else:
            if verbose:
                print(cmd)
            os.system(cmd)

            img, hdr = fitsio.read(fitsfile, ext=0, header=True)
            for ii, band in enumerate(['g', 'r', 'z']):
                bandfile = os.path.join(fitsdir, '{}-{}.fits'.format(galaxy, band))
                #print('  Writing {}'.format(bandfile))
                sample['FRACMASKED'][jj][ii] = np.sum(img[ii, :, :] == 0) / img[ii, :, :].size
                fitsio.write(bandfile, img[ii, :, :], clobber=True, header=hdr)        
            #print('  Removing {}'.format(fitsfile))
            os.remove(fitsfile)

In [23]:
%time fits_cutouts(verbose=False)


CPU times: user 8.88 s, sys: 17.6 s, total: 26.5 s
Wall time: 19min 23s

In [24]:
if os.path.isfile(galleryfile):
    os.remove(galleryfile)
    
print('Writing {}'.format(galleryfile))
sample.write(galleryfile)


Writing /global/cscratch1/sd/ioannis/dr5/gallery/gallery-dr5.fits

Build a color image using trilogy.py

Here we require no more than 2% of the pixels in all the FITS images to be masked (i.e., to have values of zero) before generating a PNG file. Then, after the PNG image has been generated, we read it back in and add a scale bar and a galaxy label.


In [25]:
fraccut = 0.02 # [fraction]

In [26]:
pngdir = os.path.join(gallerydir, 'png')
if not os.path.isdir(pngdir):
    os.mkdir(pngdir)

In [27]:
barlen30 = np.round(30.0 / PIXSCALE).astype('int')
fonttype = os.path.join(gallerydir, 'Georgia.ttf')

In [28]:
def make_png(pngsample, verbose=False):
    """Build a png file for each galaxy / group using Trilogy. 
    
    """
    for gal in pngsample:
        galaxy = galaxyname(gal)
        
        pngfile = os.path.join(pngdir, '{}.png'.format(galaxy))
        thumbfile = os.path.join(pngdir, 'thumb-{}.png'.format(galaxy))

        paramfile = os.path.join(pngdir, '{}.in'.format(galaxy))
        with open(paramfile, 'w') as param:
            param.write('B\n')
            param.write(os.path.join(fitsdir, '{}-g.fits\n'.format(galaxy)))
            param.write('\n')
            param.write('G\n')
            param.write(os.path.join(fitsdir, '{}-r.fits\n'.format(galaxy)))
            param.write('\n')        
            param.write('R\n')
            param.write(os.path.join(fitsdir, '{}-z.fits\n'.format(galaxy)))
            param.write('\n')
            param.write('indir {}\n'.format(fitsdir))
            param.write('outname {}\n'.format(pngfile))
            param.write('noiselum 0.3\n')
            param.write('satpercent 0.001\n')
            param.write('samplesize 1000\n')
            param.write('stampsize 5000\n')
            #param.write('scaling {}\n'.format(os.path.join(gallerydir, 'levels.txt')))
            param.write('colorsatfac 0.95\n')
            param.write('deletetests 1\n')
            param.write('showstamps 0\n')
            param.write('show 0\n')
            param.write('legend 0\n')
            param.write('testfirst 0\n')
        param.close()
        
        trilogy = os.path.join(os.getenv('LEGACYPIPE_DIR'), 'py', 'legacyanalysis', 'trilogy.py')
        cmd = 'python {} {}'.format(trilogy, paramfile)
        #print(cmd)
        os.system(cmd)
        for txt in ('levels.txt', 'trilogyfilterlog.txt'):
            thisfile = os.path.join(os.getenv('LEGACYPIPE_DIR'), 'doc', 'nb', txt)
            os.remove(thisfile)
        os.remove(os.path.join(pngdir, '{}_filters.txt'.format(galaxy)))
        os.remove(os.path.join(pngdir, '{}.in'.format(galaxy)))
    
        im = Image.open(pngfile)
        sz = im.size
        fntsize = np.round(sz[0]/28).astype('int')
        width = np.round(sz[0]/175).astype('int')
        font = ImageFont.truetype(fonttype, size=fntsize)
        draw = ImageDraw.Draw(im)
        # Label the galaxy name--
        draw.text((0+fntsize*2, 0+fntsize*2), galaxy.upper(), font=font)
        #draw.text((sz[0]-fntsize*6, sz[1]-fntsize*3), galaxy.upper(), font=font)
        # Add a 30 arcsec scale bar--
        x0, x1, yy = sz[1]-fntsize*2-barlen30, sz[1]-fntsize*2, sz[0]-fntsize*2
        draw.line((x0, yy, x1, yy), fill='white', width=width)
        im.save(pngfile)    
        
        # Generate a thumbnail
        cmd = '/usr/bin/convert -thumbnail 300x300 {} {}'.format(pngfile, thumbfile)
        os.system(cmd)
        
        if verbose:
            print('Wrote {}'.format(pngfile))

In [29]:
indx = np.sum(sample['FRACMASKED'] < fraccut, axis=1) == 3
pngsample = sample[indx]
print('Identified {} groups with <2% pixels masked in grz.'.format(len(pngsample)))


Identified 174 groups with <2% pixels masked in grz.

In [30]:
pngsample


Out[30]:
<Table length=174>
GROUPIDGALAXYNMEMBERSRADECDIAMETERD25MAXD25MINBRICKNAME [6]FRACMASKED [3]
int32str1000int32float64float64float32float32float32bytes20float16
614652NGC0127,NGC0128,NGC013037.31392.86903333333189.737189.73742.47670073p027 ..4.7684e-07 .. 7.1526e-06
615005PGC1036047,PGC1036361,PGC103659338.2232-6.13836333333201.95153.475128.06410081m062 ..5.3823e-05 .. 7.7784e-05
615052PGC1028413,PGC143577,PGC14357938.3796-6.82209333333151.88527.425324.44280084m067 ..0.0 .. 0.0
615465PGC1101085,PGC1100823,PGC110045039.30705-2.13501666667141.45237.857425.01220093m022 ..1.2875e-05 .. 9.4175e-06
616934PGC090503,PGC1244573,PGC1244244312.656253.07869666667151.24939.641628.06410126p030 ..0.0 .. 0.0
617185PGC173062,PGC173063,PGC173070313.28545-3.63262196.09747.659730.77170131m037 ..9.6858e-05 .. 0.0
618099PGC003597,PGC914550,PGC173260315.12245-15.18764219.21648.769825.01220151m152 ..0.00061321 .. 0.0
619319PGC929534,PGC929768,PGC004283317.8845-14.0488366667185.51645.514726.19090178m140 ..0.0010471 .. 6.1274e-05
619432PGC1036232,PGC1036166,PGC144001,PGC1036077418.1082625-6.1616275288.21454.720624.44280179m062 ..0.0 .. 1.7881e-07
619720PGC875134,PGC134705,PGC874824318.7087-17.9446033333132.90636.995732.97250187m180 ..0.0 .. 9.5367e-07
..............................
690576PGC057765,PGC200337,PGC200336,PGC0577704244.527262521.56183589.090177.29526.8012445p215 ..0.0 .. 0.0
690589PGC057773,PGC057774,PGC057777,PGC0577794244.5595512.795227560.754531.488526.8012446p127 ..0.00067329 .. 0.0
690608PGC1878557,PGC057794,PGC0577933244.6406529.8097933333162.74340.56525.01222445p297 ..0.0012741 .. 0.0011616
692756PGC1667789,PGC1667493,PGC1666803,PGC16667354256.3519522.29306268.27847.659726.19092562p222 ..0.011276 .. 0.0
693255PGC1843188,PGC1843607,PGC18438943260.1804528.7068066667166.83838.739330.07122601p287 ..0.00020266 .. 0.00010228
702442PGC1638034,PGC1637505,PGC16367183327.8406520.8574966667272.39130.771725.01223278p207 ..0.00069332 .. 0.0
702871PGC214792,UGC11853,PGC11251253329.0723-1.16768113.97855.995325.01223291m012 ..0.0 .. 0.0
704272PGC1137098,PGC1137118,PGC11370403333.18625-0.691466666667186.90128.717824.44283331m007 ..2.6047e-05 .. 9.5367e-07
706306PGC193192,PGC193199,PGC1932013339.05731.4075366666782.929643.466225.59483391p015 ..0.0 .. 0.0
706866PGC1188417,PGC1187491,PGC11874163340.60671.20671333333157.13532.221925.59483406p012 ..0.00012779 .. 0.00039315

In [31]:
%time make_png(pngsample, verbose=False)


CPU times: user 2min 15s, sys: 10 s, total: 2min 25s
Wall time: 12min 42s

To test the webpage before release, do

  • rsync -auvP /global/cscratch1/sd/ioannis/dr5/gallery/png /global/project/projectdirs/cosmo/www/temp/ioannis/dr5/gallery/
  • rsync -auvP /global/cscratch1/sd/ioannis/dr5/gallery/index.html /global/project/projectdirs/cosmo/www/temp/ioannis/dr5/gallery/

In [58]:
reject = ['pgc1667789-pgc1667493-pgc1666803-pgc1666735',
          'pgc156324-pgc156326-pgc1151284',
          'pgc200252-pgc1591421-ngc3040',
          'pgc008096-ugc01617-ugc01618-pgc008116-ugc01620'
         ]
toss = np.zeros(len(pngsample), dtype=bool)
for ii, gg in enumerate(pngsample['GALAXY']):
    for rej in np.atleast_1d(reject):
        toss[ii] = rej in gg.lower().replace(',', '-')
        if toss[ii]:
            break
print('Rejecting {} groups.'.format(np.sum(toss)))
pngkeep = pngsample[~toss]
if np.sum(toss) > 0:
    pngrej = pngsample[toss]
else:
    pngrej = []


Rejecting 4 groups.

In [62]:
nperrow = 5
nrow = np.ceil(len(pngkeep) / nperrow).astype('int')
pngsplit = np.array_split(pngkeep, nrow)
print('Splitting the sample into {} rows with {} mosaics per row.'.format(nrow, nperrow))


Splitting the sample into 34 rows with 5 mosaics per row.

In [63]:
htmlfile = os.path.join(gallerydir, 'index.html')
baseurl = 'http://legacysurvey.org/viewer-dev'

In [66]:
with open(htmlfile, 'w') as html:
    html.write('<html><head>\n')
    html.write('<style type="text/css">\n')
    html.write('img.ls-gallery {display: block;}\n')
    html.write('td.ls-gallery {width: 20%; word-wrap: break-word;}\n')
    html.write('</style>\n')
    html.write('</head><body>\n')
    html.write('<h1>Image Gallery of Galaxy Groups</h1>\n')
    html.write("""<p>This gallery highlights some of the galaxy groups in the DR5 footprint.  The
    groups were identified using an input catalog of galaxies with an apparent angular diameter greater than 24 arcsec
    and a friends-of-friends algorithm with a linking length of 2.5 arcminute.  The color mosaics
    were generated using <a class="reference external" href="http://www.stsci.edu/~dcoe/trilogy">trilogy.py</a>.  
    Each thumbnail links to a larger image while the galaxy names below the thumbnails link to the
    <a href="http://legacysurvey.org/viewer">Sky Viewer</a>.  For reference, the horizontal white bar in 
    each image represents 30 arcsec.</p>\n""")
    html.write("""<p><a href="http://portal.nersc.gov/project/cosmo/data/legacysurvey/dr4/gallery/">
    Visit the DR4 Gallery.</a></p>\n""")
    html.write('<table><tbody>\n')
    for pngrow in pngsplit:
        html.write('<tr>\n')
        for gal in pngrow:
            galaxy = galaxyname(gal)
            pngfile = os.path.join('png', '{}.png'.format(galaxy))
            thumbfile = os.path.join('png', 'thumb-{}.png'.format(galaxy))
            img = 'class="ls-gallery" src="{}" alt="{}"'.format(thumbfile, galaxy.upper())
            html.write('<td class="ls-gallery"><a href="{}"><img {} /></a></td>\n'.format(pngfile, img))
        html.write('</tr><tr>\n')
        for gal in pngrow:
            galaxy = galaxyname(gal, html=True)
            href = '{}/?layer=decals-{}&ra={:.8f}&dec={:.8f}&zoom=13'.format(baseurl, dr, gal['RA'], gal['DEC'])
            html.write('<td class="ls-gallery"><a href="{}">{}</a></td>\n'.format(href, galaxy.upper()))
        html.write('</tr>\n')
    html.write('</table></tbody>')
    html.write('</body></html>\n')

In [60]:
htmlfile_reject = os.path.join(gallerydir, 'index-reject.html')

In [61]:
if len(pngrej) > 0:
    with open(htmlfile_reject, 'w') as html:
        html.write('<html><body>\n')
        for gal in pngrej:
            galaxy = galaxyname(gal)
            pngfile = os.path.join('png', '{}.png'.format(galaxy))
            img = 'class="ls-gallery" src="{}" alt="{}"'.format(pngfile, galaxy.upper())
            html.write('<a href="{}/?ra={:.8f}&dec={:.8f}&layer=decals-{}"><img {}></a>\n'.format(
                    baseurl, gal['RA'], gal['DEC'], dr, img))
        html.write('</body></html>\n')

In [ ]: