Source code for astrobase.checkplot.pkl_png

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# pkl_png.py - Waqas Bhatti (wbhatti@astro.princeton.edu) - Feb 2019
# License: MIT.

'''
This contains utility functions that support the checkplot pickle to PNG export
functionality.

'''

#############
## LOGGING ##
#############

import logging
from astrobase import log_sub, log_fmt, log_date_fmt

DEBUG = False
if DEBUG:
    level = logging.DEBUG
else:
    level = logging.INFO
LOGGER = logging.getLogger(__name__)
logging.basicConfig(
    level=level,
    style=log_sub,
    format=log_fmt,
    datefmt=log_date_fmt,
)

LOGDEBUG = LOGGER.debug
LOGINFO = LOGGER.info
LOGWARNING = LOGGER.warning
LOGERROR = LOGGER.error
LOGEXCEPTION = LOGGER.exception


#############
## IMPORTS ##
#############

import os
import os.path

from io import BytesIO as StrIO

import numpy as np
from numpy import nan as npnan

# import from Pillow to generate pngs from checkplot dicts
from PIL import Image, ImageDraw, ImageFont


###################
## LOCAL IMPORTS ##
###################

from ..plotbase import METHODSHORTLABELS
from .pkl_io import _read_checkplot_picklefile, _base64_to_file


###################
## MAIN FUNCTION ##
###################

[docs]def checkplot_pickle_to_png( checkplotin, outfile, extrarows=None ): '''This reads the checkplot pickle or dict provided, and writes out a PNG. The output PNG contains most of the information in the input checkplot pickle/dict, and can be used to quickly glance through the highlights instead of having to review the checkplot with the `checkplotserver` webapp. This is useful for exporting read-only views of finalized checkplots from the `checkplotserver` as well, to share them with other people. The PNG has 4 x N tiles:: [ finder ] [ objectinfo ] [ varinfo/comments ] [ unphased LC ] [ periodogram1 ] [ phased LC P1 ] [ phased LC P2 ] [ phased LC P3 ] [ periodogram2 ] [ phased LC P1 ] [ phased LC P2 ] [ phased LC P3 ] . . [ periodogramN ] [ phased LC P1 ] [ phased LC P2 ] [ phased LC P3 ] for N independent period-finding methods producing: - periodogram1,2,3...N: the periodograms from each method - phased LC P1,P2,P3: the phased lightcurves using the best 3 peaks in each periodogram Parameters ---------- checkplotin : dict or str This is either a checkplotdict produced by :py:func:`astrobase.checkplot.pkl.checkplot_dict` or a checkplot pickle file produced by :py:func:`astrobase.checkplot.pkl.checkplot_pickle`. outfile : str The filename of the output PNG file to create. extrarows : list of tuples This is a list of 4-element tuples containing paths to PNG files that will be added to the end of the rows generated from the checkplotin pickle/dict. Each tuple represents a row in the final output PNG file. If there are less than 4 elements per tuple, the missing elements will be filled in with white-space. If there are more than 4 elements per tuple, only the first four will be used. The purpose of this kwarg is to incorporate periodograms and phased LC plots (in the form of PNGs) generated from an external period-finding function or program (like VARTOOLS) to allow for comparison with astrobase results. NOTE: the PNG files specified in `extrarows` here will be added to those already present in the input checkplotdict['externalplots'] if that is None because you passed in a similar list of external plots to the :py:func:`astrobase.checkplot.pkl.checkplot_pickle` function earlier. In this case, `extrarows` can be used to add even more external plots if desired. Each external plot PNG will be resized to 750 x 480 pixels to fit into an output image cell. By convention, each 4-element tuple should contain: - a periodiogram PNG - phased LC PNG with 1st best peak period from periodogram - phased LC PNG with 2nd best peak period from periodogram - phased LC PNG with 3rd best peak period from periodogram Example of extrarows:: [('/path/to/external/bls-periodogram.png', '/path/to/external/bls-phasedlc-plot-bestpeak.png', '/path/to/external/bls-phasedlc-plot-peak2.png', '/path/to/external/bls-phasedlc-plot-peak3.png'), ('/path/to/external/pdm-periodogram.png', '/path/to/external/pdm-phasedlc-plot-bestpeak.png', '/path/to/external/pdm-phasedlc-plot-peak2.png', '/path/to/external/pdm-phasedlc-plot-peak3.png'), ...] Returns ------- str The absolute path to the generated checkplot PNG. ''' if (isinstance(checkplotin, str) and os.path.exists(checkplotin)): cpd = _read_checkplot_picklefile(checkplotin) elif isinstance(checkplotin, dict): cpd = checkplotin else: LOGERROR('checkplotin: %s of type %s is not a ' 'valid checkplot filename (or does not exist), or a dict' % (os.path.abspath(checkplotin), type(checkplotin))) return None # figure out the dimensions of the output png # each cell is 750 x 480 pixels # a row is made of four cells # - the first row is for object info # - the rest are for periodograms and phased LCs, one row per method # if there are more than three phased LC plots per method, we'll only plot 3 if 'pfmethods' in cpd: cplspmethods = cpd['pfmethods'] else: cplspmethods = [] for pfm in METHODSHORTLABELS: if pfm in cpd: cplspmethods.append(pfm) cprows = len(cplspmethods) # add in any extra rows from neighbors if 'neighbors' in cpd and cpd['neighbors'] and len(cpd['neighbors']) > 0: nbrrows = len(cpd['neighbors']) else: nbrrows = 0 # add in any extra rows from keyword arguments if extrarows and len(extrarows) > 0: erows = len(extrarows) else: erows = 0 # add in any extra rows from the checkplot dict if ('externalplots' in cpd and cpd['externalplots'] and len(cpd['externalplots']) > 0): cpderows = len(cpd['externalplots']) else: cpderows = 0 totalwidth = 3000 totalheight = 480 + (cprows + erows + nbrrows + cpderows)*480 # this is the output PNG outimg = Image.new('RGBA',(totalwidth, totalheight),(255,255,255,255)) # now fill in the rows of the output png. we'll use Pillow to build up the # output image from the already stored plots and stuff in the checkplot # dict. ############################### # row 1, cell 1: finder chart # ############################### if cpd['finderchart']: finder = Image.open( _base64_to_file(cpd['finderchart'], None, writetostrio=True) ) bigfinder = finder.resize((450,450), Image.ANTIALIAS) outimg.paste(bigfinder,(150,20)) ##################################### # row 1, cell 2: object information # ##################################### # find the font we need from the package data fontpath = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', 'cpserver', 'cps-assets', 'DejaVuSans.ttf') ) # load the font if os.path.exists(fontpath): cpfontnormal = ImageFont.truetype(fontpath, 20) cpfontlarge = ImageFont.truetype(fontpath, 28) else: LOGWARNING('could not find bundled ' 'DejaVu Sans font in the astrobase package ' 'data, using ugly defaults...') cpfontnormal = ImageFont.load_default() cpfontlarge = ImageFont.load_default() # the image draw object objinfodraw = ImageDraw.Draw(outimg) # write out the object information # objectid objinfodraw.text( (625, 25), cpd['objectid'] if cpd['objectid'] else 'no objectid', font=cpfontlarge, fill=(0,0,255,255) ) # twomass id if 'twomassid' in cpd['objectinfo']: objinfodraw.text( (625, 60), ('2MASS J%s' % cpd['objectinfo']['twomassid'] if cpd['objectinfo']['twomassid'] else ''), font=cpfontnormal, fill=(0,0,0,255) ) # ndet if 'ndet' in cpd['objectinfo']: objinfodraw.text( (625, 85), ('LC points: %s' % cpd['objectinfo']['ndet'] if cpd['objectinfo']['ndet'] is not None else ''), font=cpfontnormal, fill=(0,0,0,255) ) else: objinfodraw.text( (625, 85), ('LC points: %s' % cpd['magseries']['times'].size), font=cpfontnormal, fill=(0,0,0,255) ) # coords and PM objinfodraw.text( (625, 125), ('Coords and PM'), font=cpfontnormal, fill=(0,0,0,255) ) if 'ra' in cpd['objectinfo'] and 'decl' in cpd['objectinfo']: objinfodraw.text( (900, 125), (('RA, Dec: %.3f, %.3f' % (cpd['objectinfo']['ra'], cpd['objectinfo']['decl'])) if (cpd['objectinfo']['ra'] is not None and cpd['objectinfo']['decl'] is not None) else ''), font=cpfontnormal, fill=(0,0,0,255) ) else: objinfodraw.text( (900, 125), 'RA, Dec: nan, nan', font=cpfontnormal, fill=(0,0,0,255) ) if 'propermotion' in cpd['objectinfo']: objinfodraw.text( (900, 150), (('Total PM: %.5f mas/yr' % cpd['objectinfo']['propermotion']) if (cpd['objectinfo']['propermotion'] is not None) else ''), font=cpfontnormal, fill=(0,0,0,255) ) else: objinfodraw.text( (900, 150), 'Total PM: nan', font=cpfontnormal, fill=(0,0,0,255) ) if 'rpmj' in cpd['objectinfo']: objinfodraw.text( (900, 175), (('Reduced PM [Jmag]: %.3f' % cpd['objectinfo']['rpmj']) if (cpd['objectinfo']['rpmj'] is not None) else ''), font=cpfontnormal, fill=(0,0,0,255) ) else: objinfodraw.text( (900, 175), 'Reduced PM [Jmag]: nan', font=cpfontnormal, fill=(0,0,0,255) ) # here, we have to deal with two generations of objectinfo dicts # first, deal with the new generation of objectinfo dicts if 'available_dereddened_bands' in cpd['objectinfo']: # # first, we deal with the bands and mags # # magnitudes objinfodraw.text( (625, 200), 'Magnitudes', font=cpfontnormal, fill=(0,0,0,255) ) # process the various bands # if dereddened mags aren't available, use the observed mags if len(cpd['objectinfo']['available_bands']) > 0: # we'll get all the available mags for bandind, band, label in zip( range(len(cpd['objectinfo']['available_bands'])), cpd['objectinfo']['available_bands'], cpd['objectinfo']['available_band_labels'] ): thisbandmag = cpd['objectinfo'][band] # we'll draw stuff in three rows depending on the number of # bands we have to use if bandind in (0,1,2,3,4): thispos = (900+125*bandind, 200) objinfodraw.text( thispos, '%s: %.3f' % (label, thisbandmag), font=cpfontnormal, fill=(0,0,0,255) ) elif bandind in (5,6,7,8,9): rowbandind = bandind - 5 thispos = (900+125*rowbandind, 225) objinfodraw.text( thispos, '%s: %.3f' % (label, thisbandmag), font=cpfontnormal, fill=(0,0,0,255) ) else: rowbandind = bandind - 10 thispos = (900+125*rowbandind, 250) objinfodraw.text( thispos, '%s: %.3f' % (label, thisbandmag), font=cpfontnormal, fill=(0,0,0,255) ) # # next, deal with the colors # # colors if ('dereddened' in cpd['objectinfo'] and cpd['objectinfo']['dereddened'] is True): deredlabel = "(dereddened)" else: deredlabel = "" objinfodraw.text( (625, 275), 'Colors %s' % deredlabel, font=cpfontnormal, fill=(0,0,0,255) ) if len(cpd['objectinfo']['available_colors']) > 0: # we'll get all the available mags (dereddened versions preferred) for colorind, color, colorlabel in zip( range(len(cpd['objectinfo']['available_colors'])), cpd['objectinfo']['available_colors'], cpd['objectinfo']['available_color_labels'] ): thiscolor = cpd['objectinfo'][color] # we'll draw stuff in three rows depending on the number of # bands we have to use if colorind in (0,1,2,3,4): thispos = (900+150*colorind, 275) objinfodraw.text( thispos, '%s: %.3f' % (colorlabel, thiscolor), font=cpfontnormal, fill=(0,0,0,255) ) elif colorind in (5,6,7,8,9): thisrowind = colorind - 5 thispos = (900+150*thisrowind, 300) objinfodraw.text( thispos, '%s: %.3f' % (colorlabel, thiscolor), font=cpfontnormal, fill=(0,0,0,255) ) elif colorind in (10,11,12,13,14): thisrowind = colorind - 10 thispos = (900+150*thisrowind, 325) objinfodraw.text( thispos, '%s: %.3f' % (colorlabel, thiscolor), font=cpfontnormal, fill=(0,0,0,255) ) else: thisrowind = colorind - 15 thispos = (900+150*thisrowind, 350) objinfodraw.text( thispos, '%s: %.3f' % (colorlabel, thiscolor), font=cpfontnormal, fill=(0,0,0,255) ) # otherwise, deal with older generation of checkplots else: objinfodraw.text( (625, 200), ('Magnitudes'), font=cpfontnormal, fill=(0,0,0,255) ) objinfodraw.text( (900, 200), ('gri: %.3f, %.3f, %.3f' % ((cpd['objectinfo']['sdssg'] if ('sdssg' in cpd['objectinfo'] and cpd['objectinfo']['sdssg'] is not None) else npnan), (cpd['objectinfo']['sdssr'] if ('sdssr' in cpd['objectinfo'] and cpd['objectinfo']['sdssr'] is not None) else npnan), (cpd['objectinfo']['sdssi'] if ('sdssi' in cpd['objectinfo'] and cpd['objectinfo']['sdssi'] is not None) else npnan))), font=cpfontnormal, fill=(0,0,0,255) ) objinfodraw.text( (900, 225), ('JHK: %.3f, %.3f, %.3f' % ((cpd['objectinfo']['jmag'] if ('jmag' in cpd['objectinfo'] and cpd['objectinfo']['jmag'] is not None) else npnan), (cpd['objectinfo']['hmag'] if ('hmag' in cpd['objectinfo'] and cpd['objectinfo']['hmag'] is not None) else npnan), (cpd['objectinfo']['kmag'] if ('kmag' in cpd['objectinfo'] and cpd['objectinfo']['kmag'] is not None) else npnan))), font=cpfontnormal, fill=(0,0,0,255) ) objinfodraw.text( (900, 250), ('BV: %.3f, %.3f' % ((cpd['objectinfo']['bmag'] if ('bmag' in cpd['objectinfo'] and cpd['objectinfo']['bmag'] is not None) else npnan), (cpd['objectinfo']['vmag'] if ('vmag' in cpd['objectinfo'] and cpd['objectinfo']['vmag'] is not None) else npnan))), font=cpfontnormal, fill=(0,0,0,255) ) # colors if ('dereddened' in cpd['objectinfo'] and cpd['objectinfo']['dereddened'] is True): deredlabel = "(dereddened)" else: deredlabel = "" objinfodraw.text( (625, 275), 'Colors %s' % deredlabel, font=cpfontnormal, fill=(0,0,0,255) ) objinfodraw.text( (900, 275), ('B - V: %.3f, V - K: %.3f' % ( (cpd['objectinfo']['bvcolor'] if ('bvcolor' in cpd['objectinfo'] and cpd['objectinfo']['bvcolor'] is not None) else npnan), (cpd['objectinfo']['vkcolor'] if ('vkcolor' in cpd['objectinfo'] and cpd['objectinfo']['vkcolor'] is not None) else npnan) )), font=cpfontnormal, fill=(0,0,0,255) ) objinfodraw.text( (900, 300), ('i - J: %.3f, g - K: %.3f' % ( (cpd['objectinfo']['ijcolor'] if ('ijcolor' in cpd['objectinfo'] and cpd['objectinfo']['ijcolor'] is not None) else npnan), (cpd['objectinfo']['gkcolor'] if ('gkcolor' in cpd['objectinfo'] and cpd['objectinfo']['gkcolor'] is not None) else npnan) )), font=cpfontnormal, fill=(0,0,0,255) ) objinfodraw.text( (900, 325), ('J - K: %.3f' % ( (cpd['objectinfo']['jkcolor'] if ('jkcolor' in cpd['objectinfo'] and cpd['objectinfo']['jkcolor'] is not None) else npnan),) ), font=cpfontnormal, fill=(0,0,0,255) ) # # rest of the object information # # color classification if ('color_classes' in cpd['objectinfo'] and cpd['objectinfo']['color_classes']): objinfodraw.text( (625, 375), ('star classification by color: %s' % (', '.join(cpd['objectinfo']['color_classes']))), font=cpfontnormal, fill=(0,0,0,255) ) # GAIA neighbors if ( ('gaia_neighbors' in cpd['objectinfo']) and (cpd['objectinfo']['gaia_neighbors'] is not None) and (np.isfinite(cpd['objectinfo']['gaia_neighbors'])) and ('searchradarcsec' in cpd['objectinfo']) and (cpd['objectinfo']['searchradarcsec']) ): objinfodraw.text( (625, 400), ('%s GAIA close neighbors within %.1f arcsec' % (cpd['objectinfo']['gaia_neighbors'], cpd['objectinfo']['searchradarcsec'])), font=cpfontnormal, fill=(0,0,0,255) ) # closest GAIA neighbor if ( ('gaia_closest_distarcsec' in cpd['objectinfo']) and (cpd['objectinfo']['gaia_closest_distarcsec'] is not None) and (np.isfinite(cpd['objectinfo']['gaia_closest_distarcsec'])) and ('gaia_closest_gmagdiff' in cpd['objectinfo']) and (cpd['objectinfo']['gaia_closest_gmagdiff'] is not None) and (np.isfinite(cpd['objectinfo']['gaia_closest_gmagdiff'])) ): objinfodraw.text( (625, 425), ('closest GAIA neighbor is %.1f arcsec away, ' 'GAIA mag (obj-nbr): %.3f' % (cpd['objectinfo']['gaia_closest_distarcsec'], cpd['objectinfo']['gaia_closest_gmagdiff'])), font=cpfontnormal, fill=(0,0,0,255) ) # object tags if 'objecttags' in cpd['objectinfo'] and cpd['objectinfo']['objecttags']: objtagsplit = cpd['objectinfo']['objecttags'].split(',') # write three tags per line nobjtaglines = int(np.ceil(len(objtagsplit)/3.0)) for objtagline in range(nobjtaglines): objtagslice = ','.join(objtagsplit[objtagline*3:objtagline*3+3]) objinfodraw.text( (625, 450+objtagline*25), objtagslice, font=cpfontnormal, fill=(135, 54, 0, 255) ) ################################################ # row 1, cell 3: variability info and comments # ################################################ # objectisvar objisvar = cpd['varinfo']['objectisvar'] if objisvar == '0': objvarflag = 'Variable star flag not set' elif objisvar == '1': objvarflag = 'Object is probably a variable star' elif objisvar == '2': objvarflag = 'Object is probably not a variable star' elif objisvar == '3': objvarflag = 'Not sure if this object is a variable star' elif objisvar is None: objvarflag = 'Variable star flag not set' elif objisvar is True: objvarflag = 'Object is probably a variable star' elif objisvar is False: objvarflag = 'Object is probably not a variable star' else: objvarflag = 'Variable star flag: %s' % objisvar objinfodraw.text( (1650, 125), objvarflag, font=cpfontnormal, fill=(0,0,0,255) ) # period objinfodraw.text( (1650, 150), ('Period [days]: %.6f' % (cpd['varinfo']['varperiod'] if cpd['varinfo']['varperiod'] is not None else np.nan)), font=cpfontnormal, fill=(0,0,0,255) ) # epoch objinfodraw.text( (1650, 175), ('Epoch [JD]: %.6f' % (cpd['varinfo']['varepoch'] if cpd['varinfo']['varepoch'] is not None else np.nan)), font=cpfontnormal, fill=(0,0,0,255) ) # variability tags if cpd['varinfo']['vartags']: vartagsplit = cpd['varinfo']['vartags'].split(',') # write three tags per line nvartaglines = int(np.ceil(len(vartagsplit)/3.0)) for vartagline in range(nvartaglines): vartagslice = ','.join(vartagsplit[vartagline*3:vartagline*3+3]) objinfodraw.text( (1650, 225+vartagline*25), vartagslice, font=cpfontnormal, fill=(135, 54, 0, 255) ) # object comments if 'comments' in cpd and cpd['comments']: commentsplit = cpd['comments'].split(' ') # write 10 words per line ncommentlines = int(np.ceil(len(commentsplit)/10.0)) for commentline in range(ncommentlines): commentslice = ' '.join( commentsplit[commentline*10:commentline*10+10] ) objinfodraw.text( (1650, 325+commentline*25), commentslice, font=cpfontnormal, fill=(0,0,0,255) ) # this handles JSON-ified checkplots returned by LCC server elif 'objectcomments' in cpd and cpd['objectcomments']: commentsplit = cpd['objectcomments'].split(' ') # write 10 words per line ncommentlines = int(np.ceil(len(commentsplit)/10.0)) for commentline in range(ncommentlines): commentslice = ' '.join( commentsplit[commentline*10:commentline*10+10] ) objinfodraw.text( (1650, 325+commentline*25), commentslice, font=cpfontnormal, fill=(0,0,0,255) ) ####################################### # row 1, cell 4: unphased light curve # ####################################### if (cpd['magseries'] and 'plot' in cpd['magseries'] and cpd['magseries']['plot']): magseries = Image.open( _base64_to_file(cpd['magseries']['plot'], None, writetostrio=True) ) outimg.paste(magseries,(750*3,0)) # this handles JSON-ified checkplots from LCC server elif ('magseries' in cpd and isinstance(cpd['magseries'],str)): magseries = Image.open( _base64_to_file(cpd['magseries'], None, writetostrio=True) ) outimg.paste(magseries,(750*3,0)) ############################### # the rest of the rows in cpd # ############################### for lspmethodind, lspmethod in enumerate(cplspmethods): ############################### # the periodogram comes first # ############################### if (cpd[lspmethod] and cpd[lspmethod]['periodogram']): pgram = Image.open( _base64_to_file(cpd[lspmethod]['periodogram'], None, writetostrio=True) ) outimg.paste(pgram,(0,480 + 480*lspmethodind)) ############################# # best phased LC comes next # ############################# if (cpd[lspmethod] and 0 in cpd[lspmethod] and cpd[lspmethod][0]): plc1 = Image.open( _base64_to_file(cpd[lspmethod][0]['plot'], None, writetostrio=True) ) outimg.paste(plc1,(750,480 + 480*lspmethodind)) # this handles JSON-ified checkplots from LCC server elif (cpd[lspmethod] and 'phasedlc0' in cpd[lspmethod] and isinstance(cpd[lspmethod]['phasedlc0']['plot'], str)): plc1 = Image.open( _base64_to_file(cpd[lspmethod]['phasedlc0']['plot'], None, writetostrio=True) ) outimg.paste(plc1,(750,480 + 480*lspmethodind)) ################################# # 2nd best phased LC comes next # ################################# if (cpd[lspmethod] and 1 in cpd[lspmethod] and cpd[lspmethod][1]): plc2 = Image.open( _base64_to_file(cpd[lspmethod][1]['plot'], None, writetostrio=True) ) outimg.paste(plc2,(750*2,480 + 480*lspmethodind)) # this handles JSON-ified checkplots from LCC server elif (cpd[lspmethod] and 'phasedlc1' in cpd[lspmethod] and isinstance(cpd[lspmethod]['phasedlc1']['plot'], str)): plc2 = Image.open( _base64_to_file(cpd[lspmethod]['phasedlc1']['plot'], None, writetostrio=True) ) outimg.paste(plc2,(750*2,480 + 480*lspmethodind)) ################################# # 3rd best phased LC comes next # ################################# if (cpd[lspmethod] and 2 in cpd[lspmethod] and cpd[lspmethod][2]): plc3 = Image.open( _base64_to_file(cpd[lspmethod][2]['plot'], None, writetostrio=True) ) outimg.paste(plc3,(750*3,480 + 480*lspmethodind)) # this handles JSON-ified checkplots from LCC server elif (cpd[lspmethod] and 'phasedlc2' in cpd[lspmethod] and isinstance(cpd[lspmethod]['phasedlc2']['plot'], str)): plc3 = Image.open( _base64_to_file(cpd[lspmethod]['phasedlc2']['plot'], None, writetostrio=True) ) outimg.paste(plc3,(750*3,480 + 480*lspmethodind)) ################################ ## ALL DONE WITH BUILDING PNG ## ################################ ######################### # add in any extra rows # ######################### # from the keyword arguments if erows > 0: for erowind, erow in enumerate(extrarows): # make sure we never go above 4 plots in a row for ecolind, ecol in enumerate(erow[:4]): eplot = Image.open(ecol) eplotresized = eplot.resize((750,480), Image.ANTIALIAS) outimg.paste(eplotresized, (750*ecolind, (cprows+1)*480 + 480*erowind)) # from the checkplotdict if cpderows > 0: for cpderowind, cpderow in enumerate(cpd['externalplots']): # make sure we never go above 4 plots in a row for cpdecolind, cpdecol in enumerate(cpderow[:4]): cpdeplot = Image.open(cpdecol) cpdeplotresized = cpdeplot.resize((750,480), Image.ANTIALIAS) outimg.paste(cpdeplotresized, (750*cpdecolind, (cprows+1)*480 + (erows*480) + 480*cpderowind)) # from neighbors: if nbrrows > 0: # we have four tiles # tile 1: neighbor objectid, ra, decl, distance, unphased LC # tile 2: phased LC for gls # tile 3: phased LC for pdm # tile 4: phased LC for any other period finding method # the priority is like so: ['bls','mav','aov','win'] for nbrind, nbr in enumerate(cpd['neighbors']): # figure out which period finding methods are available for this # neighbor. make sure to match the ones from the actual object in # order of priority: 'gls','pdm','bls','aov','mav','acf','win' nbrlspmethods = [] for lspmethod in cpd['pfmethods']: if lspmethod in nbr: nbrlspmethods.append(lspmethod) # restrict to top three in priority nbrlspmethods = nbrlspmethods[:3] try: # first panel: neighbor objectid, ra, decl, distance, unphased # LC nbrlc = Image.open( _base64_to_file( nbr['magseries']['plot'], None, writetostrio=True ) ) outimg.paste(nbrlc, (750*0, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind)) # overlay the objectinfo objinfodraw.text( (98, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind + 15), ('N%s: %s' % (nbrind + 1, nbr['objectid'])), font=cpfontlarge, fill=(0,0,255,255) ) # overlay the objectinfo objinfodraw.text( (98, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind + 50), ('(RA, DEC) = (%.3f, %.3f), distance: %.1f arcsec' % (nbr['ra'], nbr['decl'], nbr['dist'])), font=cpfontnormal, fill=(0,0,255,255) ) # second panel: phased LC for gls lsp1lc = Image.open( _base64_to_file( nbr[nbrlspmethods[0]][0]['plot'], None, writetostrio=True ) ) outimg.paste(lsp1lc, (750*1, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind)) # second panel: phased LC for gls lsp2lc = Image.open( _base64_to_file( nbr[nbrlspmethods[1]][0]['plot'], None, writetostrio=True ) ) outimg.paste(lsp2lc, (750*2, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind)) # second panel: phased LC for gls lsp3lc = Image.open( _base64_to_file( nbr[nbrlspmethods[2]][0]['plot'], None, writetostrio=True ) ) outimg.paste(lsp3lc, (750*3, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind)) except Exception: LOGERROR('neighbor %s does not have a magseries plot, ' 'measurements are probably all nan' % nbr['objectid']) # overlay the objectinfo objinfodraw.text( (98, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind + 15), ('N%s: %s' % (nbrind + 1, nbr['objectid'])), font=cpfontlarge, fill=(0,0,255,255) ) if 'ra' in nbr and 'decl' in nbr and 'dist' in nbr: # overlay the objectinfo objinfodraw.text( (98, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind + 50), ('(RA, DEC) = (%.3f, %.3f), distance: %.1f arcsec' % (nbr['ra'], nbr['decl'], nbr['dist'])), font=cpfontnormal, fill=(0,0,255,255) ) elif 'objectinfo' in nbr: # overlay the objectinfo objinfodraw.text( (98, (cprows+1)*480 + (erows*480) + (cpderows*480) + 480*nbrind + 50), ('(RA, DEC) = (%.3f, %.3f), distance: %.1f arcsec' % (nbr['objectinfo']['ra'], nbr['objectinfo']['decl'], nbr['objectinfo']['distarcsec'])), font=cpfontnormal, fill=(0,0,255,255) ) ##################### ## WRITE FINAL PNG ## ##################### is_strio = isinstance(outfile, StrIO) if not is_strio: # check if we've stupidly copied over the same filename as the input # pickle to expected output file if outfile.endswith('pkl'): LOGWARNING('expected output PNG filename ends with .pkl, ' 'changed to .png') outfile = outfile.replace('.pkl','.png') outimg.save(outfile, format='PNG', optimize=True) if not is_strio: if os.path.exists(outfile): LOGINFO('checkplot pickle -> checkplot PNG: %s OK' % outfile) return outfile else: LOGERROR('failed to write checkplot PNG') return None else: LOGINFO('checkplot pickle -> StringIO instance OK') return outfile
[docs]def cp2png(checkplotin, extrarows=None): '''This is just a shortened form of the function above for convenience. This only handles pickle files as input. Parameters ---------- checkplotin : str File name of a checkplot pickle file to convert to a PNG. extrarows : list of tuples This is a list of 4-element tuples containing paths to PNG files that will be added to the end of the rows generated from the checkplotin pickle/dict. Each tuple represents a row in the final output PNG file. If there are less than 4 elements per tuple, the missing elements will be filled in with white-space. If there are more than 4 elements per tuple, only the first four will be used. The purpose of this kwarg is to incorporate periodograms and phased LC plots (in the form of PNGs) generated from an external period-finding function or program (like VARTOOLS) to allow for comparison with astrobase results. NOTE: the PNG files specified in `extrarows` here will be added to those already present in the input `checkplotdict['externalplots']` if that is None because you passed in a similar list of external plots to the :py:func:`astrobase.checkplot.pkl.checkplot_pickle` function earlier. In this case, `extrarows` can be used to add even more external plots if desired. Each external plot PNG will be resized to 750 x 480 pixels to fit into an output image cell. By convention, each 4-element tuple should contain: - a periodiogram PNG - phased LC PNG with 1st best peak period from periodogram - phased LC PNG with 2nd best peak period from periodogram - phased LC PNG with 3rd best peak period from periodogram Example of extrarows:: [('/path/to/external/bls-periodogram.png', '/path/to/external/bls-phasedlc-plot-bestpeak.png', '/path/to/external/bls-phasedlc-plot-peak2.png', '/path/to/external/bls-phasedlc-plot-peak3.png'), ('/path/to/external/pdm-periodogram.png', '/path/to/external/pdm-phasedlc-plot-bestpeak.png', '/path/to/external/pdm-phasedlc-plot-peak2.png', '/path/to/external/pdm-phasedlc-plot-peak3.png'), ...] Returns ------- str The absolute path to the generated checkplot PNG. ''' if checkplotin.endswith('.gz'): outfile = checkplotin.replace('.pkl.gz','.png') else: outfile = checkplotin.replace('.pkl','.png') return checkplot_pickle_to_png(checkplotin, outfile, extrarows=extrarows)