pz-rail-astro-tools 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pz-rail-astro-tools might be problematic. Click here for more details.

@@ -0,0 +1,617 @@
1
+ """ Applying selection functions to catalog """
2
+
3
+ import os
4
+
5
+ import numpy as np
6
+ from ceci.config import StageParameter as Param
7
+ from rail.creation.degrader import Degrader
8
+ from scipy.interpolate import interp1d
9
+ from rail.core.utils import find_rail_file
10
+
11
+
12
+ class SpecSelection(Degrader):
13
+ """
14
+ The super class of spectroscopic selections.
15
+
16
+ Parameters
17
+ ----------
18
+ N_tot: integer
19
+ total number of down-sampled, spec-selected galaxies.
20
+ If N_tot is greater than the number of spec-sepected galaxies, then
21
+ it will be ignored.
22
+ nondetect_val: value to be removed for non detects
23
+ downsample: bool
24
+ If True, then downsample the pre-selected galaxies
25
+ to N_tot galaxies.
26
+ success_rate_dir: string, the path to the success rate files.
27
+ percentile_cut: If using color-based redshift cut, percentile in redshifts above which redshifts will be cut from the sample. Default is 100 (no cut)
28
+ colnames: a dictionary that includes necessary columns
29
+ (magnitudes, colors and redshift) for selection. For magnitudes, the keys are ugrizy; for colors, the keys are,
30
+ for example, gr standing for g-r; for redshift, the key is 'redshift'.
31
+ random_seed: random seed for reproducibility.
32
+
33
+ """
34
+
35
+ name = "specselection"
36
+ config_options = Degrader.config_options.copy()
37
+ config_options.update(
38
+ N_tot=Param(int, 10000, msg="Number of selected sources"),
39
+ nondetect_val=Param(float, 99.0, msg="value to be removed for non detects"),
40
+ downsample=Param(
41
+ bool,
42
+ True,
43
+ msg="If true, downsample the selected sources into a total number of N_tot",
44
+ ),
45
+ success_rate_dir=Param(
46
+ str,
47
+ find_rail_file("examples_data/creation_data/data/success_rate_data"),
48
+ msg="The path to the directory containing success rate files.",
49
+ ),
50
+ percentile_cut=Param(int, 100, msg="cut redshifts above this percentile"),
51
+ colnames=Param(
52
+ dict,
53
+ {
54
+ **{band: "mag_" + band + "_lsst" for band in "ugrizy"},
55
+ **{"redshift": "redshift"},
56
+ },
57
+ msg="a dictionary that includes necessary columns\
58
+ (magnitudes, colors and redshift) for selection. For magnitudes, the keys are ugrizy; for colors, the keys are, \
59
+ for example, gr standing for g-r; for redshift, the key is 'redshift'",
60
+ ),
61
+ random_seed=Param(int, 42, msg="random seed for reproducibility"),
62
+ )
63
+
64
+ def __init__(self, args, comm=None):
65
+ Degrader.__init__(self, args, comm=comm)
66
+ # validate the settings
67
+ self._validate_settings()
68
+ self.mask = None
69
+ self.rng = None
70
+
71
+ def _validate_settings(self):
72
+ """
73
+ Validate all the settings.
74
+ """
75
+
76
+ if self.config.N_tot < 0:
77
+ raise ValueError(
78
+ "Total number of selected sources must be a " "positive integer."
79
+ )
80
+ if os.path.exists(self.config.success_rate_dir) is not True:
81
+ raise ValueError(
82
+ "Success rate path: "
83
+ + self.config.success_rate_dir
84
+ + " does not exist!"
85
+ )
86
+
87
+ def validate_colnames(self, data):
88
+ """
89
+ Validate the column names of data table to make sure they have necessary information
90
+ for each selection.
91
+ colnames: a list of column names.
92
+ """
93
+ colnames = self.config.colnames.values()
94
+ check = all(item in data.columns for item in colnames)
95
+ if check is not True:
96
+ raise ValueError(
97
+ "Columns in the data are not enough for the selection."
98
+ + "The data should contain "
99
+ + str(list(colnames))
100
+ + ". \n"
101
+ )
102
+
103
+ def selection(self, data):
104
+ """
105
+ Selection functions. This should be overwritten by the subclasses
106
+ corresponding to different spec selections.
107
+ """
108
+
109
+ def invalid_cut(self, data):
110
+ """
111
+ This function removes entries in the data that have invalid magnitude values
112
+ (nondetect_val or NaN)
113
+ """
114
+ nondetect_val = self.config.nondetect_val
115
+ for band in "ugrizy":
116
+ if band not in self.config.colnames.keys():
117
+ continue
118
+ colname = self.config.colnames[band]
119
+ self.mask &= (np.abs(data[colname]) < nondetect_val) & (
120
+ ~np.isnan(data[colname])
121
+ )
122
+
123
+ def downsampling_N_tot(self):
124
+ """
125
+ Method to randomly sample down the objects to a given
126
+ number of data objects.
127
+ """
128
+ N_tot = self.config.N_tot
129
+ N_selected = np.count_nonzero(self.mask)
130
+ if N_tot > N_selected:
131
+ print(
132
+ "Warning: N_tot is greater than the size of spec-selected "
133
+ + "sample ("
134
+ + str(N_selected)
135
+ + "). The spec-selected sample "
136
+ + "is returned."
137
+ )
138
+ return
139
+ else:
140
+ idx_selected = np.where(self.mask)[0]
141
+ idx_keep = self.rng.choice(idx_selected, replace=False, size=N_tot)
142
+ # create a mask with only those entries enabled that have been
143
+ # selected
144
+ mask = np.zeros_like(self.mask)
145
+ mask[idx_keep] = True
146
+ # update the internal state
147
+ self.mask &= mask
148
+
149
+ def run(self):
150
+ """
151
+ Run the selection
152
+ """
153
+ self.rng = np.random.default_rng(seed=self.config.seed)
154
+ # get the bands and bandNames present in the data
155
+ data = self.get_data("input", allow_missing=True)
156
+ self.validate_colnames(data)
157
+ self.mask = np.product(~np.isnan(data.to_numpy()), axis=1)
158
+ self.invalid_cut(data)
159
+ self.selection(data)
160
+ if self.config.downsample is True:
161
+ self.downsampling_N_tot()
162
+
163
+ data_selected = data.iloc[np.where(self.mask == 1)[0]]
164
+
165
+ self.add_data("output", data_selected)
166
+
167
+ def __repr__(self):
168
+ """
169
+ Define how the model is represented and printed.
170
+ """
171
+
172
+
173
+ class SpecSelection_GAMA(SpecSelection):
174
+ """
175
+ The class of spectroscopic selections with GAMA.
176
+ The GAMA survey covers an area of 286 deg^2, with ~238000 objects
177
+ The necessary column is r band
178
+ """
179
+
180
+ name = "specselection_gama"
181
+
182
+ def selection(self, data):
183
+ """
184
+ GAMA selection function
185
+ """
186
+ print("Applying the selection from GAMA survey...")
187
+ self.mask *= data[self.config.colnames["r"]] < 19.87
188
+
189
+ def __repr__(self):
190
+ """
191
+ Define how the model is represented and printed.
192
+ """
193
+ # start message
194
+ printMsg = "Applying the GAMA selection."
195
+
196
+ return printMsg
197
+
198
+
199
+ class SpecSelection_BOSS(SpecSelection):
200
+ """
201
+ The class of spectroscopic selections with BOSS.
202
+ BOSS selection function is based on
203
+ http://www.sdss3.org/dr9/algorithms/boss_galaxy_ts.php
204
+ The selection has changed slightly compared to Dawson+13
205
+ BOSS covers an area of 9100 deg^2 with 893,319 galaxies.
206
+ For BOSS selection, the data should at least include gri bands.
207
+ """
208
+
209
+ name = "specselection_boss"
210
+
211
+ def selection(self, data):
212
+ """
213
+ The BOSS selection function.
214
+ """
215
+
216
+ print("Applying the selection from BOSS survey...")
217
+ # cut quantities (unchanged)
218
+ c_p = 0.7 * (
219
+ data[self.config.colnames["g"]] - data[self.config.colnames["r"]]
220
+ ) + 1.2 * (
221
+ data[self.config.colnames["r"]] - data[self.config.colnames["i"]] - 0.18
222
+ )
223
+ c_r = (
224
+ (data[self.config.colnames["r"]] - data[self.config.colnames["i"]])
225
+ - (data[self.config.colnames["g"]] - data[self.config.colnames["r"]]) / 4.0
226
+ - 0.18
227
+ )
228
+ d_r = (data[self.config.colnames["r"]] - data[self.config.colnames["i"]]) - (
229
+ data[self.config.colnames["g"]] - data[self.config.colnames["r"]]
230
+ ) / 8.0
231
+ # defining the LOWZ sample
232
+ # we cannot apply the r_psf - r_cmod cut
233
+ low_z = (
234
+ (data[self.config.colnames["r"]] > 16.0)
235
+ & (data[self.config.colnames["r"]] < 20.0)
236
+ & (np.abs(c_r) < 0.2) # 19.6
237
+ & (data[self.config.colnames["r"]] < 13.35 + c_p / 0.3)
238
+ ) # 13.5, 0.3
239
+ # defining the CMASS sample
240
+ # we cannot apply the i_fib2, i_psf - i_mod and z_psf - z_mod cuts
241
+ cmass = (
242
+ (data[self.config.colnames["i"]] > 17.5)
243
+ & (data[self.config.colnames["i"]] < 20.1)
244
+ & (d_r > 0.55) # 19.9
245
+ & (data[self.config.colnames["i"]] < 19.98 + 1.6 * (d_r - 0.7))
246
+ & ( # 19.86, 1.6, 0.8
247
+ (data[self.config.colnames["r"]] - data[self.config.colnames["i"]])
248
+ < 2.0
249
+ )
250
+ )
251
+ # NOTE: we ignore the CMASS sparse sample
252
+ self.mask *= low_z | cmass
253
+
254
+ def __repr__(self):
255
+ """
256
+ Define how the model is represented and printed.
257
+ """
258
+ # start message
259
+ printMsg = "Applying the BOSS selection."
260
+
261
+ return printMsg
262
+
263
+
264
+ class SpecSelection_DEEP2(SpecSelection):
265
+ """
266
+ The class of spectroscopic selections with DEEP2.
267
+ DEEP2 has a sky coverage of 2.8 deg^2 with ~53000 spectra.
268
+ For DEEP2, one needs R band magnitude, B-R/R-I colors which are not available for the time being.
269
+ So we use LSST gri bands now. When the conversion degrader is ready, this subclass will be updated
270
+ accordingly.
271
+ """
272
+
273
+ name = "specselection_deep2"
274
+
275
+ def photometryCut(self, data):
276
+ """
277
+ Applying DEEP2 photometric cut based on Newman+13.
278
+ This modified selection gives the best match to the data n(z) with
279
+ its cut at z~0.75 and the B-R/R-I distribution (Newman+13, Fig. 12)
280
+
281
+ Notes
282
+ -----
283
+ We cannot apply the surface brightness cut and do not apply the
284
+ Gaussian weighted sampling near the original colour cuts.
285
+ """
286
+ mask = (
287
+ (data[self.config.colnames["r"]] > 18.5)
288
+ & (data[self.config.colnames["r"]] < 24.1)
289
+ & ( # 24.1
290
+ (
291
+ data[self.config.colnames["g"]] - data[self.config.colnames["r"]]
292
+ < 2.45
293
+ * (
294
+ data[self.config.colnames["r"]]
295
+ - data[self.config.colnames["i"]]
296
+ )
297
+ - 0.2976
298
+ )
299
+ |
300
+ # 2.45, 0.2976
301
+ (
302
+ data[self.config.colnames["r"]] - data[self.config.colnames["i"]]
303
+ > 1.1
304
+ )
305
+ | (
306
+ data[self.config.colnames["g"]] - data[self.config.colnames["r"]]
307
+ < 0.5
308
+ )
309
+ )
310
+ ) # 0.5
311
+ # update the internal state
312
+ self.mask &= mask
313
+
314
+ def speczSuccess(self, data):
315
+ """
316
+ Spec-z success rate as function of r_AB for Q>=3 read of Figure 13 in
317
+ Newman+13 for DEEP2 fields 2-4. Values are binned in steps of 0.2 mag
318
+ with the first and last bin centered on 19 and 24.
319
+ """
320
+ success_R_bins = np.arange(18.9, 24.1 + 0.01, 0.2)
321
+ success_R_centers = (success_R_bins[1:] + success_R_bins[:-1]) / 2.0
322
+ # paper has given 1 - [sucess rate] in the histogram
323
+ success_rate_dir = self.config.success_rate_dir
324
+ success_R_rate = np.loadtxt(os.path.join(success_rate_dir, "DEEP2_success.txt"))
325
+ # interpolate the success rate as probability of being selected with
326
+ # the probability at R > 24.1 being 0
327
+ p_success_R = interp1d(
328
+ success_R_centers,
329
+ success_R_rate,
330
+ kind="quadratic",
331
+ bounds_error=False,
332
+ fill_value=(success_R_rate[0], 0.0),
333
+ )
334
+ # Randomly sample objects according to their success rate
335
+ random_draw = self.rng.random(len(data))
336
+ mask = random_draw < p_success_R(data[self.config.colnames["r"]])
337
+ # update the internal state
338
+ self.mask &= mask
339
+
340
+ def selection(self, data):
341
+ """
342
+ DEEP2 selection function
343
+ """
344
+ self.photometryCut(data)
345
+ self.speczSuccess(data)
346
+
347
+ def __repr__(self):
348
+ """
349
+ Define how the model is represented and printed.
350
+ """
351
+ # start message
352
+ printMsg = "Applying the DEEP2 selection."
353
+
354
+ return printMsg
355
+
356
+
357
+ class SpecSelection_VVDSf02(SpecSelection):
358
+ """
359
+ The class of spectroscopic selections with VVDSf02.
360
+ It covers an area of 0.5 deg^2 with ~10000 sources.
361
+ Necessary columns are i band magnitude and redshift.
362
+ """
363
+
364
+ name = "specselection_VVDSf02"
365
+
366
+ def photometryCut(self, data):
367
+ """
368
+ Photometric cut of VVDS 2h-field based on LeFèvre+05.
369
+
370
+ Notes
371
+ -----
372
+ The oversight of 1.0 magnitudes on the bright end misses 0.2% of galaxies.
373
+ """
374
+ mask = (data[self.config.colnames["i"]] > 17.5) & (
375
+ data[self.config.colnames["i"]] < 24.0
376
+ )
377
+ # 17.5, 24.0
378
+ self.mask &= mask
379
+
380
+ def speczSuccess(self, data):
381
+ """
382
+ Success rate of VVDS 2h-field
383
+
384
+ Notes
385
+ -----
386
+ We use a redshift-based and I-band based success rate
387
+ independently here since we do not know their correlation,
388
+ which makes the success rate worse than in reality.
389
+
390
+ Spec-z success rate as function of i_AB read of Figure 16 in
391
+ LeFevre+05 for the VVDS 2h field. Values are binned in steps of
392
+ 0.5 mag with the first starting at 17 and the last bin ending at 24.
393
+ """
394
+ success_I_bins = np.arange(17.0, 24.0 + 0.01, 0.5)
395
+ success_I_centers = (success_I_bins[1:] + success_I_bins[:-1]) / 2.0
396
+ success_rate_dir = self.config.success_rate_dir
397
+ success_I_rate = np.loadtxt(
398
+ os.path.join(success_rate_dir, "VVDSf02_I_success.txt")
399
+ )
400
+ # interpolate the success rate as probability of being selected with
401
+ # the probability at I > 24 being 0
402
+ p_success_I = interp1d(
403
+ success_I_centers,
404
+ success_I_rate,
405
+ kind="quadratic",
406
+ bounds_error=False,
407
+ fill_value=(success_I_rate[0], 0.0),
408
+ )
409
+ # Randomly sample objects according to their success rate
410
+ random_draw = self.rng.random(len(data))
411
+ mask = random_draw < p_success_I(data["mag_i_lsst"])
412
+ # Spec-z success rate as function of redshift read of Figure 13a/b in
413
+ # LeFevre+13 for VVDS deep sample. The listing is split by i_AB into
414
+ # ranges (17.5; 22.5] and (22.5; 24.0].
415
+ # NOTE: at z > 1.75 there are only lower limits (due to a lack of
416
+ # spec-z?), thus the success rate is extrapolated as 1.0 at z > 1.75
417
+ success_z_bright_centers, success_z_bright_rate = np.loadtxt(
418
+ os.path.join(success_rate_dir, "VVDSf02_z_bright_success.txt")
419
+ ).T
420
+ success_z_deep_centers, success_z_deep_rate = np.loadtxt(
421
+ os.path.join(success_rate_dir, "VVDSf02_z_deep_success.txt")
422
+ ).T
423
+ # interpolate the success rates as probability of being selected with
424
+ # the probability in the bright bin at z > 1.75 being 1.0 and the deep
425
+ # bin at z > 4.0 being 0.0
426
+ p_success_z_bright = interp1d(
427
+ success_z_bright_centers,
428
+ success_z_bright_rate,
429
+ kind="quadratic",
430
+ bounds_error=False,
431
+ fill_value=(success_z_bright_rate[0], 1.0),
432
+ )
433
+ p_success_z_deep = interp1d(
434
+ success_z_deep_centers,
435
+ success_z_deep_rate,
436
+ kind="quadratic",
437
+ bounds_error=False,
438
+ fill_value=(success_z_deep_rate[0], 0.0),
439
+ )
440
+ # Randomly sample objects according to their success rate
441
+ random_draw = self.rng.random(len(data))
442
+ iterator = zip(
443
+ [
444
+ data[self.config.colnames["i"]] <= 22.5,
445
+ data[self.config.colnames["i"]] > 22.5,
446
+ ],
447
+ [p_success_z_bright, p_success_z_deep],
448
+ )
449
+ for m, p_success_z in iterator:
450
+ mask[m] &= random_draw[m] < p_success_z(
451
+ data[self.config.colnames["redshift"]][m]
452
+ )
453
+ # update the internal state
454
+ self.mask &= mask
455
+
456
+ def selection(self, data):
457
+ self.photometryCut(data)
458
+ self.speczSuccess(data)
459
+
460
+ def __repr__(self):
461
+ """
462
+ Define how the model is represented and printed.
463
+ """
464
+ # start message
465
+ printMsg = "Applying the VVDSf02 selection."
466
+
467
+ return printMsg
468
+
469
+
470
+ class SpecSelection_zCOSMOS(SpecSelection):
471
+ """
472
+ The class of spectroscopic selections with zCOSMOS
473
+ It covers an area of 1.7 deg^2 with ~20000 galaxies.
474
+ For zCOSMOS, the data should at least include i band and redshift.
475
+ """
476
+
477
+ name = "specselection_zCOSMOS"
478
+
479
+ def photometryCut(self, data):
480
+ """
481
+ Photometry cut for zCOSMOS based on Lilly+09.
482
+ NOTE: This only includes zCOSMOS bright.
483
+ update the internal state
484
+ """
485
+ mask = (data[self.config.colnames["i"]] > 15.0) & (
486
+ data[self.config.colnames["i"]] < 22.5
487
+ )
488
+ # 15.0, 22.5
489
+ self.mask &= mask
490
+
491
+ def speczSuccess(self, data):
492
+ """
493
+ Spec-z success rate as function of redshift (x) and I_AB (y) read of
494
+ Figure 3 in Lilly+09 for zCOSMOS bright sample.
495
+ """
496
+ success_rate_dir = self.config.success_rate_dir
497
+ x = np.arange(0, 1.4, 0.00587002, dtype=np.float64)
498
+ y = np.arange(18, 22.4, 0.01464226, dtype=np.float64)
499
+
500
+ pixels_y = np.searchsorted(y, data[self.config.colnames["i"]])
501
+ pixels_x = np.searchsorted(x, data[self.config.colnames["redshift"]])
502
+
503
+ rates = np.loadtxt(os.path.join(success_rate_dir, "zCOSMOS_success.txt"))
504
+ ratio_list = np.zeros(len(pixels_y))
505
+ for i, py in enumerate(pixels_y):
506
+ if (
507
+ (py >= rates.shape[0])
508
+ or (pixels_x[i] >= rates.shape[1])
509
+ or (py == 0)
510
+ or (pixels_x[i] == 0)
511
+ ):
512
+ ratio_list[i] = 0
513
+ else:
514
+ ratio_list[i] = rates[pixels_y[i] - 1][pixels_x[i] - 1]
515
+
516
+ randoms = self.rng.uniform(size=data[self.config.colnames["i"]].size)
517
+ mask = randoms <= ratio_list
518
+ self.mask &= mask
519
+
520
+ def selection(self, data):
521
+ self.photometryCut(data)
522
+ self.speczSuccess(data)
523
+
524
+ def __repr__(self):
525
+ """
526
+ Define how the model is represented and printed.
527
+ """
528
+ # start message
529
+ printMsg = "Applying the zCOSMOS selection."
530
+
531
+ return printMsg
532
+
533
+
534
+ class SpecSelection_HSC(SpecSelection):
535
+ """
536
+ The class of spectroscopic selections with HSC
537
+ or HSC, the data should at least include giz bands and redshift.
538
+ """
539
+
540
+ name = "specselection_HSC"
541
+
542
+ def photometryCut(self, data):
543
+ """
544
+ HSC galaxies were binned in color magnitude space with i-band mag from -2 to 6 and g-z color from 13 to 26.
545
+ """
546
+ mask = (data[self.config.colnames["i"]] > 13.0) & (
547
+ data[self.config.colnames["i"]] < 26.0
548
+ )
549
+ self.mask &= mask
550
+ gz = data[self.config.colnames["g"]] - data[self.config.colnames["z"]]
551
+ mask = (gz > -2.0) & (gz < 6.0)
552
+ self.mask &= mask
553
+
554
+ def speczSuccess(self, data):
555
+ """
556
+ HSC galaxies were binned in color magnitude space with i-band mag from -2 to 6 and g-z color from 13 to 26
557
+ 200 bins in each direction. The ratio of of galaxies with spectroscopic redshifts (training galaxies) to
558
+ galaxies with only photometry in HSC wide field (application galaxies) was computed for each pixel. We divide
559
+ the data into the same pixels and randomly select galaxies into the training sample based on the HSC ratios
560
+ """
561
+ success_rate_dir = self.config.success_rate_dir
562
+ x_edge = np.linspace(13, 26, 201, endpoint=True)
563
+ y_edge = np.linspace(-2, 6, 201, endpoint=True)
564
+
565
+ rates = np.loadtxt(os.path.join(success_rate_dir, "hsc_success.txt"))
566
+
567
+ pixels_y = np.searchsorted(
568
+ y_edge, data[self.config.colnames["g"]] - data[self.config.colnames["z"]]
569
+ )
570
+ pixels_x = np.searchsorted(x_edge, data[self.config.colnames["i"]])
571
+
572
+ # Do the color-based, percentile-based redshift cut
573
+
574
+ percentile_cut = self.config.percentile_cut
575
+
576
+ mask_keep = np.ones_like(data[self.config.colnames["i"]])
577
+ if percentile_cut != 100: # pragma: no cover
578
+ pixels_y_unique = np.unique(pixels_y)
579
+ pixels_x_unique = np.unique(pixels_x)
580
+
581
+ for y in pixels_y_unique:
582
+ for x in pixels_x_unique:
583
+ ind_inpix = np.where((pixels_y == y) * (pixels_x == x))[0]
584
+ if ind_inpix.size == 0:
585
+ continue
586
+ redshifts = data[self.config.colnames["redshift"]][ind_inpix]
587
+ percentile = np.percentile(redshifts, percentile_cut)
588
+ ind_remove = ind_inpix[redshifts > percentile]
589
+ mask_keep[ind_remove] = 0
590
+ self.mask &= mask_keep
591
+
592
+ pixels_y = pixels_y - 1
593
+ pixels_x = pixels_x - 1
594
+
595
+ ratio_list = np.zeros(len(pixels_y))
596
+ for i, py in enumerate(pixels_y):
597
+ if (py >= rates.shape[0]) or (pixels_x[i] >= rates.shape[1]):
598
+ ratio_list[i] = 0
599
+ else:
600
+ ratio_list[i] = rates[pixels_y[i]][pixels_x[i]]
601
+
602
+ randoms = self.rng.uniform(size=data[self.config.colnames["i"]].size)
603
+ mask = randoms <= ratio_list
604
+ self.mask &= mask
605
+
606
+ def selection(self, data):
607
+ self.photometryCut(data)
608
+ self.speczSuccess(data)
609
+
610
+ def __repr__(self):
611
+ """
612
+ Define how the model is represented and printed.
613
+ """
614
+ # start message
615
+ printMsg = "Applying the HSC selection."
616
+
617
+ return printMsg