Skip to content

Commit 92cd3c7

Browse files
committed
ngmix_catalog now handles gauss, cm and bdf which required some updates to ngmix2gs() #79. Random rotations are now applied to the truth table g1g2 values. Closes #76
1 parent 7637186 commit 92cd3c7

File tree

5 files changed

+142
-63
lines changed

5 files changed

+142
-63
lines changed

balrog/balinput.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ def _register_input_type(self, input_type, CATMOD, LOADMOD, has_nobj=False):
6262

6363
return
6464

65-
def _setup_proxy_catalog(self, save_proxy=False):
65+
def _setup_proxy_catalog(self, save_proxy=False, get_subtype=False):
66+
# Some input catalogs have additional subtypes, like ngmix_catalog
67+
# Sometimes need to have this information for truth catalog updates, but
68+
# can only do it here if save_proxy is False
69+
6670
conf = deepcopy(self.gsconfig)
6771

6872
# Need to remove other input types as they may not have been registered
@@ -86,6 +90,11 @@ def _setup_proxy_catalog(self, save_proxy=False):
8690
self.cat = cat_proxy.getCatalog()
8791
self.nobjects = cat_proxy.getNObjects()
8892

93+
if get_subtype is True:
94+
self.sub_type = cat_proxy.getSubtype()
95+
else:
96+
self.sub_type = None
97+
8998
# Need to load in additional parametric catalog for some input types
9099
try:
91100
self.parametric_cat = cat_proxy.getParamCatalog()
@@ -97,9 +106,11 @@ def _setup_proxy_catalog(self, save_proxy=False):
97106
def generate_inj_catalog(self, config, tile, realization, mixed, mixed_grid=None):
98107
inp_type = self.input_type
99108
inj_type = self.inj_type
109+
sub_type = self.sub_type
100110

101111
self.inj_catalog = balobject.build_bal_inject_cat(inp_type,
102112
inj_type,
113+
sub_type,
103114
tile,
104115
needs_band=self.needs_band,
105116
mixed=mixed)
@@ -146,7 +157,8 @@ def __init__(self, gsconfig, indx=None, tilename=None):
146157
# TODO: Can we grab the injection type from the registered GS catalog?
147158
self.inj_type = 'ngmixGalaxy'
148159

149-
self._setup_proxy_catalog()
160+
# ngmix catalogs have additional subtypes, e.g. cm
161+
self._setup_proxy_catalog(get_subtype=True)
150162

151163
return
152164

balrog/balobject.py

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import numpy as np
22
import galsim
33
import galsim.config.input as gsinput
4+
import ngmix
45
import os
56
import copy
7+
from numpy.lib.recfunctions import append_fields
68

79
# Balrog files
810
import mathutil as util
911
import grid
1012

11-
# import pudb
12-
1313
# TODO: Implement as needed
1414
class BalObject(object):
1515
'''
@@ -20,9 +20,10 @@ def __init__(self):
2020

2121
class BalInjectionCatalog(object):
2222

23-
def __init__(self, input_type, inj_type, tile, needs_band=False, mixed=False):
23+
def __init__(self, input_type, inj_type, sub_type, tile, needs_band=False, mixed=False):
2424
self.input_type = input_type
2525
self.inj_type = inj_type
26+
self.sub_type = sub_type
2627
self.needs_band = needs_band
2728
self.mixed = False
2829

@@ -276,8 +277,24 @@ def get_truth_outfile(self, base_outfile, real):
276277
self.truth_outfile[real] = os.path.join(base_outfile, truth_fname)
277278
return self.truth_outfile[real]
278279

279-
def write_new_positions(self, truth_cat, realization):
280-
pos = self.pos[realization]
280+
def update_truth_cols(self, config, truth_cat, real):
281+
self.write_new_positions(truth_cat, real)
282+
self.update_colnames(truth_cat, real)
283+
284+
if self.rotate[real] is not None:
285+
self.update_truth_shapes(config, truth_cat, real)
286+
287+
def update_truth_shapes(self, config, truth_cat, real):
288+
# Only here to be inherited by subclasses, but not bothering with
289+
# a full abstract class for now
290+
raise NotImplementedError('Need to implement `update_truth_shapes()` to apply rotations to ' +
291+
'custom BalObjects!')
292+
293+
def update_colnames(self, truth_cat, real):
294+
pass
295+
296+
def write_new_positions(self, truth_cat, real):
297+
pos = self.pos[real]
281298

282299
# If nothing is set for a given custom input, try the obvious
283300
try:
@@ -294,9 +311,6 @@ def write_new_positions(self, truth_cat, realization):
294311

295312
return
296313

297-
def update_colnames(self, truth_cat, realization):
298-
pass
299-
300314
def setup_chip_config(self, config, bal_config, chip, chip_indx):
301315
# Many injection types will requite nothing special in setup
302316
pass
@@ -308,10 +322,10 @@ def build_multi_chip_config(self, config, bal_config, chip, chip_indx, input_ind
308322
pass
309323

310324
class DESInjectionCatalog(BalInjectionCatalog):
311-
def __init__(self, input_type, inj_type, tile, needs_band, mixed=False):
325+
def __init__(self, input_type, inj_type, sub_type, tile, needs_band, mixed=False):
312326
# All catalogs require band input
313327
assert needs_band is True
314-
super(DESInjectionCatalog, self).__init__(input_type, inj_type, tile, needs_band, mixed)
328+
super(DESInjectionCatalog, self).__init__(input_type, inj_type, sub_type, tile, needs_band, mixed)
315329

316330
return
317331

@@ -355,6 +369,33 @@ def generate_objects(self, config, realization, mixed_grid=None):
355369

356370
return mixed_grid
357371

372+
def update_truth_shapes(self, config, truth_cat, real):
373+
g_colname = self.sub_type + '_g'
374+
g1 = truth_cat[self.inj_type][g_colname][:,0]
375+
g2 = truth_cat[self.inj_type][g_colname][:,1]
376+
377+
# Need to unpack array of '{rotation in deg} deg'
378+
deg2rad = np.pi / 180.
379+
theta = np.array([float(r.split()[0].strip()) for r in self.rotate[real]])
380+
381+
g1_rot, g2_rot = ngmix.shape.rotate_shape(g1, g2, deg2rad*theta)
382+
383+
# Update truth catalog shape information
384+
truth_cat[self.inj_type][g_colname][:,0] = g1_rot
385+
truth_cat[self.inj_type][g_colname][:,1] = g2_rot
386+
387+
# These values are also stored in pars
388+
truth_cat[self.inj_type][self.sub_type+'_pars'][:,2] = g1_rot
389+
truth_cat[self.inj_type][self.sub_type+'_pars'][:,3] = g2_rot
390+
391+
# Add rotation angles to truth catalog
392+
truth_cat[self.inj_type] = append_fields(truth_cat[self.inj_type],
393+
'rotation',
394+
theta,
395+
usemask=False)
396+
397+
return
398+
358399
class MEDSInjectionCatalog(DESInjectionCatalog):
359400
def generate_objects(self, config, realization, mixed_grid=None):
360401
mixed_grid = super(MEDSInjectionCatalog, self).generate_objects(config,
@@ -409,8 +450,8 @@ def build_multi_chip_config(self, config, bal_config, chip, chip_indx, input_ind
409450
return
410451

411452
class DESStarInjectionCatalog(DESInjectionCatalog):
412-
def __init__(self, input_type, inj_type, tile, needs_band=False, mixed=False):
413-
super(DESStarInjectionCatalog, self).__init__(input_type, inj_type, tile, needs_band, mixed)
453+
def __init__(self, input_type, inj_type, sub_type, tile, needs_band=False, mixed=False):
454+
super(DESStarInjectionCatalog, self).__init__(input_type, inj_type, sub_type, tile, needs_band, mixed)
414455

415456
# Might add something like this in the future, if we end up passing configs
416457
# during init...
@@ -489,6 +530,10 @@ def _generate_sahar_coords(self, config, realization):
489530

490531
return
491532

533+
def update_truth_shapes(self, config, truth_cat, real):
534+
# Stars are injected as delta functions, so no need to update shape info
535+
pass
536+
492537
def write_new_positions(self, truth_cat, realization):
493538
# Currently, all used DES star catalogs have Sahar's naming scheme anyway,
494539
# so this is check is not needed
@@ -603,11 +648,12 @@ class Star(object):
603648
def __init__(self):
604649
pass
605650

606-
def build_bal_inject_cat(input_type, inj_type, tile, needs_band, mixed=False):
651+
def build_bal_inject_cat(input_type, inj_type, sub_type, tile, needs_band, mixed=False):
607652
if input_type in BALROG_INJECTION_TYPES:
608653
# User-defined injection catalog construction
609654
inject_cat = BALROG_INJECTION_TYPES[input_type](input_type,
610655
inj_type,
656+
sub_type,
611657
tile,
612658
needs_band,
613659
mixed)

balrog/balrog_injection.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,11 @@
1515
import tile as Tile
1616
import fileio as io
1717

18-
# Use for debugging
19-
# import pudb
20-
2118
#-------------------------------------------------------------------------------
2219
# Important todo's:
2320
# TODO: Check pixel origin for transformations!
2421

2522
# Some extra todo's:
26-
# TODO: Have code automatically check fitsio vs astropy
2723
# TODO: Add check for python path!
2824
# TODO: Fix some bugged multi-line print statements
2925

balrog/ngmix_catalog.py

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import logging
1313
import warnings
1414
from past.builtins import basestring # Python 2&3 compatibility
15-
# import pudb
1615

1716
# TODO: Include noise, pixscale
1817

@@ -54,29 +53,33 @@ class ngmixCatalog(object):
5453
_req_params = { 'file_name' : str, 'bands' : str}
5554
_opt_params = { 'dir' : str, 'catalog_type' : str, 'snr_min' : float, 'snr_max' : float,
5655
't_frac' : float, 't_min' : float, 't_max' : float, 'version' : str,
57-
'de_redden' : bool}
56+
'de_redden' : bool, 'TdByTe' : float}
5857
_single_params = []
5958
_takes_rng = False
6059

6160
# Only these ngmix catalog types currently supported
6261
# `gauss`: Single Gaussian fit
6362
# `cm`: Combined bulge+disk model
64-
# `mof`: CM model with multi-object fitting
63+
# `bdf`: CM model with buldge-disk ratio fixed
6564
# NOTE: In principle, should be able to handle any type supported by ngmix
66-
_valid_catalog_types = ['gauss','cm','mof']
65+
# See Issue #79
66+
_valid_catalog_types = ['gauss','cm','bdf']
6767

6868
# Only these color bands are currently supported for an ngmix catalog
6969
_valid_band_types = 'griz'
7070

7171
# Dictionary of color band flux to array index in ngmix catalogs
72+
# TODO: This should be grabbed from the fits header rather than be hardcoded
73+
# See Issue #80
7274
_band_index = {'g' : 0, 'r' : 1, 'i' : 2, 'z' : 3}
7375

74-
# The catalog column name prefix doens't always match the catalog type (e.g. 'mof' has a prefix
75-
# of 'cm' for most columns). Set this for each new supported catalog type.
76-
_cat_col_prefix = {'gauss' : 'gauss', 'cm' : 'cm', 'mof' : 'cm'}
76+
# NOTE: In previous versions, the catalog column name prefix didn't always
77+
# match the catalog type (e.g. 'mof' had a prefix of 'cm' for most columns).
78+
# This shouldn't be needed in the future but leaving for now
79+
_cat_col_prefix = {'gauss' : 'gauss', 'cm' : 'cm', 'bdf' : 'bdf'}
7780

7881
def __init__(self, file_name, bands, dir=None, catalog_type=None, snr_min=None, snr_max=None,
79-
t_frac=None, t_min=None, t_max=None, version=None, de_redden=False,
82+
t_frac=None, t_min=None, t_max=None, version=None, de_redden=False, TdByTe=None,
8083
_nobjects_only=False):
8184

8285
if dir:
@@ -102,10 +105,10 @@ def __init__(self, file_name, bands, dir=None, catalog_type=None, snr_min=None,
102105
# Attempt to determine catalog type from filename (this is generally true for DES ngmix
103106
# catalogs)
104107
match = 0
105-
for type in self._valid_catalog_types:
106-
if type in self.file_name:
108+
for t in self._valid_catalog_types:
109+
if t in self.file_name:
107110
match +=1
108-
self.cat_type = type
111+
self.cat_type = t
109112
# Reject if multiple matches
110113
if match == 0:
111114
raise ValueError("No inputted ngmix catalog type, and no matches in filename! "
@@ -114,8 +117,17 @@ def __init__(self, file_name, bands, dir=None, catalog_type=None, snr_min=None,
114117
raise ValueError("No inputted ngmix catalog type, and multiple matches in filename!"
115118
" Please set a valid catalog type.")
116119

120+
if TdByTe is not None:
121+
if self.cat_type != 'bdf':
122+
raise ValueError('Can only set a constant `TdByTe` for ngmix type `bdf`!')
123+
if TdByTe < 0:
124+
raise ValueError('TdByTe must be non-negative!')
125+
else:
126+
# This should almost always be 1
127+
TdByTe = 1.
128+
self._TdByTe = TdByTe
129+
117130
# Catalog column name prefixes don't always match catalog type
118-
# (e.g. 'cm' is still used for many 'mof' columns)
119131
self.col_prefix = self._cat_col_prefix[self.cat_type]
120132

121133
if isinstance(bands, basestring):
@@ -242,13 +254,13 @@ def getFlags(self):
242254
# General flags
243255
self.flags = self.catalog['flags']
244256

245-
# TODO: Look at these in more detail!
257+
# We don't want to cut on these explicitly anymore:
258+
246259
#self.obj_flags = self.catalog['obj_flags']
247260

248261
# ngmix catalog-specific flags
249262
#self.ngmix_flags = self.catalog[self.col_prefix+'_flags']
250263

251-
# TODO: Check for additional flags
252264
# if self.cat_type == 'mof':
253265
# # mof has additional flags
254266
# self.mof_flags = self.catalog[self.col_prefix+'_mof_flags']
@@ -269,7 +281,8 @@ def makeMask(self):
269281

270282
# For now, remove objects with any flags present
271283
mask[self.flags != 0] = False
272-
# TODO: We probably want to remove these!
284+
285+
# No longer do these explicitly:
273286
# mask[self.obj_flags !=0] = False
274287
# mask[self.ngmix_flags !=0] = False
275288
# Extra flags for 'mof' catalogs
@@ -408,24 +421,43 @@ def ngmix2gs(self, index, band, gsparams=None):
408421

409422
flux = self.catalog[flux_colname][index][self._band_index[band]]
410423

411-
# Gaussian-Mixture parameters are in the format of:
424+
# NOTE: It used to be the case that all Gaussian-Mixture parameters
425+
# were in the format of:
412426
# gm_pars = [centroid1, centroid2, g1, g2, T, flux]
413-
# (this is identical to ngmix catalogs, except that flux is a vector
414-
# of fluxes in all color bands)
415-
gm_pars = [0.0, 0.0, g1, g2, T, flux]
416-
417-
# Build the appropriate Gaussian mixture for a cm-model
418-
fracdev = self.catalog[cp+'_fracdev'][index]
419-
TdByTe = self.catalog[cp+'_TdByTe'][index]
420-
gm = ngmix.gmix.GMixCM(fracdev, TdByTe, gm_pars)
427+
# (this is identical to ngmix `pars`, except that flux is a vector
428+
# of fluxes in all bands)
429+
# However, this now depends on the gmix type, so we have to wait
430+
# gm_pars = [0.0, 0.0, g1, g2, T, flux]
431+
432+
# TODO: Implement this once we get a response back from Erin why
433+
# CM isn't included in this function
434+
# https://github.com/esheldon/ngmix/blob/master/ngmix/gmix.py#L39
435+
# This allows us to construct the given gmix type without knowing
436+
# gm.make_gmix_model(pars, model, **kw):
437+
438+
# Build the appropriate Gaussian mixture for a given model
439+
if ct == 'gauss':
440+
# Uses 'simple' pars scheme
441+
gm_pars = [0.0, 0.0, g1, g2, T, flux]
442+
gm = ngmix.gmix.GMixModel(gm_pars, 'gaussian')
443+
444+
elif ct == 'cm':
445+
fracdev = self.catalog[cp+'_fracdev'][index]
446+
TdByTe = self.catalog[cp+'_TdByTe'][index]
447+
# Uses 'simple' pars scheme
448+
gm_pars = [0.0, 0.0, g1, g2, T, flux]
449+
gm = ngmix.gmix.GMixCM(fracdev, TdByTe, gm_pars)
450+
451+
elif ct == 'bdf':
452+
fracdev = self.catalog[cp+'_fracdev'][index]
453+
TdByTe = self._TdByTe
454+
# Uses different 'bdf' pars scheme
455+
gm_pars = [0.0, 0.0, g1, g2, T, fracdev, flux]
456+
gm = ngmix.gmix.GMixBDF(pars=gm_pars, TdByTe=TdByTe)
421457

422458
# The majority of the conversion will be handled by `ngmix.gmix.py`
423459
gs_gal = gm.make_galsim_object(gsparams=gsp)
424460

425-
# NOTE: Can add any model-specific requirements in future if needed
426-
# if ct == 'mof':
427-
# gal = ...
428-
429461
return gs_gal
430462

431463
#----------------------------------------------------------------------------------------------
@@ -486,6 +518,11 @@ def selectRandomIndex(self, n_random=1, rng=None, _n_rng_calls=False):
486518

487519
#----------------------------------------------------------------------------------------------
488520

521+
# The catalog type is referred to as a 'subtype' w.r.t BalInput types
522+
# (i.e. the BalInput type is 'ngmix_catalog' with subtype self.catalog_type)
523+
def getSubtype(self):
524+
return self.cat_type
525+
489526
def getNObjects(self):
490527
# Used by input/logger methods
491528
return self.nobjects

0 commit comments

Comments
 (0)