GRAVI Pipeline Reference Manual 1.9.2
Loading...
Searching...
No Matches
gravity_pcacal.c
Go to the documentation of this file.
1/*
2 * This file is part of the GRAVI Pipeline
3 * Copyright (C) 2002,2003 European Southern Observatory
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24/*-----------------------------------------------------------------------------
25 Includes
26 -----------------------------------------------------------------------------*/
27
28#include <cpl.h>
29#include <stdio.h>
30#include <string.h>
31#include <time.h>
32
33#include "gravi_data.h"
34#include "gravi_pfits.h"
35#include "gravi_dfs.h"
36#include "gravi_utils.h"
37#include "gravi_calib.h"
38
39/*-----------------------------------------------------------------------------
40 Private function prototypes
41 -----------------------------------------------------------------------------*/
42
43static int gravity_pcacal_create(cpl_plugin *);
44static int gravity_pcacal_exec(cpl_plugin *);
45static int gravity_pcacal_destroy(cpl_plugin *);
46static int gravity_pcacal(cpl_frameset *, const cpl_parameterlist *);
47
48/*-----------------------------------------------------------------------------
49 Static variables
50 -----------------------------------------------------------------------------*/
51static char gravity_pcacal_short[] = "Generate static calibration files for flattening phase visibility data using the PCA method.";
53"This recipe produces a PCA calibration file from a set of calibration frames to be used for flattening phase visibility data.\n"
55 "* Select good input frames using tracking ratio criterion.\n"
56 "* Compute PCA decomposition for each baseline and polarisation channel\n"
57 "* Fit component model and write calibration product\n"
59 GRAVI_VIS_SINGLE_CALIB" ≥20 : input frames\n"
61 GRAVI_PHASE_PCA" : PCA calibration\n"
62 "";
63
64/* Detector "epochs" corresponding to thermal cycles: calibration valid within interval from one date to next. */
65#define N_EPOCH 3
66static double TIME_MJD_EPOCH_START[N_EPOCH] = {
67 57754.000000, // 2017-01-01
68 58758.000000, // 2019-10-02
69 59178.000000, // 2020-11-25
70};
71
72/* Minimum number of valid calibration frames to accept */
73static cpl_size MIN_CALIB_FRAMES = 20;
74
75/*-----------------------------------------------------------------------------
76 Function code
77 -----------------------------------------------------------------------------*/
78
79/*----------------------------------------------------------------------------*/
89/*----------------------------------------------------------------------------*/
90int cpl_plugin_get_info(cpl_pluginlist *list)
91{
92 cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe);
93 cpl_plugin *plugin = &recipe->interface;
94
95 if (cpl_plugin_init(plugin,
96 CPL_PLUGIN_API,
97 GRAVI_BINARY_VERSION,
98 CPL_PLUGIN_TYPE_RECIPE,
99 "gravity_pcacal",
102 "Calvin Sykes, Shangguan Jinyi, Sebastian Hoenig",
103 PACKAGE_BUGREPORT,
108 )) {
109 cpl_msg_error(cpl_func, "Plugin initialization failed");
110 (void)cpl_error_set_where(cpl_func);
111 return 1;
112 }
113
114 if (cpl_pluginlist_append(list, plugin)) {
115 cpl_msg_error(cpl_func, "Error adding plugin to list");
116 (void)cpl_error_set_where(cpl_func);
117 return 1;
118 }
119
120 return 0;
121}
122
123/*----------------------------------------------------------------------------*/
131/*----------------------------------------------------------------------------*/
132static int gravity_pcacal_create(cpl_plugin *plugin)
133{
134 cpl_recipe *recipe;
135
136 /* Do not create the recipe if an error code is already set */
137 if (cpl_error_get_code() != CPL_ERROR_NONE) {
138 cpl_msg_error(cpl_func, "%s():%d: An error is already set: %s",
139 cpl_func, __LINE__, cpl_error_get_where());
140 return (int)cpl_error_get_code();
141 }
142
143 if (plugin == NULL) {
144 cpl_msg_error(cpl_func, "Null plugin");
145 cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
146 }
147
148 /* Verify plugin type */
149 if (cpl_plugin_get_type(plugin) != CPL_PLUGIN_TYPE_RECIPE) {
150 cpl_msg_error(cpl_func, "Plugin is not a recipe");
151 cpl_ensure_code(0, (int)CPL_ERROR_TYPE_MISMATCH);
152 }
153
154 /* Get the recipe */
155 recipe = (cpl_recipe *)plugin;
156
157 /* Create the parameters list in the cpl_recipe object */
158 recipe->parameters = cpl_parameterlist_new();
159 if (recipe->parameters == NULL) {
160 cpl_msg_error(cpl_func, "Parameter list allocation failed");
161 cpl_ensure_code(0, (int)CPL_ERROR_ILLEGAL_OUTPUT);
162 }
163
164 /* Fill the parameters list */
165
166 /* Use static names (output_procatg.fits) */
167 gravi_parameter_add_static_name(recipe->parameters);
168
169 /* PCA parameters */
170 gravi_parameter_add_pcacalib(recipe->parameters);
171
172 return 0;
173}
174
175/*----------------------------------------------------------------------------*/
181/*----------------------------------------------------------------------------*/
182static int gravity_pcacal_exec(cpl_plugin * plugin)
183{
184
185 cpl_recipe *recipe;
186 int recipe_status;
187 cpl_errorstate initial_errorstate = cpl_errorstate_get();
188
189 /* Return immediately if an error code is already set */
190 if (cpl_error_get_code() != CPL_ERROR_NONE) {
191 cpl_msg_error(cpl_func, "%s():%d: An error is already set: %s",
192 cpl_func, __LINE__, cpl_error_get_where());
193 return (int)cpl_error_get_code();
194 }
195
196 if (plugin == NULL) {
197 cpl_msg_error(cpl_func, "Null plugin");
198 cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
199 }
200
201 /* Verify plugin type */
202 if (cpl_plugin_get_type(plugin) != CPL_PLUGIN_TYPE_RECIPE) {
203 cpl_msg_error(cpl_func, "Plugin is not a recipe");
204 cpl_ensure_code(0, (int)CPL_ERROR_TYPE_MISMATCH);
205 }
206
207 /* Get the recipe */
208 recipe = (cpl_recipe *)plugin;
209
210 /* Verify parameter and frame lists */
211 if (recipe->parameters == NULL) {
212 cpl_msg_error(cpl_func, "Recipe invoked with NULL parameter list");
213 cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
214 }
215 if (recipe->frames == NULL) {
216 cpl_msg_error(cpl_func, "Recipe invoked with NULL frame set");
217 cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
218 }
219
220 /* Invoke the recipe */
221 recipe_status = gravity_pcacal(recipe->frames, recipe->parameters);
222
223 /* Ensure DFS-compliance of the products */
224 if (cpl_dfs_update_product_header(recipe->frames)) {
225 if (!recipe_status) recipe_status = (int)cpl_error_get_code();
226 }
227
228 if (!cpl_errorstate_is_equal(initial_errorstate)) {
229 /* Dump the error history since recipe execution start.
230 At this point the recipe cannot recover from the error */
231 cpl_errorstate_dump(initial_errorstate, CPL_FALSE, NULL);
232 }
233
234 return recipe_status;
235}
236
237/*----------------------------------------------------------------------------*/
243/*----------------------------------------------------------------------------*/
244static int gravity_pcacal_destroy(cpl_plugin * plugin)
245{
246 cpl_recipe * recipe;
247
248 if (plugin == NULL) {
249 cpl_msg_error(cpl_func, "Null plugin");
250 cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
251 }
252
253 /* Verify plugin type */
254 if (cpl_plugin_get_type(plugin) != CPL_PLUGIN_TYPE_RECIPE) {
255 cpl_msg_error(cpl_func, "Plugin is not a recipe");
256 cpl_ensure_code(0, (int)CPL_ERROR_TYPE_MISMATCH);
257 }
258
259 /* Get the recipe */
260 recipe = (cpl_recipe *)plugin;
261
262 cpl_parameterlist_delete(recipe->parameters);
263
264 return 0;
265}
266
267/*----------------------------------------------------------------------------*/
276/*----------------------------------------------------------------------------*/
277static cpl_boolean gravi_test_tracking_ratio(const cpl_propertylist *hdr, int min_ratio)
278{
279 const int nbase = 6;
280 char tr_param_name[100];
281
282 for (int i = 0; i < nbase; i++) {
283 sprintf(tr_param_name, "ESO QC TRACKING_RATIO_FT%s", GRAVI_BASE_NAME[i]);
284 const cpl_property *tr_prop = cpl_propertylist_get_property_const(hdr, tr_param_name);
285
286 int tracking_ratio = 0;
287 if (cpl_property_get_type(tr_prop) == CPL_TYPE_INT)
288 tracking_ratio = cpl_property_get_int(tr_prop);
289 else if (cpl_property_get_type(tr_prop) == CPL_TYPE_DOUBLE)
290 tracking_ratio = (int) cpl_property_get_double(tr_prop);
291 else
292 cpl_msg_error(cpl_func, "Could not get tracking ratio");
293
294 if (tracking_ratio < min_ratio)
295 return CPL_FALSE;
296 }
297 return CPL_TRUE;
298}
299
300/*----------------------------------------------------------------------------*/
307/*----------------------------------------------------------------------------*/
308static int gravity_pcacal(cpl_frameset * frameset,
309 const cpl_parameterlist * parlist)
310{
311 cpl_frameset *vis_cal_frameset = NULL;
312 cpl_frameset *used_frameset = cpl_frameset_new();
313 cpl_frame *frame = NULL;
314
315 gravi_data *data_tmp = NULL, **data_accepted = NULL, *pca_data = NULL;
316 cpl_propertylist *hdr = NULL, *header_first = NULL, *wave_plist = NULL, *wv_plisti = NULL;
317 const char *telescope = NULL, *pola_mode = NULL, *spec_res = NULL;
318 int nframes, nwave, nwavei, npol, naccept;
319 const int nbase = 6;
320 char product_filename[100];
321
322 /* Message */
325
326 int min_tracking_ratio = cpl_parameter_get_int(
327 cpl_parameterlist_find_const(parlist, "gravity.calib.pca-tracking-ratio"));
328
329 /* Get the input frameset */
330 cpl_ensure_code(gravi_dfs_set_groups(frameset) == CPL_ERROR_NONE,
331 cpl_error_get_code());
332
333 vis_cal_frameset = gravi_frameset_extract_vis_calib(frameset);
334 if (cpl_frameset_is_empty(vis_cal_frameset)) {
335 cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
336 "No VIS_CAL on the frameset");
337 goto cleanup;
338 }
339
340 /* Get header data from first frame */
341 frame = cpl_frameset_get_position(vis_cal_frameset, 0);
342 data_tmp = gravi_data_load_frame(frame, NULL);
343 header_first = cpl_propertylist_duplicate(gravi_data_get_header(data_tmp));
344
345 telescope = cpl_propertylist_get_string(header_first, "TELESCOP");
346 pola_mode = gravi_pfits_get_pola_mode(header_first, GRAVI_SC);
347 npol = gravi_pfits_get_pola_num(header_first, GRAVI_SC);
348 spec_res = gravi_pfits_get_spec_res(header_first);
349
350 /* Check on time */
351 double time_mjd_obs = cpl_propertylist_get_double(header_first, "MJD-OBS");
352 int epoch = -1;
353 if (time_mjd_obs < TIME_MJD_EPOCH_START[0]) {
354 cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
355 "First frame is too old\n");
356 }
357 for (int i = 0; i < N_EPOCH; i++) {
358 /* Select latest epoch date that precedes observation date */
359 if (TIME_MJD_EPOCH_START[i] <= time_mjd_obs)
360 epoch = i;
361 }
362
363 /* Get length of wavelength axis */
364 wave_plist = gravi_data_get_oi_wave_plist(data_tmp, GRAVI_SC, 0, npol);
365 nwave = cpl_propertylist_get_int(wave_plist, "NWAVE");
366
367 FREE(gravi_data_delete, data_tmp);
368
369 cpl_msg_info(cpl_func, "INS.NAME=%s POLA.MODE=%s SPEC.RES=%s NPOL=%d NWAVE=%d",
370 telescope, pola_mode, spec_res, npol, nwave);
371
372 /* Check all frames for compatibility and reject if below tracking ratio */
373 naccept = 0;
374 nframes = cpl_frameset_get_size(vis_cal_frameset);
375 data_accepted = cpl_malloc(nframes * sizeof(gravi_data *));
376 for (int n = 0; n < nframes; n++) {
377 frame = cpl_frameset_get_position(vis_cal_frameset, n);
378 data_tmp = gravi_data_load_frame(frame, NULL);
379 CPLCHECK_CLEAN("Could not load input frame");
380
381 /* Check for uniform wavelength axis */
382 hdr = gravi_data_get_header(data_tmp);
383 wv_plisti = gravi_data_get_oi_wave_plist(data_tmp, GRAVI_SC, 0, npol);
384 nwavei = cpl_propertylist_get_int(wv_plisti, "NWAVE");
385 if (nwave != nwavei)
386 cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
387 "Input files have inconsistent wavelength axes");
388
389 /* Check for matching telescope */
390 if (strcmp(telescope, cpl_propertylist_get_string(hdr, "TELESCOP"))) {
391 cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
392 "Input files have multiple TELESCOP");
393 }
394
395 /* Check for matching polarisation mode */
396 if (strcmp(pola_mode, gravi_pfits_get_pola_mode(hdr, GRAVI_SC))) {
397 cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
398 "Input files have multiple INS.POLA.MODE");
399 }
400
401 /* Check for matching resolution */
402 if (strcmp(spec_res, gravi_pfits_get_spec_res(hdr))) {
403 cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
404 "Input files have multiple INS.SPEC.RES");
405 }
406
407 /* Check for matching epoch */
408 time_mjd_obs = cpl_propertylist_get_double(hdr, "MJD-OBS");
409 if (time_mjd_obs < TIME_MJD_EPOCH_START[epoch] ||
410 (epoch < N_EPOCH-1 && time_mjd_obs > TIME_MJD_EPOCH_START[epoch+1])) {
411 cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
412 "Input files span multiple epochs");
413 }
414
415 /* Check if visibilities are all zeroes (bad data?) */
416 cpl_boolean skip = CPL_FALSE;
417 cpl_table *vis_tmp = gravi_data_get_oi_vis(data_tmp, GRAVI_SC, n, npol);
418 for (int i = 0; i < nbase; i++) {
419 const cpl_array *vis_arr = cpl_table_get_array(vis_tmp, "VISPHI", i);
420 if ((cpl_array_get_max(vis_arr) < 1e-2) || (cpl_array_get_min(vis_arr) > -1e-2)) {
421 cpl_msg_warning(cpl_func, "Input file %s has invalid data and will be skipped [range=%f--%f].",
422 cpl_frame_get_filename(frame), cpl_array_get_max(vis_arr), cpl_array_get_min(vis_arr));
423 skip = CPL_TRUE;
424 break;
425 }
426 }
427
428 /* Check if tracking ratio for file exceeds threshold and discard if not */
429 if (!skip && gravi_test_tracking_ratio(hdr, min_tracking_ratio)) {
430 data_accepted[naccept] = data_tmp;
431 cpl_frameset_insert(used_frameset, frame);
432 ++naccept;
433 } else {
434 FREE(gravi_data_delete, data_tmp);
435 continue;
436 }
437 }
438 cpl_msg_info(cpl_func, "Accepting %d of %d frames", naccept, nframes);
439 data_accepted = cpl_realloc(data_accepted, naccept * sizeof(gravi_data *));
440
441 if (naccept == 0)
442 cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
443 "None of the input files satisfy the minimum tracking ratio criterion");
444
445 if (naccept < MIN_CALIB_FRAMES)
446 cpl_msg_warning(cpl_func, "Fewer than %lld valid frames provided, calibration may be inaccurate", MIN_CALIB_FRAMES);
447
448 if (cpl_error_get_code())
449 goto cleanup;
450
451 pca_data = gravi_compute_pca(data_accepted, naccept, parlist);
452 CPLCHECK_CLEAN("Could not compute PCA decomposition");
453
454 cpl_propertylist *product_header = gravi_data_get_plist(pca_data, GRAVI_PCA_EXT);
455 cpl_propertylist_append_double(product_header, "PCA EPOCH BEGIN", TIME_MJD_EPOCH_START[epoch]);
456 cpl_propertylist_append_double(product_header, "PCA EPOCH END", epoch < N_EPOCH-1 ? TIME_MJD_EPOCH_START[epoch+1] : 99999);
457
458 /* Add filenames for accepted files to output */
459 cpl_errorstate e_state = cpl_errorstate_get();
460 cpl_table *table = gravi_data_get_table(pca_data, GRAVI_PCA_RESID_EXT);
461 if (table) {
462 cpl_table_new_column(table, "FILES", CPL_TYPE_STRING);
463 for (int i = 0; i < naccept; i++) {
464 const cpl_frame *frame = cpl_frameset_get_position_const(used_frameset, i);
465 cpl_table_set_string(table, "FILES", i, cpl_frame_get_filename(frame));
466 }
467 } else {
468 /* Catch error from accessing null table */
469 cpl_errorstate_set(e_state);
470 }
471
472 char* mjd_str = gravi_convert_to_timestamp(TIME_MJD_EPOCH_START[epoch]);
473 sprintf(product_filename, "GRAVI.%s.%s_%s_%s.", mjd_str, spec_res, pola_mode, telescope);
474 gravi_data_save_new(pca_data, frameset, product_filename, NULL, parlist,
475 used_frameset, NULL, "gravity_phase_pca",
476 NULL, GRAVI_PHASE_PCA);
477 // frame = cpl_frameset_get_position(vis_cal_frameset, 0);
478 // gravi_data_save_new(pca_data, frameset, NULL, NULL, parlist,
479 // used_frameset, frame, "gravity_phase_pca",
480 // product_header, GRAVI_PHASE_PCA);
481
482cleanup:
483 FREE(cpl_propertylist_delete, header_first);
484 FREE(cpl_frameset_delete, vis_cal_frameset);
485 FREELOOP(gravi_data_delete, data_accepted, naccept);
486 FREE(gravi_data_delete, pca_data);
487
489 return (int)cpl_error_get_code();
490}
typedefCPL_BEGIN_DECLS struct _gravi_data_ gravi_data
Definition: gravi_data.h:39
#define gravi_data_get_header(data)
Definition: gravi_data.h:75
#define gravi_data_get_oi_vis(data, type, pol, npol)
Definition: gravi_data.h:46
#define gravi_data_get_oi_wave_plist(data, type, pol, npol)
Definition: gravi_data.h:69
#define GRAVI_RECIPE_OUTPUT
Definition: gravi_dfs.h:39
#define GRAVI_RECIPE_FLOW
Definition: gravi_dfs.h:37
#define GRAVI_RECIPE_INPUT
Definition: gravi_dfs.h:38
#define GRAVI_VIS_SINGLE_CALIB
Definition: gravi_dfs.h:97
#define GRAVI_PHASE_PCA
Definition: gravi_dfs.h:87
cpl_msg_info(cpl_func, "Compute WAVE_SCAN for %s", GRAVI_TYPE(type_data))
#define GRAVI_SC
Definition: gravi_pfits.h:165
#define GRAVI_PCA_EXT
Definition: gravi_pfits.h:76
#define GRAVI_PCA_RESID_EXT
Definition: gravi_pfits.h:77
#define CPLCHECK_CLEAN(msg)
Definition: gravi_utils.h:54
#define gravi_msg_function_exit(flag)
Definition: gravi_utils.h:85
#define FREE(function, variable)
Definition: gravi_utils.h:69
#define gravi_msg_function_start(flag)
Definition: gravi_utils.h:84
#define FREELOOP(function, variable, n)
Definition: gravi_utils.h:72
int cpl_plugin_get_info(cpl_pluginlist *list)
Build the list of available plugins, for this module.
static int gravity_pcacal_create(cpl_plugin *)
Setup the recipe options.
static cpl_boolean gravi_test_tracking_ratio(const cpl_propertylist *hdr, int min_ratio)
Test whether tracking ratio for all baselines exceeds a limit.
static char gravity_pcacal_short[]
static int gravity_pcacal_exec(cpl_plugin *)
Execute the plugin instance given by the interface.
static char gravity_pcacal_description[]
static cpl_size MIN_CALIB_FRAMES
static int gravity_pcacal_destroy(cpl_plugin *)
Destroy what has been created by the 'create' function.
static int gravity_pcacal(cpl_frameset *, const cpl_parameterlist *)
Compute the PCA model from the provided calibration data.
#define N_EPOCH
static double TIME_MJD_EPOCH_START[N_EPOCH]
gravi_data * gravi_compute_pca(gravi_data **data, int naccept, const cpl_parameterlist *params)
Fit model for visphi flattening using PCA.
Definition: gravi_calib.c:2945
cpl_propertylist * gravi_data_get_plist(gravi_data *self, const char *extname)
Get the propertylist from EXTNAME.
Definition: gravi_data.c:2049
gravi_data * gravi_data_load_frame(cpl_frame *frame, cpl_frameset *used_frameset)
Load a FITS file and create a gravi_data.
Definition: gravi_data.c:599
cpl_table * gravi_data_get_table(gravi_data *self, const char *extname)
Return a pointer on a table extension by its EXTNAME.
Definition: gravi_data.c:2096
cpl_error_code gravi_data_save_new(gravi_data *self, cpl_frameset *allframes, const char *filename, const char *suffix, const cpl_parameterlist *parlist, cpl_frameset *usedframes, cpl_frame *frame, const char *recipe, cpl_propertylist *applist, const char *proCatg)
Save a gravi data in a CPL-complian FITS file.
Definition: gravi_data.c:925
void gravi_data_delete(gravi_data *self)
Delete a gravi data.
Definition: gravi_data.c:146
cpl_frameset * gravi_frameset_extract_vis_calib(cpl_frameset *frameset)
Definition: gravi_dfs.c:1373
cpl_parameter * gravi_parameter_add_static_name(cpl_parameterlist *self)
Definition: gravi_dfs.c:464
cpl_parameter * gravi_parameter_add_pcacalib(cpl_parameterlist *self)
Add pca calibration parameters to the input parameter list.
Definition: gravi_dfs.c:247
cpl_error_code gravi_dfs_set_groups(cpl_frameset *set)
Set the group as RAW or CALIB in a frameset.
Definition: gravi_dfs.c:78
void gravity_print_banner(void)
Definition: gravi_dfs.c:61
int gravi_pfits_get_pola_num(const cpl_propertylist *plist, int type_data)
Definition: gravi_pfits.c:263
const char * gravi_pfits_get_pola_mode(const cpl_propertylist *plist, int type_data)
Definition: gravi_pfits.c:169
const char * gravi_pfits_get_spec_res(const cpl_propertylist *plist)
Definition: gravi_pfits.c:162
char * gravi_convert_to_timestamp(double mjd)
Definition: gravi_pfits.c:1196
char GRAVI_BASE_NAME[6][3]
Definition: gravi_utils.c:57
const char * gravi_get_license(void)
Get the pipeline copyright and license.
Definition: gravi_utils.c:104