Commit c47d69129cf8073d9978befc2e1152cbe3e4731a

Authored by Jürgen Knödlseder
Committed by Luigi Tibaldo
1 parent 81cbfae6

Integrate code

ChangeLog
1   -2018-11-23
  1 +2018-12-11
2 2  
3 3 * Version 1.6.0 released
4 4 ========================
... ... @@ -7,6 +7,7 @@
7 7 CTA background rates are now per ontime (#2758)
8 8 Apply event bin weighting in ctmodel for stacked computation (#2721)
9 9 Enable ctbin to produce counts cubes for a joint binned analysis (#2714)
  10 + Add ctfindvar tool (#2713)
10 11 Add Jupyter notebooks for H.E.S.S. DR1 (#2712)
11 12 csphagen now creates appropriate model for likelihood fitting (#2711)
12 13 csphagen now supports any kind of background model (#2710)
... ...
1 1 New Features and Important Changes in ctools 1.6.0
2 2  
3   -23 November 2018
  3 +11 December 2018
4 4  
5 5  
6 6 Introduction
... ... @@ -126,6 +126,12 @@ ctexpcube - CTA exposure cube generation
126 126 None
127 127  
128 128  
  129 +ctfindvar - Variability search
  130 +------------------------------
  131 +New: the ctfindvar tool searches for source variability using an On/Off method
  132 +in the time domain (#2713).
  133 +
  134 +
129 135 ctlike - Maximum likelihood fitting
130 136 -----------------------------------
131 137 Add hidden "like_accuracy" parameter to allow control of the accuracy of
... ...
doc/source/admin/release_history/1.6.rst
... ... @@ -14,6 +14,7 @@ In particular, this release provides:
14 14 * Support for Xspec spectral analysis
15 15 * Improved computation speed for :ref:`ctskymap` and :ref:`csphagen`
16 16 * Parallelisation of various cscripts
  17 +* Support for source variability searches
17 18 * Support for Python pickeling
18 19 * A Code Generator to facilitate adding ctools and cscripts
19 20  
... ... @@ -102,6 +103,8 @@ Improvements
102 103 New features
103 104 ------------
104 105  
  106 +* [`2713 <https://cta-redmine.irap.omp.eu/issues/2713>`_] -
  107 + Add :ref:`ctfindvar` tool
105 108 * [`2709 <https://cta-redmine.irap.omp.eu/issues/2709>`_] -
106 109 Add code generator
107 110 * [`2694 <https://cta-redmine.irap.omp.eu/issues/2694>`_] -
... ...
doc/source/users/reference_manual/ctfindvar.rst
1 1 .. _ctfindvar:
2 2  
3 3 ctfindvar
4   -==============================
  4 +=========
5 5  
6   -ToDo: Describe in a one liner what the tool is doing.
  6 +Search for source variability.
7 7  
8 8  
9 9 Synopsis
10 10 --------
11   -This tool is used to search for source variability.
12   -The variability is computed by binning the arrival time of the photons
13   -and applying the on-off method to each bin, integrating the background
14   -over all the other bins.
15   -A Limit is set to include a bin in the background sample, i.e., its
16   -significance with respect to all the others bins has to be lower than 4.5
17   -The significance of each bin is computed using Li&Ma eq. 17.
18   -A limit on the background sample can also be included, taking only into
19   -account bins that have a number of counts which is higher than a threshold
20   -defined by the user via the "minoff" parameter.
21   -
22   -Variability is calculated for each pixel of the sky given in the input files.
23   -The results of the variability is further stored in the srcsig.fits file,
24   -where the GTIs and the significance of each bin are stored.
25   -Only the sources with a significance > 4.5 sigmas are stored.
26   -
27   -:ref:`ctfindvar` takes in input some observation files.
28   -It calculates a cube with several maps, each one with a count number integrated
29   -over a time period defined by the tinterval parameter, for each pixel.
30   -The tinterval parameter is the time scale over which the variability
31   -will be searched.
32   -
33   -Inputs files:
34   -- observation file xml, or event list .fits
35   -
36   -Output files:
37   -- countscube.fits
38   - provides all the count-skymaps contained in the cube.
39   -- peaksigmap.fits
40   - a skymap of the highest significance for each pixel
41   -- srcsig.fits
42   - a table containing the GTIs together with the significance
43   - for all the sources with a significant variability detected.
44   -
45   -
46   -The significance of a source can be displayed using the
47   -``show_variability_evolution.py`` script in the example forlder
  11 +
  12 +This tool searches for source variability by applying the On/Off method in the
  13 +time domain. The variability of sky map pixels is computed by binning the events
  14 +as function of the photon arrival time and applying the On/Off method to each
  15 +time bin, integrating the background over all the other time bins. Only bins
  16 +with a variabilty significance below a given threshold, defined by the hidden
  17 +``threshold`` parameter, are included in the background estimate. Furthermore,
  18 +only time bins that exceed a minimum number of events, specified by the hidden
  19 +``minoff`` parameter, are considered for the background estimate. The significance
  20 +of each time bin is computed using Li & Ma formula.
  21 +
  22 +:ref:`ctfindvar` will search a grid of sky map pixels for source variability. Using
  23 +the hidden ``smooth_kernel`` and ``smooth_rad`` parameters, the sky map may be
  24 +smoothed before the variability search. The tool will compute sky maps for a
  25 +number of time bins. The duration of each time bin is specified by the ``tinterval``
  26 +parameter, the start and stop time are defined by the ``tmin`` and ``tmax``
  27 +parameters. If the start and stop time are ``NONE``, the start and stop times will
  28 +be extracted from the input event list(s). Note that ``tinterval`` specifies the
  29 +time scale over which the variability will be searched.
  30 +
  31 +The tool will determine the sky map pixel with the maximum variability and store
  32 +the variability histogram and pixel position as ``MAXSIGPIXEL`` in the output file,
  33 +specified using the ``outmap`` parameter. In addition, the tool will determine the
  34 +variability for a number of source positions. If an input model is specified
  35 +using the ``inmodel`` parameter, the source positions will be defined by the centre
  36 +positions of all gamma-ray source components in the input model. If no input
  37 +model is specified, the tool will use the ``xsrc`` and ``ysrc`` parameters to allow
  38 +the specification of a source position. The results will be stored in the output
  39 +file using either the source names found in the model definition file, or the
  40 +name ``SOURCE`` if the source position was specified by the ``xsrc`` and ``ysrc``
  41 +parameters.
  42 +
  43 +All sky map pixels with a variability above the value specified by the ``threshold``
  44 +will also added be as a point source model to the ``outmodel`` file. Each source will
  45 +have a power law as the spectral component with a total flux that corresponds to
  46 +1% of the Crab nebula flux.
  47 +
48 48  
49 49 General parameters
50 50 ------------------
... ... @@ -56,7 +56,17 @@ General parameters
56 56 Input model definition file for extracting source positions.
57 57  
58 58 ``(outcube = NONE) [file]``
59   - Filename for saving counts cube, NONE will result in no cube being saved.
  59 + Filename for saving counts cube, ``NONE`` will result in no cube being saved.
  60 +
  61 +``outmap [file]``
  62 + Filename for output sky map containing the maximum significance for each
  63 + spatial pixel. The file also contains the variability significance as function
  64 + of time for each source of interest, and the pixel with the maximum
  65 + significance. Furthermore, it contains the Right Ascension and Declination
  66 + of each source of interest and the pixel with the maximum significance.
  67 +
  68 +``outmodel [file]``
  69 + Output model definition file containing sources for significant pixels.
60 70  
61 71 ``caldb [string]``
62 72 Calibration database (only required for IRF background subtraction if no
... ... @@ -66,40 +76,45 @@ General parameters
66 76 Instrument response function (only required for IRF background subtraction
67 77 if no response information is provided by ``inobs``).
68 78  
69   -``prefix [string]``
70   - Output file prefix. The method will save two files:
71   - - Skymap containing the maximum significance for each spatial pixel
72   - - File containing the significance as a function of time interval for each
73   - source
74 79  
75   -``(minoff = 0) [real]``
76   - Minimum number of off counts to compute the significance
  80 +Variability search parameters
  81 +-----------------------------
  82 +
  83 +``coordsys <CEL|GAL> [string]``
  84 + Coordinate system (CEL - celestial, GAL - galactic).
  85 +
  86 +``xsrc [real]``
  87 + Right Ascension / Galactic longitude of source of interest (J2000, in degrees)
  88 +
  89 +``ysrc [real]``
  90 + Declination / Galactic latitude of source of interest (J2000, in degrees)
77 91  
78 92 ``emin [real]``
79   - Minimum energy (TeV) for extracting events. A value of 0 results in no cut
80   - being applied.
  93 + Minimum energy (TeV) for extracting events.
81 94  
82 95 ``emax [real]``
83   - Maximum energy (TeV) for extracting events. A value of 0 results in no cut
84   - being applied.
  96 + Maximum energy (TeV) for extracting events.
85 97  
86   -``(sig_thr = 4.5) [real]``
  98 +``(threshold = 4.5) [real]``
87 99 Significance threshold for variability detection.
88 100  
89   -``(smoothkrnl = NONE) <GAUSSIAN|DISK|NONE> [string]``
  101 +``(minoff = 0) [real]``
  102 + Minimum number of off counts to compute the significance.
  103 +
  104 +``(smooth_kernel = NONE) <GAUSSIAN|DISK|NONE> [string]``
90 105 Kernel to be used in smoothing the counts maps before computing
91 106 significances.
92 107  
93   -``(smoothpar = 0.05) [real]``
94   - Parameter for the smoothing kernel. This is either the sigma of the GAUSSIAN
95   - or the radius of the DISK.
  108 +``(smooth_rad = 0.05) [real]``
  109 + Radius of smoothing kernel in degrees. This is either the sigma of the
  110 + ``GAUSSIAN`` or the radius of the ``DISK``.
96 111  
97 112  
98 113 Time binning parameters
99 114 -----------------------
100 115  
101 116 ``tinterval [real]``
102   - interval in seconds for each time step.
  117 + Interval in seconds for each time step.
103 118  
104 119 ``tmin [time]``
105 120 Start time for interval determination (UTC string, JD, MJD or MET in seconds).
... ... @@ -120,7 +135,7 @@ Spatial binning parameters
120 135 --------------------------
121 136  
122 137 ``(usepnt = no) [boolean]``
123   - Use CTA pointing direction for cube centre instead of xref/yref parameters?
  138 + Use CTA pointing direction for sky map centre instead of xref/yref parameters?
124 139  
125 140 ``nxpix [integer]``
126 141 Number of cube bins in Right Ascension or Galactic longitude.
... ... @@ -129,25 +144,16 @@ Spatial binning parameters
129 144 Number of cube bins in Declination or Galactic latitude.
130 145  
131 146 ``binsz [real]``
132   - Cube bin size (in degrees/pixel).
133   -
134   -``coordsys <CEL|GAL> [string]``
135   - Coordinate system (CEL - celestial, GAL - galactic).
  147 + Sky map pixel size (in degrees/pixel).
136 148  
137 149 ``proj <AIT|AZP|CAR|GLS|MER|MOL|SFL|SIN|STG|TAN> [string]``
138 150 Projection method.
139 151  
140 152 ``xref [real]``
141   - Right Ascension / Galactic longitude of cube centre (J2000, in degrees).
  153 + Right Ascension / Galactic longitude of sky map centre (J2000, in degrees).
142 154  
143 155 ``yref [real]``
144   - Declination / Galactic latitude of cube centre (J2000, in degrees).
145   -
146   -``xsrc [real]``
147   - Right Ascension / Galactic longitude of source of interest (J2000, in degrees)
148   -
149   -``ysrc [real]``
150   - Declination / Galactic latitude of source of interest (J2000, in degrees)
  156 + Declination / Galactic latitude of sky map centre (J2000, in degrees).
151 157  
152 158  
153 159 Standard parameters
... ...
doc/source/users/reference_manual/index.rst
... ... @@ -17,7 +17,6 @@ ctools
17 17 .. toctree::
18 18 :maxdepth: 1
19 19  
20   - ctfindvar --- ToDo: Describe what tool is doing <ctfindvar>
21 20 ctbin --- Generates counts cube <ctbin>
22 21 ctbkgcube --- Generates background cube <ctbkgcube>
23 22 ctbutterfly --- Compute butterfly <ctbutterfly>
... ... @@ -25,6 +24,7 @@ ctools
25 24 ctedispcube --- Generates energy dispersion cube <ctedispcube>
26 25 cterror --- Calculates likelihood profile errors <cterror>
27 26 ctexpcube --- Generates exposure cube <ctexpcube>
  27 + ctfindvar --- Search for source variability <ctfindvar>
28 28 ctlike --- Performs maximum likelihood fitting <ctlike>
29 29 ctmapcube --- Generates a map cube <ctmapcube>
30 30 ctmodel --- Computes model counts cube <ctmodel>
... ...
examples/show_variability_evolution.py
1 1 #! /usr/bin/env python
2 2 # ==========================================================================
3   -# Shows butterfly diagram created with ctbutterfly
  3 +# Shows source variability evolution created with ctfindvar
4 4 #
5   -# Copyright (C) 2014-2017 Simon Bonnefoy
  5 +# Copyright (C) 2018 Simon Bonnefoy
6 6 #
7 7 # This program is free software: you can redistribute it and/or modify
8 8 # it under the terms of the GNU General Public License as published by
... ... @@ -43,6 +43,8 @@ def plot_variability_evolution(filename, plotfile, srcname):
43 43 Name of spectrum FITS file
44 44 plotfile : str
45 45 Plot file name
  46 + srcname : str
  47 + Source name
46 48 """
47 49 # Read variability file
48 50 fits = gammalib.GFits(filename)
... ... @@ -51,41 +53,45 @@ def plot_variability_evolution(filename, plotfile, srcname):
51 53 c_gti_stop = table['TSTOP']
52 54 c_sig = table[srcname]
53 55  
54   - #convert the c_sig object to list
55   - table_c_sig=[]
56   - table_c_gti=[]
57   -
  56 + # Convert the c_sig object to list
  57 + table_c_sig = []
  58 + table_c_gti = []
58 59 for k in c_sig:
59 60 table_c_sig.append(k)
60   - for j,k in zip(c_gti_start, c_gti_stop):
  61 + for j, k in zip(c_gti_start, c_gti_stop):
61 62 table_c_gti.append(0.5*(j+k))
62 63  
63   - #Initializing the plots
64   - axarr=[]
65   - f = plt.figure(figsize=(12,7))
66   - ax1 = f.add_subplot(121)
67   - ax2 = f.add_subplot(122)
  64 + # Initializing the plots
  65 + axarr = []
  66 + f = plt.figure(figsize=(12,7))
  67 + ax1 = f.add_subplot(121)
  68 + ax2 = f.add_subplot(122)
68 69 axarr.append(ax1)
69 70 axarr.append(ax2)
70 71  
  72 + # Create first plot
71 73 axarr[0].plot(table_c_gti, table_c_sig)
72 74 axarr[0].set_title('Significance evolution')
73 75 axarr[0].set_xlabel('MJD')
74 76 axarr[0].set_ylabel('Significance')
75 77  
76   - axarr[1].hist(table_c_sig, 20, range=[min(table_c_sig), max(table_c_sig)], histtype='stepfilled', facecolor='g', alpha=0.75)
  78 + # Create second plot
  79 + axarr[1].hist(table_c_sig, 20, range=[min(table_c_sig), max(table_c_sig)],
  80 + histtype='stepfilled', facecolor='g', alpha=0.75)
77 81 axarr[1].set_title('Significance distribution')
78 82 axarr[1].set_xlabel('Significance')
79 83 axarr[1].set_ylabel('Counts')
80 84  
81   - #IT SEEMS THAT THE LOG SCALE IS NOT WORKING WELL !!!!!!
82   - #axarr[1].gca().set_yscale('log')
83   -
  85 + # Optionally save result in file
84 86 if plotfile != "":
85 87 f.savefig(plotfile+".png")
86 88  
87 89 plt.show()
88 90  
  91 + # Return
  92 + return
  93 +
  94 +
89 95 # ========================== #
90 96 # Show variability evolution #
91 97 # ========================== #
... ...
src/ctfindvar/ctfindvar.cpp
... ... @@ -39,6 +39,7 @@
39 39 #endif
40 40  
41 41 /* __ Method name definitions ____________________________________________ */
  42 +#define G_INX2GTI "ctfindvar::inx2gti(int&)"
42 43 #define G_FILL_CUBE "ctfindvar::fill_cube(GCTAObservation*)"
43 44 #define G_FILL_ALPHA_VECTOR "ctfindvar::fill_alpha_vector(const int&, "\
44 45 "vector<double>&)"
... ... @@ -235,146 +236,16 @@ void ctfindvar::run(void)
235 236 // Write input observation container into logger
236 237 log_observations(NORMAL, m_obs, "Input observation");
237 238  
238   - // Loop over all unbinned CTA observations in the container
239   - #pragma omp parallel for
240   - for (int i = 0; i < m_obs.size(); ++i) {
241   -
242   - // Get pointer to observation
243   - GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[i]);
244   -
245   - // Fill the cube
246   - fill_cube(obs);
247   -
248   - // Dispose events to free memory
249   - obs->dispose_events();
250   -
251   - } // endfor: looped over observations
252   -
253   - // Smooth the maps if requested
254   - if ((*this)["smoothkrnl"].is_valid() &&
255   - (*this)["smoothkrnl"].string() != "NONE") {
256   - m_counts.smooth((*this)["smoothkrnl"].string(),
257   - (*this)["smoothpar"].real());
258   - }
259   -
260   - // Get number of maps
261   - const int nbins = m_counts.nmaps();
262   -
263   - // creating GSkyDir to get the position of the source and each pixels
264   -
265   - // Extract which pixel in the map each source is located in
266   - std::vector<int> srcInxPix;
267   - if (m_inmodel.size() > 0) {
268   - for (int mod = 0; mod < m_inmodel.size(); ++mod) {
269   -
270   - // Extract the model
271   - GModelSky* model = dynamic_cast<GModelSky*>(m_inmodel[mod]);
272   - if (model != NULL) {
273   - // Get the source position
274   - GModelSpatial* model_spatial = model->spatial();
275   - GSkyRegion* model_region = model_spatial->region();
276   - GSkyDir srcSkyDir =
277   - dynamic_cast<GSkyRegionCircle*>(model_region)->centre();
278   -
279   - // Store the map index of this source
280   - int src_map_index = m_counts.dir2inx(srcSkyDir);
281   - srcInxPix.push_back(src_map_index);
282   - } else {
283   - m_inmodel.remove(mod);
284   - mod--;
285   - }
286   - }
287   - }
288   - // ... otherwise only one source position is queried
289   - else {
290   - GSkyDir srcSkyDir;
291   - if ((*this)["coordsys"].string() == "CEL") {
292   - srcSkyDir.radec_deg((*this)["xsrc"].real(),(*this)["ysrc"].real());
293   - } else {
294   - srcSkyDir.lb_deg((*this)["xsrc"].real(),(*this)["ysrc"].real());
295   - }
296   -
297   - // Store the index of this source
298   - int src_map_index = m_counts.dir2inx(srcSkyDir);
299   - srcInxPix.push_back(src_map_index);
300   - }
301   -
302   - //preparing the histogram with significance evolution
303   - //for the source center and the highest sig-pix
304   - m_pixsigsrc = GNdarray(srcInxPix.size(), nbins);
305   - m_pixsigmax = GNdarray(nbins);
306   -
307   - //Prepare the final skymap with the max significance of each pixel
308   - double max_sig = 0;
309   -
310   - log_header1(NORMAL, "Looping Over Pixels");
311   -
312   - //looping over all the pixels in the cube
313   - #pragma omp parallel for
314   - for (int pix_number = 0; pix_number < m_counts.npix(); pix_number++) {
315   -
316   - //Preparing array to store significance evolution of
317   - //each pixel
318   - GNdarray pixSig(nbins);
319   - double total_counts=0;
320   -
321   - //Checking the integrated count of each pixel
322   - for (int k = 0; k < m_counts.nmaps(); k++) {
323   - total_counts += m_counts(pix_number, k);
324   - }
325   -
326   - //Getting the variability significance for the pixel
327   - get_variability_sig(pix_number,nbins, pixSig);
328   -
329   - //Getting the significance evolution for the source
330   - for (int src = 0; src < srcInxPix.size(); src++) {
  239 + // Create GTIs
  240 + init_gtis();
331 241  
332   - // Store the distribution if the source is located at this position
333   - if (srcInxPix[src] == pix_number) {
  242 + // Create counts cube
  243 + create_cube();
334 244  
335   - #pragma omp critical(ctfind_run)
336   - for (int i = 0; i < nbins; i++) {
337   - m_pixsigsrc(src,i) = pixSig(i);
338   - }
339   - }
340   - #ifdef G_DEBUG
341   - std::cout << "checking pixel number of the source of interest" << pix_number << std::endl;
342   - std::cout << "number of counts in pixel of the source of interest" << total_counts << std::endl;
343   - pixSigSrc=pixSig;
344   - std::cout << "checking pixel number " << pix_number << " with total number of counts of: " << total_counts << std::endl;
345   - std::cin.ignore();
346   - std::cout << "checking pixel number of the source of interest: " << pix_number << " - with total number of counts of: " << total_counts << std::endl;
347   - #endif
348   - }
349   - }
350   -
351   - // Getting the evolution for the pix with highest significance
352   - #pragma omp critical(ctfind_run)
353   - {
354   - if (max(pixSig) > max_sig) {
355   - // Update the pixel position with the maximum sigma
356   - max_sig = max(pixSig);
357   - m_max_sig_dir = m_counts.inx2dir(pix_number);
358   - m_pixsigmax = pixSig;
359   - }
360   - }
361   - //storing sig in skymap
362   - m_peaksigmap(pix_number) = max(pixSig);
363   -
364   - //If the significance is greater than sig threshold, the position
365   - //is stored in a model
366   - if (max(pixSig) > m_sig_threshold) {
367   - GSkyDir dir_pix = m_counts.inx2dir(pix_number);
368   - fill_model_sig_pos(dir_pix);
369   - }
370   -
371   - }
372   -
373   - log_header1(NORMAL, "Analysis finished");
374   - log_value(NORMAL, "Maximum sigma", max_sig);
375   - log_value(NORMAL, "Max pixel RA", m_max_sig_dir.ra_deg());
376   - log_value(NORMAL, "Max pixel DEC", m_max_sig_dir.dec_deg());
  245 + // Analyse cube
  246 + analyse_cube();
377 247  
  248 + // Return
378 249 return;
379 250 }
380 251  
... ... @@ -388,37 +259,52 @@ void ctfindvar::run(void)
388 259 ***************************************************************************/
389 260 void ctfindvar::save(void)
390 261 {
  262 + // Write header
391 263 log_header1(TERSE, "Saving results");
392 264  
393   - // Filenames
394   - std::string prefix((*this)["prefix"].string());
395   -
396 265 // Write counts cube
397 266 if ((*this)["outcube"].is_valid()) {
  267 +
  268 + // Get counts cube output filename
398 269 GFilename outcube = (*this)["outcube"].filename();
399   - log_value(TERSE, "Saving counts cube", outcube);
  270 +
  271 + // Save counts cube
400 272 m_counts.save(outcube, (*this)["clobber"].boolean());
  273 +
  274 + // Log saving
  275 + log_value(TERSE, "Saving counts cube", outcube);
401 276 }
402 277  
403 278 // Create a FITS file for storing the output
404   - m_outfile.clear();
  279 + GFits fits;
405 280  
406 281 // Write the most significant values for each pixel
407   - log_string(TERSE, "Appending maximum significances map to output file");
408   - m_peaksigmap.write(m_outfile, "PEAKSIGMAP");
  282 + m_peaksigmap.write(fits, "PEAKSIGMAP");
  283 +
  284 + // Write source histograms
  285 + write_source_histograms(fits);
409 286  
410   - // Write individual source distributions
411   - write_srchist();
  287 + // Get output map filename
  288 + GFilename outmap = (*this)["outmap"].filename();
412 289  
413   - // Write the output file
414   - GFilename outfilename = prefix + "signifmap.fits";
415   - log_value(TERSE, "Saving results to ", outfilename);
416   - m_outfile.saveto(outfilename, (*this)["clobber"].boolean());
  290 + // Save output map
  291 + fits.saveto(outmap, (*this)["clobber"].boolean());
417 292  
418   - //Save the model file with position with significance
  293 + // Log saving
  294 + log_value(TERSE, "Saving output map", outmap);
  295 +
  296 + // Save the model definition file
419 297 if (m_model_above_thr.size() > 2) {
420   - GFilename model_filename = prefix + "model_significant_directions.xml";
421   - m_model_above_thr.save(model_filename);
  298 +
  299 + // Get model output filename
  300 + GFilename outmodel = (*this)["outmodel"].filename();
  301 +
  302 + // Save model
  303 + m_model_above_thr.save(outmodel);
  304 +
  305 + // Log saving
  306 + log_value(TERSE, "Saving output model", outmodel);
  307 +
422 308 }
423 309  
424 310 // Return
... ... @@ -429,7 +315,7 @@ void ctfindvar::save(void)
429 315 /***********************************************************************//**
430 316 * @brief Get the map index associated with a given time
431 317 *
432   - * @param[in] time Time
  318 + * @param[in] time Time
433 319 * @return Index of map in counts cube
434 320 ***************************************************************************/
435 321 int ctfindvar::time2inx(const GTime& time)
... ... @@ -438,29 +324,35 @@ int ctfindvar::time2inx(const GTime&amp; time)
438 324 int map_index = -1;
439 325  
440 326 // Loop over all GTIs
441   - for (int i = 0; i < m_gti.size(); i++) {
442   - // Check if interval contains the time
  327 + for (int i = 0; i < m_gti.size(); ++i) {
  328 +
  329 + // If interval contains the time then break
443 330 if (m_gti[i].contains(time)) {
444 331 map_index = i;
445 332 break;
446 333 }
  334 +
447 335 }
448 336  
  337 + // Return map index
449 338 return map_index;
450 339 }
451 340  
452 341  
453 342 /***********************************************************************//**
454   - * @brief Return reference to significance cube object
  343 + * @brief Return Good Time Interval for index
  344 + *
  345 + * @param[in] index Index
  346 + * @return Good Time Interval
455 347 ***************************************************************************/
456 348 GGti ctfindvar::inx2gti(const int& index)
457 349 {
  350 + // Throw an exception if index is invalid
458 351 if ((index < 0) || (index >= m_gti.size())) {
459   - // Index is invalid, so throw an error
460   - throw GException::invalid_value("ctfindvar::inx2gti(const int&)",
461   - "'index' parameter out of range");
  352 + throw GException::out_of_range(G_INX2GTI, "Time index", index, m_gti.size());
462 353 }
463 354  
  355 + // Return Good Time Interval
464 356 return m_gti[index];
465 357 }
466 358  
... ... @@ -483,7 +375,6 @@ void ctfindvar::init_members(void)
483 375 m_max_sig_dir.clear();
484 376 m_minoff = 0.0;
485 377 m_sig_threshold = 0.0;
486   - m_outfile.clear();
487 378 m_peaksigmap.clear();
488 379 m_pixsigsrc.clear();
489 380 m_tstart.clear();
... ... @@ -511,7 +402,6 @@ void ctfindvar::copy_members(const ctfindvar&amp; app)
511 402 m_max_sig_dir = app.m_max_sig_dir;
512 403 m_minoff = app.m_minoff;
513 404 m_sig_threshold = app.m_sig_threshold;
514   - m_outfile = app.m_outfile;
515 405 m_peaksigmap = app.m_peaksigmap;
516 406 m_pixsigsrc = app.m_pixsigsrc;
517 407 m_tstart = app.m_tstart;
... ... @@ -537,39 +427,35 @@ void ctfindvar::free_members(void)
537 427  
538 428 /***********************************************************************//**
539 429 * @brief Get application parameters
540   - *
541   - * @todo Implement method
542 430 ***************************************************************************/
543 431 void ctfindvar::get_parameters(void)
544 432 {
545   - #ifdef _OPENMP
546   - int nthreads = (*this)["nthreads"].integer();
547   - if (nthreads > 0) {
548   - omp_set_num_threads(nthreads);
549   - }
550   - #endif
551   -
552 433 // Load the observations
553 434 setup_observations(m_obs, true, true, false);
554 435  
555   - // Get the rest of the parameters
556   - if (read_ahead()) {
557   - (*this)["prefix"].string();
558   -
559   - if ((*this)["inmodel"].is_valid()) {
560   - m_inmodel = GModels((*this)["inmodel"].filename());
561   - } else {
562   - // Get the desired source position from user
563   - (*this)["xsrc"].real();
564   - (*this)["ysrc"].real();
565   - }
  436 + // Either load model or query source position parameters
  437 + if ((*this)["inmodel"].is_valid()) {
  438 + m_inmodel = GModels((*this)["inmodel"].filename());
  439 + }
  440 + else {
  441 + (*this)["coordsys"].string();
  442 + (*this)["xsrc"].real();
  443 + (*this)["ysrc"].real();
566 444 }
567 445  
568   - // Create GTIs and counts cube
569   - init_gtis();
570   - init_cube();
  446 + // Query time limit parameters
  447 + (*this)["tinterval"].real();
  448 + if ((*this)["tmin"].is_valid()) {
  449 + (*this)["tmin"].time();
  450 + }
  451 + if ((*this)["tmax"].is_valid()) {
  452 + (*this)["tmax"].time();
  453 + }
  454 +
  455 + // Create map to query spatial parameters
  456 + create_map(m_obs);
571 457  
572   - // Get the energy limits
  458 + // Read energy limits
573 459 m_emin = GEnergy((*this)["emin"].real(), "TeV");
574 460 m_emax = GEnergy((*this)["emax"].real(), "TeV");
575 461  
... ... @@ -577,7 +463,28 @@ void ctfindvar::get_parameters(void)
577 463 m_minoff = (*this)["minoff"].real();
578 464  
579 465 // Get minimum significance to set a source as variable
580   - m_sig_threshold = (*this)["sig_thr"].real();
  466 + m_sig_threshold = (*this)["threshold"].real();
  467 +
  468 + // Query smoothing parameters
  469 + if ((*this)["smooth_kernel"].is_valid()) {
  470 + (*this)["smooth_kernel"].string();
  471 + (*this)["smooth_rad"].real();
  472 + }
  473 +
  474 + // If needed later, query output filenames now
  475 + if (read_ahead()) {
  476 + (*this)["outcube"].query();
  477 + (*this)["outmap"].query();
  478 + (*this)["outmodel"].query();
  479 + }
  480 +
  481 + // Set number of OpenMP threads
  482 + #ifdef _OPENMP
  483 + int nthreads = (*this)["nthreads"].integer();
  484 + if (nthreads > 0) {
  485 + omp_set_num_threads(nthreads);
  486 + }
  487 + #endif
581 488  
582 489 // Write parameters into logger
583 490 log_parameters(TERSE);
... ... @@ -588,117 +495,139 @@ void ctfindvar::get_parameters(void)
588 495  
589 496  
590 497 /***********************************************************************//**
591   - * @brief Initialize counts cube
  498 + * @brief Initialize Good Time Intervals
592 499 *
593   - * Sets up the required event cube and time interval storage objects
594   - * associated with this object
595   - ***************************************************************************/
596   -void ctfindvar::init_cube(void)
597   -{
598   - // Create the basic skymap
599   - m_counts = create_map(m_obs);
600   -
601   - // Resize to the appropriate number of time intervals
602   - m_counts.nmaps(m_gti.size());
603   -
604   - // Create the peaksigmap
605   - m_peaksigmap = m_counts.extract(1);
606   -}
607   -
608   -
609   -/***********************************************************************//**
610   - * @brief Initialize counts cube
  500 + * @exception GException::invalid_value
  501 + * Invalid start or stop time or insufficient time bins
611 502 *
612   - * Sets up the required event cube and time interval storage objects
613   - * associated with this object
  503 + * Initialise Good Time Intervals for the variability search.
614 504 ***************************************************************************/
615 505 void ctfindvar::init_gtis(void)
616 506 {
617   - double tinterval = (*this)["tinterval"].real();
618   - m_tstart = GTime("JD 999999999"); // large unreasonable Julian date
619   - m_tstop = GTime("JD 0"); // small unreasonable Julian date
  507 + // Log header
  508 + log_header1(NORMAL, "Initialise Good Time Intervals");
620 509  
621   - // Set the start time
622   - if ((*this)["tmin"].is_valid()) {
623   - log_string(NORMAL, "Setting start time from command line");
624   - m_tstart = (*this)["tmin"].time();
625   - } else {
626   - log_string(NORMAL, "Setting start time from observations");
627   - for (int o=0; o<m_obs.size(); o++) {
628   - GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[o]);
629   -
630   - // Check that the start time of the run is less than tstart
631   - if (obs->gti().tstart() < m_tstart) {
632   - m_tstart = obs->gti().tstart();
633   - }
634   - }
635   - }
  510 + // Clear time intervals
  511 + m_gti.clear();
636 512  
637   - // Set the stop time
638   - if ((*this)["tmax"].is_valid()) {
639   - log_string(NORMAL, "Setting stop time from command line");
640   - m_tstop = (*this)["tmax"].time();
641   - } else {
  513 + // Set start and stop times
  514 + m_tstart = get_tstart();
  515 + m_tstop = get_tstop();
642 516  
643   - log_string(NORMAL, "Setting stop time from observations");
644   - for (int o=0; o<m_obs.size(); o++) {
645   - GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[o]);
646   -
647   - // Check that the stop time of the run is larger than tstop
648   - if (obs->gti().tstop() > m_tstop) {
649   - m_tstop = obs->gti().tstop();
650   - }
651   - }
652   - }
  517 + // Query time interval
  518 + double tinterval = (*this)["tinterval"].real();
653 519  
654   - // Fill the number of good time intervals
  520 + // Compute time bins
655 521 double tstart_sec = m_tstart.secs();
656 522 double tstop_sec = m_tstop.secs();
657 523 int bins = (tstop_sec - tstart_sec) / tinterval + 0.5;
658 524  
659 525 // Log information
660   - log_value(NORMAL, "treference (mjd)", m_tstart.reference().mjdref());
661   - log_value(NORMAL, "tstart (sec)", tstart_sec);
662   - log_value(NORMAL, "tstop (sec)", tstop_sec);
663   - log_value(NORMAL, "Total time", tstop_sec-tstart_sec);
664   - log_value(NORMAL, "Time interval", tinterval);
665   - log_value(NORMAL, "nbins", bins);
666   -
667   - // Throw if tstart == tstop
  526 + log_value(NORMAL, "Reference time (mjd)", m_tstart.reference().mjdref());
  527 + log_value(NORMAL, "Start time (sec)", tstart_sec);
  528 + log_value(NORMAL, "Stop time (sec)", tstop_sec);
  529 + log_value(NORMAL, "Total time (sec)", tstop_sec-tstart_sec);
  530 + log_value(NORMAL, "Time interval (sec)", tinterval);
  531 + log_value(NORMAL, "Number of time bins", bins);
  532 +
  533 + // Throw exception if tstart == tstop
668 534 if (m_tstart == m_tstop) {
669 535 throw GException::invalid_value(G_INIT_GTIS,
670 536 "Start time is equal to stop time");
671   - } else if (bins < 2) {
  537 + }
  538 + else if (bins < 2) {
672 539 std::string msg = "Method requires at least two time bins (" +
673 540 gammalib::str(bins) + " bins found).";
674 541 throw GException::invalid_value(G_INIT_GTIS, msg);
675 542 }
676 543  
677   - for (int i=0; i<bins; i++) {
  544 + // Set up Good Time Intervals by appending all intervals that overlap
  545 + // with at least one of the observations
  546 + for (int i = 0; i < bins; ++i) {
678 547  
679   - // Get the next time interval
680   - GTime next_tstart(m_tstart + i*tinterval);
681   - GTime next_tstop(m_tstart + (i+1.0)*tinterval);
682   - GGti next_gti(next_tstart, next_tstop);
  548 + // Get time interval
  549 + GTime tstart(m_tstart + i*tinterval);
  550 + GTime tstop(m_tstart + (i+1.0)*tinterval);
  551 + GGti gti(tstart, tstop);
683 552  
684 553 // Make sure there's an observation that overlaps with this GTI
685   - for (int o=0; o<m_obs.size(); o++) {
686   - GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[o]);
687   -
688   - if (gti_overlap(obs->gti(), next_gti) > 0.0) {
689   - m_gti.push_back(next_gti);
690   - break;
691   - }
692   - }
  554 + for (int k = 0; k < m_obs.size(); ++k) {
  555 +
  556 + // Get CTA observation
  557 + GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[k]);
  558 +
  559 + // Continue only if observation is valid
  560 + if (obs != NULL) {
  561 +
  562 + // Add GTI is the time intervals overlap
  563 + if (gti_overlap(obs->gti(), gti) > 0.0) {
  564 + m_gti.push_back(gti);
  565 + break;
  566 + }
  567 +
  568 + } // endif: observation was valid
  569 +
  570 + } // endfor: looped over observations
  571 +
  572 + } // endfor: looped over Good Time Intervals
  573 +
  574 + // Return
  575 + return;
  576 +}
  577 +
  578 +
  579 +/***********************************************************************//**
  580 + * @brief Create counts cube
  581 + *
  582 + * Creates a counts cube comprising a sky map for each Good Time Interval.
  583 + ***************************************************************************/
  584 +void ctfindvar::create_cube(void)
  585 +{
  586 + // Log header
  587 + log_header1(NORMAL, "Create counts cube");
  588 +
  589 + // Create the basic skymap
  590 + m_counts = create_map(m_obs);
  591 +
  592 + // Resize to the appropriate number of time intervals
  593 + m_counts.nmaps(m_gti.size());
  594 +
  595 + // Create the peaksigmap
  596 + m_peaksigmap = m_counts.extract(1);
  597 +
  598 + // Loop over all unbinned CTA observations in the container
  599 + #pragma omp parallel for
  600 + for (int i = 0; i < m_obs.size(); ++i) {
  601 +
  602 + // Get pointer to observation
  603 + GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[i]);
  604 +
  605 + // Fill the cube
  606 + fill_cube(obs);
  607 +
  608 + // Dispose events to free memory
  609 + obs->dispose_events();
  610 +
  611 + } // endfor: looped over observations
  612 +
  613 + // Smooth the maps if requested
  614 + if ((*this)["smooth_kernel"].is_valid() &&
  615 + (*this)["smooth_kernel"].string() != "NONE") {
  616 + m_counts.smooth((*this)["smooth_kernel"].string(),
  617 + (*this)["smooth_rad"].real());
693 618 }
  619 +
  620 + // Return
  621 + return;
694 622 }
695 623  
696 624  
697 625 /***********************************************************************//**
698   - * @brief Fill the cube from the events in the map
  626 + * @brief Fill the cube from the events in an observation
699 627 *
700   - * Sets up the required event cube and time interval storage objects
701   - * associated with this object
  628 + * @param[in] obs Pointer to CTA observation.
  629 + *
  630 + * Fills the cube from the events in an observation.
702 631 ***************************************************************************/
703 632 void ctfindvar::fill_cube(GCTAObservation* obs)
704 633 {
... ... @@ -733,10 +662,6 @@ void ctfindvar::fill_cube(GCTAObservation* obs)
733 662 int num_outside_time = 0;
734 663 int num_in_map = 0;
735 664  
736   - // Specify whether we are cutting on energy or not
737   - bool cut_emin = (m_emin.TeV() > 0.0) ? true : false;
738   - bool cut_emax = (m_emax.TeV() > 0.0) ? true : false;
739   -
740 665 // Fill counts sky map
741 666 for (int i = 0; i < events->size(); ++i) {
742 667  
... ... @@ -744,16 +669,14 @@ void ctfindvar::fill_cube(GCTAObservation* obs)
744 669 const GCTAEventAtom* event = (*events)[i];
745 670  
746 671 // Check that the energy is valid
747   - if ((cut_emin && event->energy() < m_emin) ||
748   - (cut_emax && event->energy() > m_emax)) {
  672 + if ((event->energy() < m_emin) || (event->energy() > m_emax)) {
749 673 num_outside_ecut++;
750 674 continue;
751 675 }
752 676  
753   - // Determine event time
754   - GTime evnt_time = event->time();
755   - int time_indx = time2inx(evnt_time);
756   -
  677 + // Determine event time index
  678 + int time_indx = time2inx(event->time());
  679 +
757 680 // Check if event is in the GTIs
758 681 if ((time_indx > -1) && (time_indx < m_counts.nmaps())) {
759 682  
... ... @@ -791,10 +714,11 @@ void ctfindvar::fill_cube(GCTAObservation* obs)
791 714 // Increment number of maps
792 715 num_in_map++;
793 716  
794   - } else {
795   - // Event falls outside the specified time range
  717 + }
  718 +
  719 + // ... otherwise, event falls outside the specified time range
  720 + else {
796 721 num_outside_time++;
797   - continue;
798 722 }
799 723  
800 724 } // endfor: looped over all events
... ... @@ -818,242 +742,557 @@ void ctfindvar::fill_cube(GCTAObservation* obs)
818 742  
819 743  
820 744 /***********************************************************************//**
821   - * @brief Save the current sky direction to model if significance > thr
822   - *
823   - * @param[in] dir_pix pixel sky direction
824   - * @param[out] m_model_sig_above_thr model position with sig>thr
  745 + * @brief Analyse all pixels of counts cube
825 746 ***************************************************************************/
826   -void ctfindvar::fill_model_sig_pos(const GSkyDir& dir_pix)
  747 +void ctfindvar::analyse_cube(void)
827 748 {
828   - //Set spatial components
829   - GModelSpatialPointSource spatial(dir_pix);
  749 + // Log header
  750 + log_header1(NORMAL, "Analyse sky map pixels");
830 751  
831   - //Set fake spectral component (powerlaw with 1% of Crab)
832   - GModelSpectralPlaw spectral(5.7e-18, -2.48, GEnergy(0.3, "TeV"));
  752 + // Get number of GTIs
  753 + const int nbins = m_gti.size();
833 754  
834   - //Creat a sky model
835   - GModelSky model(spatial, spectral);
836   -
837   - //Extracting the coordinate to name the model
838   - double ra = dir_pix.ra_deg();
839   - double dec = dir_pix.dec_deg();
840   - std::string name = "Sky_direction_" + gammalib::str(ra, 4) +
841   - "_" + gammalib::str(dec, 4);
842   - model.name(name);
843   -
844   - //Add the model for this pixel to the output mode
845   - m_model_above_thr.append(model);
  755 + // Get relevant sky map pixels
  756 + std::vector<int> srcInxPix = get_pixels();
  757 +
  758 + // Preparing the histogram with significance evolution for the source
  759 + // center and the highest sig-pix
  760 + m_pixsigsrc = GNdarray(srcInxPix.size(), nbins);
  761 + m_pixsigmax = GNdarray(nbins);
  762 +
  763 + // Initialise maximum significance
  764 + double max_sig = 0;
  765 +
  766 + // looping over all the pixels in the cube
  767 + #pragma omp parallel for
  768 + for (int ipix = 0; ipix < m_counts.npix(); ++ipix) {
  769 +
  770 + // Compute total counts for pixel
  771 + double total_counts = 0.0;
  772 + for (int k = 0; k < nbins; ++k) {
  773 + total_counts += m_counts(ipix, k);
  774 + }
  775 +
  776 + // Getting the variability significance for the current pixel
  777 + GNdarray pixSig = get_variability_sig(ipix);
  778 +
  779 + // Getting the significance evolution for the source
  780 + for (int src = 0; src < srcInxPix.size(); ++src) {
  781 +
  782 + // Store the distribution if the source is located at this position
  783 + if (srcInxPix[src] == ipix) {
  784 +
  785 + #pragma omp critical(ctfindvar_analyse_cube)
  786 + for (int i = 0; i < nbins; i++) {
  787 + m_pixsigsrc(src,i) = pixSig(i);
  788 + }
  789 + }
  790 +
  791 + } // endfor: looped over sources
  792 +
  793 + // Make sure that this code is not executed in parallel
  794 + #pragma omp critical(ctfindvar_analyse_cube)
  795 + {
  796 + // Determine the pixel with the highest significance
  797 + if (max(pixSig) > max_sig) {
  798 + max_sig = max(pixSig);
  799 + m_max_sig_dir = m_counts.inx2dir(ipix);
  800 + m_pixsigmax = pixSig;
  801 + }
  802 +
  803 + // Storing sig in skymap
  804 + m_peaksigmap(ipix) = max(pixSig);
  805 +
  806 + // If the significance is greater than the significance threshold,
  807 + // the position is stored in a model
  808 + if (max(pixSig) > m_sig_threshold) {
  809 + GSkyDir dir_pix = m_counts.inx2dir(ipix);
  810 + m_model_above_thr.append(sky_model(dir_pix));
  811 + }
  812 + }
846 813  
  814 + } // endfor: looped over pixels
  815 +
  816 + // Log results
  817 + log_value(NORMAL, "Maximum significance", max_sig);
  818 + log_value(NORMAL, "Right Ascension of maximum", m_max_sig_dir.ra_deg());
  819 + log_value(NORMAL, "Declination of maximum", m_max_sig_dir.dec_deg());
  820 +
  821 + // Return
  822 + return;
847 823 }
  824 +
  825 +
848 826 /***********************************************************************//**
849   - * @brief Get the map index associated with a given time
  827 + * @brief Return pixel vector
  828 + *
  829 + * @return Vector of sky map pixels
850 830 *
851   - * @param[in] pix_number Spatial pixel
852   - * @param[out] alpha_vector Vector containing effective exposure
  831 + * Returns a vector of sky map pixels that corresponds to the source model
  832 + * positions.
853 833 ***************************************************************************/
854   -void ctfindvar::fill_alpha_vector(const int& pix_number,
855   - std::vector<double>& alpha_vector)
  834 +std::vector<int> ctfindvar::get_pixels(void)
856 835 {
857   - // Pixel position
858   - GSkyDir pix_dir = m_counts.inx2dir(pix_number);
  836 + // Initialise pixel array
  837 + std::vector<int> srcInxPix;
859 838  
860   - // Loop over all observations
861   - for (int i = 0; i < m_obs.size(); i++) {
862   - // Skip if observation does not overlap with this pixel position
863   - GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[i]);
864   - GCTARoi roi = obs->roi();
865   - GSkyDir roi_centre = roi.centre().dir();
  839 + // If there are input models then extract
  840 + if (m_inmodel.size() > 0) {
866 841  
867   - if (roi_centre.dist_deg(pix_dir) > roi.radius()) {
868   - continue;
869   - }
870   -
871   - // Convert sky direction to instrument direction
872   - GCTAInstDir instdir = obs->pointing().instdir(pix_dir);
  842 + // Loop over models
  843 + for (int i = 0; i < m_inmodel.size(); ++i) {
873 844  
874   - // Extract the good time intervals of the observation
875   - const GGti& obs_gti(obs->gti());
  845 + // Extract the model
  846 + GModelSky* model = dynamic_cast<GModelSky*>(m_inmodel[i]);
876 847  
877   - // Loop over all time bins
878   - for (int j = 0; j < m_gti.size(); j++) {
  848 + // If model is a sky model then extract the model centre. Note
  849 + // that this only works for a model that results a GSkyRegionCircle
  850 + // object as region.
  851 + if (model != NULL) {
879 852  
880   - // Make sure observation overlaps with this time interval
881   - double exposure = gti_overlap(m_gti[j], obs_gti);
882   - if (exposure > 0.0) {
  853 + // Get the source position
  854 + GModelSpatial* spatial = model->spatial();
  855 + GSkyRegion* region = spatial->region();
  856 + GSkyRegionCircle* circle = dynamic_cast<GSkyRegionCircle*>(region);
  857 +
  858 + // If region circle is valid then
  859 + if (circle != NULL) {
  860 + int ipix = m_counts.dir2inx(circle->centre());
  861 + srcInxPix.push_back(ipix);
  862 + continue;
  863 + }
883 864  
884   - // Get IRF response
885   - const GCTAResponseIrf* rsp = dynamic_cast<const GCTAResponseIrf*>
886   - (obs->response());
  865 + } // endif: model was a sky model
887 866  
888   - // Throw an exception if observation has no instrument response function
889   - if (rsp == NULL) {
890   - std::string msg =
891   - "No response information available for "+
892   - get_obs_header(obs)+" to compute IRF background. "
893   - "Please specify response information or use "
894   - "another background subtraction method.";
895   - throw GException::invalid_value(G_FILL_ALPHA_VECTOR, msg);
896   - }
  867 + // If we are still alive then remove the model since it is neither
  868 + // a sky model, nor does it have a region circle
  869 + m_inmodel.remove(i);
  870 + i--;
897 871  
898   - // Get IRF background template
899   - const GCTABackground* bkg = rsp->background();
  872 + } // endfor: looped over all models
900 873  
901   - // Throw an exception if observation has no IRF background template
902   - if (bkg == NULL) {
903   - std::string msg =
904   - "No IRF background template found in instrument "
905   - "response function for "+
906   - get_obs_header(obs)+". Please specify an instrument "
907   - "response function containing a background template.";
908   - throw GException::invalid_value(G_FILL_ALPHA_VECTOR, msg);
909   - }
  874 + } // endif: there were input models
  875 +
  876 + // ... otherwise query the source position
  877 + else {
910 878  
911   - // Multiply background rate with livetime and solid angle
912   - #pragma omp critical(ctfindvar_alpha_vector)
913   - alpha_vector[j] += exposure * bkg->rate_ebin(instdir, m_emin, m_emax);
914   - }
  879 + // Query source direction
  880 + GSkyDir dir;
  881 + if ((*this)["coordsys"].string() == "CEL") {
  882 + dir.radec_deg((*this)["xsrc"].real(),(*this)["ysrc"].real());
  883 + }
  884 + else {
  885 + dir.lb_deg((*this)["xsrc"].real(),(*this)["ysrc"].real());
915 886 }
916   - }
917 887  
918   - return;
919   -}
  888 + // Store the index of this source
  889 + int ipix = m_counts.dir2inx(dir);
  890 + srcInxPix.push_back(ipix);
  891 +
  892 + } // endelse: queried source position
  893 +
  894 + // Return pixel array
  895 + return srcInxPix;
  896 +}
  897 +
  898 +
  899 +/***********************************************************************//**
  900 + * @brief Return sky model for a given sky direction
  901 + *
  902 + * @param[in] dir Sky direction
  903 + * @return Sky model for a given sky direction
  904 + *
  905 + * Returns a point source sky model at the position of the current pixel
  906 + * with a power law spectral model corresponding to 1% of the Crab flux.
  907 + *
  908 + * The model name is built from the celestial coordinates of the source.
  909 + ***************************************************************************/
  910 +GModelSky ctfindvar::sky_model(const GSkyDir& dir) const
  911 +{
  912 + // Set point source spatial component from sky direction
  913 + GModelSpatialPointSource spatial(dir);
920 914  
  915 + // Set fake spectral component (powerlaw with 1% of Crab)
  916 + GModelSpectralPlaw spectral(5.7e-18, -2.48, GEnergy(0.3, "TeV"));
921 917  
  918 + // Create a sky model
  919 + GModelSky model(spatial, spectral);
  920 +
  921 + // Build model name from celestial coordinates
  922 + double ra = dir.ra_deg();
  923 + double dec = dir.dec_deg();
  924 + std::string name = gammalib::str(ra, 4);
  925 + if (dec < 0.0) {
  926 + name += "-";
  927 + }
  928 + else {
  929 + name += "+";
  930 + }
  931 + name += gammalib::str(std::abs(dec), 4);
  932 +
  933 + // Set model name
  934 + model.name(name);
  935 +
  936 + // Return model
  937 + return model;
  938 +}
922 939  
923 940  
924 941 /***********************************************************************//**
925   - * @brief Obtain the significance of variability in a given bin
  942 + * @brief Get the significance of variability for a given skymap pixel
926 943 *
927   - * @param[in] pix_number Pixel in the skymap
928   - * @param[in] nbins Number of bins in the skymap
929   - * @param[out] sig_histogram Histogram for storing returned significances
  944 + * @param[in] ipix Sky map pixel index.
  945 + * @return Histogram of significances.
930 946 *
931 947 * Significance is computed according to Li & Ma equation 17:
932 948 * see: https://doi.org/10.1086/161295
933 949 * Some modification is made in order to handle the case where Non = 0,
934 950 * but Noff != 0.
935 951 ***************************************************************************/
936   -void ctfindvar::get_variability_sig(const int& pix_number,
937   - const int& nbins,
938   - GNdarray& sig_histogram)
  952 +GNdarray ctfindvar::get_variability_sig(const int& ipix)
939 953 {
940   - std::vector<bool> accepted_bin_bckg_vector(nbins, true);
941   - bool background_validated = false;
942   - double non, noff;
943   - double alpha, sig;
  954 + // Initialise result
  955 + GNdarray sig_histogram(m_gti.size());
  956 +
  957 + // Initialise vectors
  958 + std::vector<bool> accepted_bin_bckg_vector(m_gti.size(), true);
944 959  
945   - // Fill the alpha values for each vector
946   - std::vector<double> alpha_vector(nbins, 0.0);
947   - fill_alpha_vector(pix_number, alpha_vector);
  960 + // Get alpha values for specified pixel
  961 + std::vector<double> alphas = get_alphas(ipix);
948 962  
949   - // Loop over pixels until all background pixls are removed
  963 + // Loop over pixels until all background pixels are removed
  964 + bool background_validated = false;
950 965 while (background_validated == false) {
  966 +
  967 + // Signal that background was validated
951 968 background_validated = true;
952 969  
953 970 // Loop over all the GTIs of the pixel
954   - for (int i = 0; i < nbins; i++) {
  971 + for (int i = 0; i < m_gti.size(); ++i) {
955 972  
956   - // The GTI is discared from bckg calculation and not checked again.
  973 + // The GTI is discared from background calculation and not
  974 + // checked again
957 975 if (!accepted_bin_bckg_vector[i]) {
958 976 continue;
959   - }
960   - // Check if bin fails minoff check or no observations overlap it
961   - else if (m_counts(pix_number, i) < m_minoff || alpha_vector[i] == 0) {
  977 + }
  978 +
  979 + // ... otherwise check if bin fails minoff check or no observations
  980 + // overlap it
  981 + else if (m_counts(ipix, i) < m_minoff || alphas[i] == 0) {
962 982 accepted_bin_bckg_vector[i] = false;
963 983 continue;
964 984 }
965 985  
966   - noff = 0.0;
967   - alpha = 0.0;
968   - // Gor one GTI selected (i), looping over all the others (j).
969   - for (int j = 0; j < nbins; j++) {
  986 + // ... otherwise
  987 + double noff = 0.0;
  988 + double alpha = 0.0;
  989 +
  990 + // Got one GTI selected (i), looping over all the others (j).
  991 + for (int j = 0; j < m_gti.size(); ++j) {
970 992 if (j != i && accepted_bin_bckg_vector[j] == 1) {
971   - noff += m_counts(pix_number, j);
972   - alpha += alpha_vector[j];
  993 + noff += m_counts(ipix, j);
  994 + alpha += alphas[j];
973 995 }
974 996 }
975 997  
976 998 // The background is averaged on the number of bins -1
977   - non = m_counts(pix_number, i);
978   - alpha = alpha_vector[i]/alpha;
  999 + double non = m_counts(ipix, i);
  1000 + alpha = alphas[i]/alpha;
979 1001  
980 1002 // Compute sensitivity in Gaussian sigma using Li & Ma
981 1003 double alpha1 = alpha + 1.0;
982 1004 double ntotal = non+noff;
983 1005 double arg1 = non/ntotal;
984 1006 double arg2 = noff/ntotal;
985   - if (noff == 0.0) {
986   - sig = 0.0;
987   - } else if (non > 0.0) {
988   - double term1 = non * std::log((alpha1/alpha)*arg1);
989   - double term2 = noff * std::log(alpha1*arg2);
990   - sig = std::sqrt(2.0 * (term1 + term2));
991   - } else {
992   - sig = std::sqrt(2.0 * noff * std::log(alpha1));
993   - }
994   -
995   - // Specify the sign of the significance
996   - sig *= (non < alpha*noff) ? -1.0 : 1.0;
997   -
998   - if (alpha == 0.0) {
999   - sig = 0.0;
  1007 + double sig = 0.0;
  1008 + if (alpha != 0.0) {
  1009 + if (noff == 0.0) {
  1010 + sig = 0.0;
  1011 + }
  1012 + else if (non > 0.0) {
  1013 + double term1 = non * std::log((alpha1/alpha)*arg1);
  1014 + double term2 = noff * std::log(alpha1*arg2);
  1015 + sig = std::sqrt(2.0 * (term1 + term2));
  1016 + }
  1017 + else {
  1018 + sig = std::sqrt(2.0 * noff * std::log(alpha1));
  1019 + }
  1020 + sig *= (non < alpha*noff) ? -1.0 : 1.0;
1000 1021 }
1001 1022  
1002 1023 // Update the significance
1003 1024 sig_histogram(i) = sig;
1004 1025  
1005   - // If bin is significant, it's removed from bckg and we loop again.
  1026 + // If bin is significant, it's removed from the background and
  1027 + // we loop again
1006 1028 if (sig > m_sig_threshold) {
1007 1029 accepted_bin_bckg_vector[i] = false;
1008 1030 background_validated = false;
1009 1031 }
1010   - }
1011 1032  
1012   - }
  1033 + } // endfor: looped over all GTIs
  1034 +
  1035 + } // endwhile: iterate until converged
  1036 +
  1037 + // Return significance histogram
  1038 + return sig_histogram;
1013 1039 }
1014 1040  
1015 1041  
1016 1042 /***********************************************************************//**
  1043 + * @brief Get alpha vector
  1044 + *
  1045 + * @param[in] ipix Sky map pixel index
  1046 + * @return Vector containing effective exposure
  1047 + ***************************************************************************/
  1048 +std::vector<double> ctfindvar::get_alphas(const int& ipix) const
  1049 +{
  1050 + // Initialise alpha vector
  1051 + std::vector<double> alphas(m_gti.size(), 0.0);
  1052 +
  1053 + // Convert pixel index into sky direction
  1054 + GSkyDir pix_dir = m_counts.inx2dir(ipix);
  1055 +
  1056 + // Loop over all observations
  1057 + for (int i = 0; i < m_obs.size(); ++i) {
  1058 +
  1059 + // Get CTA observation
  1060 + const GCTAObservation* obs = dynamic_cast<const GCTAObservation*>(m_obs[i]);
  1061 +
  1062 + // Fall through if observation is not valid
  1063 + if (obs == NULL) {
  1064 + continue;
  1065 + }
  1066 +
  1067 + // Get RoI. Fall through if observation does not overlap with this
  1068 + // pixel position
  1069 + GCTARoi roi = obs->roi();
  1070 + GSkyDir roi_centre = roi.centre().dir();
  1071 + if (roi_centre.dist_deg(pix_dir) > roi.radius()) {
  1072 + continue;
  1073 + }
  1074 +
  1075 + // Convert sky direction to instrument direction
  1076 + GCTAInstDir instdir = obs->pointing().instdir(pix_dir);
  1077 +
  1078 + // Extract the good time intervals of the observation
  1079 + const GGti& obs_gti(obs->gti());
  1080 +
  1081 + // Loop over all time bins
  1082 + for (int j = 0; j < m_gti.size(); ++j) {
  1083 +
  1084 + // Make sure observation overlaps with this time interval
  1085 + double exposure = gti_overlap(m_gti[j], obs_gti);
  1086 + if (exposure > 0.0) {
  1087 +
  1088 + // Get IRF response
  1089 + const GCTAResponseIrf* rsp =
  1090 + dynamic_cast<const GCTAResponseIrf*>(obs->response());
  1091 +
  1092 + // Throw an exception if observation has no instrument response function
  1093 + if (rsp == NULL) {
  1094 + std::string msg =
  1095 + "No response information available for "+
  1096 + get_obs_header(obs)+" to compute IRF background. "
  1097 + "Please specify response information or use "
  1098 + "another background subtraction method.";
  1099 + throw GException::invalid_value(G_FILL_ALPHA_VECTOR, msg);
  1100 + }
  1101 +
  1102 + // Get IRF background template
  1103 + const GCTABackground* bkg = rsp->background();
  1104 +
  1105 + // Throw an exception if observation has no IRF background template
  1106 + if (bkg == NULL) {
  1107 + std::string msg =
  1108 + "No IRF background template found in instrument "
  1109 + "response function for "+
  1110 + get_obs_header(obs)+". Please specify an instrument "
  1111 + "response function containing a background template.";
  1112 + throw GException::invalid_value(G_FILL_ALPHA_VECTOR, msg);
  1113 + }
  1114 +
  1115 + // Add up background rate
  1116 + alphas[j] += exposure * bkg->rate_ebin(instdir, m_emin, m_emax);
  1117 +
  1118 + } // endif: exposure was positive
  1119 +
  1120 + } // endfor: looped over all time bins
  1121 +
  1122 + } // endfor: looped over all observations
  1123 +
  1124 + // Return alphas
  1125 + return alphas;
  1126 +}
  1127 +
  1128 +
  1129 +/***********************************************************************//**
1017 1130 * @brief Returns number of seconds that two GTIs overlap
1018 1131 *
1019 1132 * @param[in] gti1 First GTI
1020 1133 * @param[in] gti2 Second GTI
1021 1134 * @return Number of seconds that @p gti1 and @p gti2 overlap
1022 1135 ***************************************************************************/
1023   -double ctfindvar::gti_overlap(const GGti& gti1, const GGti& gti2)
  1136 +double ctfindvar::gti_overlap(const GGti& gti1, const GGti& gti2) const
1024 1137 {
  1138 + // Initialise no overlap
1025 1139 double overlap = 0.0;
  1140 +
  1141 + // Set first and second GTI so that gti_1st starts before gti_2nd
1026 1142 const GGti& gti_1st = (gti1.tstart() <= gti2.tstart()) ? gti1 : gti2;
1027   - const GGti& gti_2nd = (gti1.tstart() > gti2.tstart()) ? gti1 : gti2;
  1143 + const GGti& gti_2nd = (gti1.tstart() > gti2.tstart()) ? gti1 : gti2;
1028 1144  
1029   - // gti1 starts earlier
  1145 + // Compute overlap
1030 1146 if (gti_1st.tstop() > gti_2nd.tstart()) {
1031 1147 if (gti_1st.tstop() <= gti_2nd.tstop()) {
1032 1148 overlap = gti_1st.tstop() - gti_2nd.tstart();
1033   - } else if (gti_1st.tstop() > gti_2nd.tstop()) {
  1149 + }
  1150 + else if (gti_1st.tstop() > gti_2nd.tstop()) {
1034 1151 overlap = gti_2nd.tstop() - gti_2nd.tstart();
1035 1152 }
1036 1153 }
1037 1154  
  1155 + // Return overlap
1038 1156 return overlap;
1039 1157 }
1040 1158  
1041 1159  
1042 1160 /***********************************************************************//**
1043   - * @brief Write individual histograms in CSV format
  1161 + * @brief Get start time
  1162 + *
  1163 + * @return Start time
  1164 + ***************************************************************************/
  1165 +GTime ctfindvar::get_tstart(void)
  1166 +{
  1167 + // Initialise start time with large unreasonable Julian date
  1168 + GTime tstart("JD 999999999");
  1169 +
  1170 + // If tmin parameter is valid then set the start time
  1171 + if ((*this)["tmin"].is_valid()) {
  1172 +
  1173 + // Query tmin parameter
  1174 + tstart = (*this)["tmin"].time();
  1175 +
  1176 + // Log action
  1177 + log_string(NORMAL, "Setting start time \"tmin\" parameter");
  1178 +
  1179 + } // endif: tmin parameter was valid
  1180 +
  1181 + // ... otherwise set start time from observations
  1182 + else {
  1183 +
  1184 + // Loop over all observations
  1185 + for (int k = 0; k < m_obs.size(); ++k) {
  1186 +
  1187 + // Get CTA observation
  1188 + GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[k]);
  1189 +
  1190 + // Continue only if observation is valid
  1191 + if (obs != NULL) {
  1192 +
  1193 + // Update start time if the start time of the observation is
  1194 + // smaller than the current start time
  1195 + if (obs->gti().tstart() < tstart) {
  1196 + tstart = obs->gti().tstart();
  1197 + }
  1198 +
  1199 + } // endif: observation was valid
  1200 +
  1201 + } // endfor: looped over observations
  1202 +
  1203 + // Log action
  1204 + log_string(NORMAL, "Setting start time from observations");
  1205 +
  1206 + } // endelse: no valid time specified
  1207 +
  1208 + // Return start time
  1209 + return tstart;
  1210 +}
  1211 +
  1212 +
  1213 +/***********************************************************************//**
  1214 + * @brief Get stop time
  1215 + *
  1216 + * @return Stop time
1044 1217 ***************************************************************************/
1045   -void ctfindvar::write_srchist(void)
  1218 +GTime ctfindvar::get_tstop(void)
  1219 +{
  1220 + // Initialise stop time with small unreasonable Julian date
  1221 + GTime tstop("JD 0");
  1222 +
  1223 + // If tmax parameter is valid then set the stop time
  1224 + if ((*this)["tmax"].is_valid()) {
  1225 +
  1226 + // Query tmax parameter
  1227 + tstop = (*this)["tmax"].time();
  1228 +
  1229 + // Log action
  1230 + log_string(NORMAL, "Setting stop time \"tmax\" parameter");
  1231 +
  1232 + } // endif: tmax parameter was valid
  1233 +
  1234 + // ... otherwise set stop time from observations
  1235 + else {
  1236 +
  1237 + // Loop over all observations
  1238 + for (int k = 0; k < m_obs.size(); ++k) {
  1239 +
  1240 + // Get CTA observation
  1241 + GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[k]);
  1242 +
  1243 + // Continue only if observation is valid
  1244 + if (obs != NULL) {
  1245 +
  1246 + // Update stop time if the stop time of the observation is
  1247 + // larger than the current stop time
  1248 + if (obs->gti().tstop() > tstop) {
  1249 + tstop = obs->gti().tstop();
  1250 + }
  1251 +
  1252 + } // endif: observation was valid
  1253 +
  1254 + } // endfor: looped over observations
  1255 +
  1256 + // Log action
  1257 + log_string(NORMAL, "Setting stop time from observations");
  1258 +
  1259 + } // endelse: no valid time specified
  1260 +
  1261 + // Return stop time
  1262 + return tstop;
  1263 +}
  1264 +
  1265 +
  1266 +/***********************************************************************//**
  1267 + * @brief Write source histograms in FITS format
  1268 + *
  1269 + * @param[in,out] fits FITS file
  1270 + *
  1271 + * This method creates two FITS binary table extensions and appends them to
  1272 + * the FITS file.
  1273 + *
  1274 + * The first extension is named "SOURCE SIGNIFICANCE" and contains the
  1275 + * significance of the variability as function of time bin for the pixel
  1276 + * with the maximum significance and for each source.
  1277 + *
  1278 + * The second extension is named "SOURCE POSITION" and contains the Right
  1279 + * Ascension and Declination for the maximum significance pixel and for each
  1280 + * source.
  1281 + ***************************************************************************/
  1282 +void ctfindvar::write_source_histograms(GFits& fits)
1046 1283 {
1047 1284 // Setup data storage for time and source significances
1048 1285 double tinterval = (*this)["tinterval"].real();
1049   - int nentries = (m_tstop-m_tstart) / tinterval + 0.5;
1050   - int nsources = m_pixsigsrc.shape()[0];
1051   - GNdarray time_info(2, nentries);
1052   - GNdarray max_pixel_info(nentries);
1053   - GNdarray src_info(nsources, nentries);
1054   -
1055   - // Store the full set of information
1056   - for (int i=0; i<nentries; i++) {
  1286 + int nbins = (m_tstop-m_tstart) / tinterval + 0.5;
  1287 + int nsrc = m_pixsigsrc.shape()[0];
  1288 + GNdarray time_info(2, nbins);
  1289 + GNdarray max_pixel_info(nbins);
  1290 + GNdarray src_info(nsrc, nbins);
  1291 +
  1292 + // Loop over time bins
  1293 + for (int i = 0; i < nbins; ++i) {
  1294 +
  1295 + // Set time bin mid point
1057 1296 GTime tstart_bin(m_tstart + i * tinterval);
1058 1297 GTime tstop_bin(m_tstart + (i+1) * tinterval);
1059 1298 GTime midpoint((tstop_bin.secs()+tstart_bin.secs())*0.5);
... ... @@ -1066,111 +1305,125 @@ void ctfindvar::write_srchist(void)
1066 1305 int index = time2inx(midpoint);
1067 1306  
1068 1307 // Loop over every source
1069   - for (int src=0; src<nsources; src++) {
1070   - if (index >= 0) {
1071   - src_info(src,i) = m_pixsigsrc(src,index);
1072   - } else {
1073   - src_info(src,i) = 0.0;
1074   - }
  1308 + for (int k = 0; k < nsrc; ++k) {
  1309 + src_info(k,i) = (index >= 0) ? m_pixsigsrc(k,index) : 0.0;
1075 1310 }
1076 1311  
1077   - // Fill max pixel information
1078   - if (index >= 0) {
1079   - max_pixel_info(i) = m_pixsigmax(index);
1080   - }
  1312 + // Fill maximum pixel information
  1313 + max_pixel_info(i) = (index >= 0) ? m_pixsigmax(index) : 0.0;
  1314 +
  1315 + } // endfor: looped over time bins
  1316 +
  1317 + // Create binary tables
  1318 + GFitsBinTable table_signif(nbins);
  1319 + GFitsBinTable table_pos(nsrc+1);
  1320 + table_signif.extname("SOURCE SIGNIFICANCE");
  1321 + table_pos.extname("SOURCE POSITION");
  1322 +
  1323 + // Create columns for time bins
  1324 + GFitsTableDoubleCol time_start("TSTART", nbins);
  1325 + GFitsTableDoubleCol time_stop("TSTOP", nbins);
  1326 + GFitsTableDoubleCol time_sigma("MAXSIGPIXEL", nbins);
  1327 + time_start.unit("MJD");
  1328 + time_stop.unit("MJD");
  1329 + time_sigma.unit("sigma");
  1330 +
  1331 + // Create columns for source position
  1332 + GFitsTableStringCol pos_name("SOURCE", nsrc+1, 20);
  1333 + GFitsTableDoubleCol pos_ra("RA", nsrc+1);
  1334 + GFitsTableDoubleCol pos_dec("DEC", nsrc+1);
  1335 + pos_ra.unit("deg");
  1336 + pos_dec.unit("deg");
  1337 +
  1338 + // Fill time bin columns
  1339 + for (int i = 0; i < nbins; ++i) {
  1340 + time_start(i) = time_info(0,i);
  1341 + time_stop(i) = time_info(1,i);
  1342 + time_sigma(i) = max_pixel_info(i);
1081 1343 }
1082 1344  
1083   - // Call the appropriate method for writing the data
1084   - write_srchist_fits(time_info, max_pixel_info, src_info);
1085   -}
  1345 + // Fill source columns
  1346 + pos_name(0) = "MAXSIGPIXEL";
  1347 + pos_ra(0) = m_max_sig_dir.ra_deg();
  1348 + pos_dec(0) = m_max_sig_dir.dec_deg();
1086 1349  
1087   -
1088   -/***********************************************************************//**
1089   - * @brief Write individual histograms in FITS format
1090   - *
1091   - * @param[in] time_info Start and stop time in MJD for each bin
1092   - * @param[in] src_info Significance distribution for each source
1093   -
1094   - * This method stores the extracted significance for each source at each time
1095   - * interval between tstart and tstop. Each time bin contains information on
1096   - * the start and stop time of that bin (in MJD) and the extracted variability
1097   - * significance of the source in that time bin.
1098   - ***************************************************************************/
1099   -void ctfindvar::write_srchist_fits(const GNdarray& time_info,
1100   - const GNdarray& max_pixel_info,
1101   - const GNdarray& src_info)
1102   -{
1103   - log_string(NORMAL, "Storing histograms in FITS format");
1104   -
1105   - // Create the table
1106   - int nrows = time_info.shape()[1];
1107   - GFitsBinTable table_signif(nrows);
1108   - GFitsBinTable table_dir(2);
1109   - table_signif.extname("SRCSIGNIF");
1110   - table_dir.extname("SRCPOSITION");
1111   -
1112   - // Append the start/stop time and max pixel columns
1113   - GFitsTableDoubleCol start_mjd("TSTART", nrows);
1114   - GFitsTableDoubleCol stop_mjd("TSTOP", nrows);
1115   - GFitsTableDoubleCol max_pix("MAXSIGPIXEL", nrows);
1116   - for (int i=0; i<nrows; i++) {
1117   - start_mjd(i) = time_info(0,i);
1118   - stop_mjd(i) = time_info(1,i);
1119   - max_pix(i) = max_pixel_info(i);
1120   - }
1121   - table_signif.append(start_mjd);
1122   - table_signif.append(stop_mjd);
1123   - table_signif.append(max_pix);
1124   -
1125   - // Store the position of the peak pixel
1126   - GFitsTableDoubleCol max_pix_dir("MAXSIGPIXEL",2);
1127   - max_pix_dir(0) = m_max_sig_dir.ra_deg();
1128   - max_pix_dir(1) = m_max_sig_dir.dec_deg();
1129   - table_dir.append(max_pix_dir);
  1350 + // Append columns to tables
  1351 + table_signif.append(time_start);
  1352 + table_signif.append(time_stop);
  1353 + table_signif.append(time_sigma);
1130 1354  
1131 1355 // Append the distribution for each source
1132   - int src_count = src_info.shape()[0];
1133   - for (int src=0; src<src_count; src++) {
1134   -
  1356 + for (int k = 0; k < nsrc; ++k) {
  1357 +
  1358 + // Initialise source name and direction
1135 1359 std::string src_name("SOURCE");
1136 1360 GSkyDir src_dir;
1137 1361  
1138 1362 // Get the name from the input model file
1139   - if (m_inmodel.size() > 0) {
1140   - src_name = m_inmodel[src]->name();
1141   -
1142   - // Get source position
1143   - GModelSky* model = dynamic_cast<GModelSky*>(m_inmodel[src]);
1144   - GModelSpatial* model_spatial = model->spatial();
1145   - GSkyRegion* model_region = model_spatial->region();
1146   - src_dir = dynamic_cast<GSkyRegionCircle*>(model_region)->centre();
1147   - }
1148   - // ... otherwise set the position from xsrc,ysrc
  1363 + if (m_inmodel.size() > k) {
  1364 +
  1365 + // Extract the model
  1366 + GModelSky* model = dynamic_cast<GModelSky*>(m_inmodel[k]);
  1367 +
  1368 + // If model is a sky model then extract the model centre. Note
  1369 + // that this only works for a model that results a GSkyRegionCircle
  1370 + // object as region.
  1371 + if (model != NULL) {
  1372 +
  1373 + // Get the source position
  1374 + GModelSpatial* spatial = model->spatial();
  1375 + GSkyRegion* region = spatial->region();
  1376 + GSkyRegionCircle* circle = dynamic_cast<GSkyRegionCircle*>(region);
  1377 +
  1378 + // If region circle is valid then extract source name and
  1379 + // sky direction
  1380 + if (circle != NULL) {
  1381 + src_name = m_inmodel[k]->name();
  1382 + src_dir = circle->centre();
  1383 + }
  1384 +
  1385 + } // endif: model was a sky model
  1386 +
  1387 + } // endif: there were enough models in the input model
  1388 +
  1389 + // ... otherwise set the position from the xsrc and ysrc parameters
1149 1390 else {
1150 1391 if ((*this)["coordsys"].string() == "CEL") {
1151   - src_dir.radec_deg((*this)["xsrc"].real(), (*this)["yref"].real());
1152   - } else {
1153   - src_dir.lb_deg((*this)["xsrc"].real(), (*this)["yref"].real());
  1392 + src_dir.radec_deg((*this)["xsrc"].real(), (*this)["ysrc"].real());
  1393 + }
  1394 + else {
  1395 + src_dir.lb_deg((*this)["xsrc"].real(), (*this)["ysrc"].real());
1154 1396 }
1155 1397 }
1156   -
1157   - // Create and fill a new column
1158   - GFitsTableDoubleCol src_sig_col(src_name, nrows);
1159   - for (int i=0; i<nrows; i++) {
1160   - src_sig_col(i) = src_info(src, i);
  1398 +
  1399 + // Create new time column for source
  1400 + GFitsTableDoubleCol column(src_name, nbins);
  1401 + column.unit("sigma");
  1402 +
  1403 + // Fill column
  1404 + for (int i = 0; i < nbins; ++i) {
  1405 + column(i) = src_info(k, i);
1161 1406 }
1162   - table_signif.append(src_sig_col);
1163 1407  
1164   - // Store the positions of the source
1165   - GFitsTableDoubleCol src_dir_col(src_name,2);
1166   - src_dir_col(0) = src_dir.ra_deg();
1167   - src_dir_col(1) = src_dir.dec_deg();
1168   - table_dir.append(src_dir_col);
1169   - }
  1408 + // Append column to time table
  1409 + table_signif.append(column);
  1410 +
  1411 + // Store source infirmation
  1412 + pos_name(k+1) = src_name;
  1413 + pos_ra(k+1) = src_dir.ra_deg();
  1414 + pos_dec(k+1) = src_dir.dec_deg();
  1415 +
  1416 + } // endfor: looped over sources
1170 1417  
1171   - // Store information regarding the source positions
  1418 + // Append columns to tables
  1419 + table_pos.append(pos_name);
  1420 + table_pos.append(pos_ra);
  1421 + table_pos.append(pos_dec);
1172 1422  
1173   - // Write the tables to a file
1174   - m_outfile.append(table_signif);
1175   - m_outfile.append(table_dir);
  1423 + // Append tables to FITS file
  1424 + fits.append(table_signif);
  1425 + fits.append(table_pos);
  1426 +
  1427 + // Return
  1428 + return;
1176 1429 }
... ...
src/ctfindvar/ctfindvar.hpp
... ... @@ -68,27 +68,22 @@ public:
68 68  
69 69 protected:
70 70 // Protected methods
71   - void init_members(void);
72   - void copy_members(const ctfindvar& app);
73   - void free_members(void);
74   - void get_parameters(void);
75   - void init_cube(void);
76   - void init_gtis(void);
77   - void fill_cube(GCTAObservation* obs);
78   - void fill_model_sig_pos(const GSkyDir& dir_pix);
79   - void fill_alpha_vector(const int& pix_number,
80   - std::vector<double>& alpha_vector);
81   - void get_variability_sig(const int& pix_number,
82   - const int& nbins,
83   - GNdarray& sig_histogram);
84   - double gti_overlap(const GGti& gti1,
85   - const GGti& gti2);
86   -
87   - // Writing methods for individual source histograms
88   - void write_srchist(void);
89   - void write_srchist_fits(const GNdarray& time_info,
90   - const GNdarray& max_pixel_info,
91   - const GNdarray& src_info);
  71 + void init_members(void);
  72 + void copy_members(const ctfindvar& app);
  73 + void free_members(void);
  74 + void get_parameters(void);
  75 + void init_gtis(void);
  76 + void create_cube(void);
  77 + void fill_cube(GCTAObservation* obs);
  78 + void analyse_cube(void);
  79 + std::vector<int> get_pixels(void);
  80 + GNdarray get_variability_sig(const int& ipix);
  81 + std::vector<double> get_alphas(const int& ipix) const;
  82 + double gti_overlap(const GGti& gti1, const GGti& gti2) const;
  83 + GTime get_tstart(void);
  84 + GTime get_tstop(void);
  85 + GModelSky sky_model(const GSkyDir& dir) const;
  86 + void write_source_histograms(GFits& fits);
92 87  
93 88 // Protected members
94 89 GSkyMap m_counts; //!< Counts for each time interval
... ... @@ -97,7 +92,6 @@ protected:
97 92 GSkyDir m_max_sig_dir; //!< Sky direction associated with maximum significance
98 93 double m_minoff; //!< Minimum counts for use in significance calculation
99 94 double m_sig_threshold; //!< Minimum significance required to set source as variable
100   - GFits m_outfile; //!< FITS object to store output
101 95 GSkyMap m_peaksigmap; //!< Skymap holding the maximum significance
102 96 GNdarray m_pixsigsrc; //!< Store distributions of the source significances
103 97 GNdarray m_pixsigmax; //!< Store distribution for pixel with max significance
... ...
src/ctfindvar/ctfindvar.par
... ... @@ -16,22 +16,26 @@
16 16 #
17 17 # General parameters
18 18 #===================
19   -inobs, f, a, events.fits,,, "Input event list, counts cube or observation definition XML file"
  19 +inobs, f, a, events.fits,,, "Input event list or observation definition XML file"
20 20 inmodel, f, h, NONE,,, "Input model definition file for extracting source positions"
21   -outcube, f, h, NONE,,, "Filename for counts cube"
  21 +outcube, f, h, NONE,,, "Output counts cube file"
  22 +outmap, f, a, map.fits,,, "Output map file"
  23 +outmodel, f, a, model.xml,,, "Output model definition file"
22 24 caldb, s, a, prod2,,, "Calibration database"
23 25 irf, s, a, South_0.5h,,, "Instrument response function"
24   -prefix, s, a, findvar_,,, "Prefix for output files"
25 26  
26 27 #
27 28 # Variability search parameters
28 29 #==============================
29   -minoff, r, h, 0,,, "Minimum required events in off bin for computing Noff and alpha"
30   -emin, r, a, 0,,, "Lower energy limit (units of TeV; 0 => no cut)"
31   -emax, r, a, 0,,, "Upper energy limit (units of TeV; 0 => no cut)"
32   -sig_thr, r, h, 4.5,,, "Significance threshold for variability detection"
33   -smoothkrnl, s, h, NONE, GAUSSIAN|DISK|NONE,, "Smoothing kernel for counts maps"
34   -smoothpar, r, h, 0.05,,, "Parameter for the smoothing kernel"
  30 +coordsys, s, a, CEL,CEL|GAL,,"Coordinate system (CEL - celestial, GAL - galactic)"
  31 +xsrc, r, a, 83.63,0,360, "First coordinate of the source of interest (RA or galactic l)"
  32 +ysrc, r, a, 22.01,-90,90, "Second coordinate of the source of interest (DEC or galactic b)"
  33 +emin, r, a, 0.1,,, "Lower energy limit (TeV)"
  34 +emax, r, a, 100.0,,, "Upper energy limit (TeV)"
  35 +threshold, r, h, 4.5,,, "Significance threshold for variability detection"
  36 +minoff, r, h, 0,,, "Minimum required events in off bin for computing Noff and alpha"
  37 +smooth_kernel, s, h, NONE, GAUSSIAN|DISK|NONE,, "Smoothing kernel for counts maps"
  38 +smooth_rad, r, h, 0.05,,, "Smoothing kernel radius (deg)"
35 39  
36 40 #
37 41 # Time binning parameters
... ... @@ -47,12 +51,9 @@ usepnt, b, h, no,,, &quot;Use pointing instead of xref/yref parameters?&quot;
47 51 nxpix, i, a, 200,,, "Size of the X axis in pixels"
48 52 nypix, i, a, 200,,, "Size of the Y axis in pixels"
49 53 binsz, r, a, 0.02,,, "Image scale (in degrees/pixel)"
50   -coordsys, s, a, CEL,CEL|GAL,,"Coordinate system (CEL - celestial, GAL - galactic)"
51 54 proj, s, a, CAR,AIT|AZP|CAR|GLS|MER|MOL|SFL|SIN|STG|TAN,, "Projection method"
52 55 xref, r, a, 83.63,0,360, "First coordinate of image center in degrees (RA or galactic l)"
53 56 yref, r, a, 22.51,-90,90, "Second coordinate of image center in degrees (DEC or galactic b)"
54   -xsrc, r, a, 83.63,0,360, "First coordinate of the source of interest (RA or galactic l)"
55   -ysrc, r, a, 22.51,-90,90, "Second coordinate of the source of interest (DEC or galactic b)"
56 57  
57 58 #
58 59 # Standard parameters
... ...
test/test_ctfindvar.py
... ... @@ -71,8 +71,9 @@ class Test(test):
71 71  
72 72 # Setup ctfindvar command
73 73 cmd = ctfindvar+' inobs="'+self._events+'"'+\
  74 + ' outmap="ctfindvar_cmd1.fits"'+\
  75 + ' outmodel="ctfindvar_cmd1.xml"'+\
74 76 ' logfile="ctfindvar_cmd1.log" chatter=1'+\
75   - ' prefix=ctfindvar_test_ '+\
76 77 ' tmin=NONE tmax=NONE tinterval=20'+\
77 78 ' emin=0.2 emax=1.1'+\
78 79 ' nxpix=200 nypix=200 binsz=0.05'+\
... ... @@ -80,12 +81,28 @@ class Test(test):
80 81 ' xref=83.63 yref=22.51'+\
81 82 ' xsrc=83.63 ysrc=22.51'+\
82 83 ' caldb=prod2 irf=South_0.5h'
83   -
84   -
  84 +
85 85 # Check if execution was successful
86 86 self.test_value(self._execute(cmd), 0,
87 87 'Check successful execution from command line')
88 88  
  89 + # Setup ctfindvar command
  90 + cmd = ctfindvar+' inobs="event_file_that_does_not_exist.fits"'+ \
  91 + ' outmap="ctfindvar_cmd2.fits"'+ \
  92 + ' outmodel="ctfindvar_cmd2.xml"'+ \
  93 + ' logfile="ctfindvar_cmd2.log" chatter=1'+ \
  94 + ' tmin=NONE tmax=NONE tinterval=20'+ \
  95 + ' emin=0.2 emax=1.1'+ \
  96 + ' nxpix=200 nypix=200 binsz=0.05'+ \
  97 + ' proj=CAR coordsys=CEL'+ \
  98 + ' xref=83.63 yref=22.51'+ \
  99 + ' xsrc=83.63 ysrc=22.51'+ \
  100 + ' caldb=prod2 irf=South_0.5h'
  101 +
  102 + # Check if execution was successful
  103 + self.test_assert(self._execute(cmd, success=False) != 0,
  104 + 'Check invalid input file when executed from command line')
  105 +
89 106 # Check ctfindvar --help
90 107 self._check_help(ctfindvar)
91 108  
... ... @@ -101,11 +118,8 @@ class Test(test):
101 118 # Allocate ctfindvar
102 119 ctfindvar = ctools.ctfindvar()
103 120  
104   - #setup ctfindvar
  121 + # Setup ctfindvar
105 122 ctfindvar['inobs'] = self._events
106   - ctfindvar['logfile'] = 'ctfindvar_py0.log'
107   - ctfindvar['chatter'] = 1
108   - ctfindvar['prefix'] = 'ctfindvar_test_python_'
109 123 ctfindvar['tmin'] = 'NONE'
110 124 ctfindvar['tmax'] = 'NONE'
111 125 ctfindvar['tinterval'] = 20
... ... @@ -122,32 +136,39 @@ class Test(test):
122 136 ctfindvar['ysrc'] = 22.51
123 137 ctfindvar['caldb'] = 'prod2'
124 138 ctfindvar['irf'] = 'South_0.5h'
  139 + ctfindvar['outmap'] = 'ctfindvar_py0.fits'
  140 + ctfindvar['outmodel'] = 'ctfindvar_py0.xml'
  141 + ctfindvar['logfile'] = 'ctfindvar_py0.log'
  142 + ctfindvar['chatter'] = 1
125 143  
126   - ## Run ctfindvar tool
  144 + # Run ctfindvar tool
127 145 ctfindvar.logFileOpen()
128 146 ctfindvar.run()
129 147  
130 148 # Check that saving does not nothing
131   - ctfindvar['logfile'] = 'ctfindvar_py1.log'
  149 + ctfindvar['outmap'] = 'ctfindvar_py1.fits'
  150 + ctfindvar['outmodel'] = 'ctfindvar_py1.xml'
  151 + ctfindvar['logfile'] = 'ctfindvar_py1.log'
132 152 ctfindvar.logFileOpen()
133 153 ctfindvar.save()
134 154  
135 155 # Check result
136   - self._check_result_file('ctfindvar_test_python_signifmap.fits')
  156 + self._check_result_file('ctfindvar_py1.fits')
137 157  
138 158 # Copy ctfindvar tool
139 159 cpy_tool = ctfindvar
140 160  
141 161 # Run copy of ctfindvar tool again
142   - cpy_tool['logfile'] = 'ctfindvar_py2.log'
143   - cpy_tool['chatter'] = 3
144   - cpy_tool['prefix'] = 'ctfindvar_test_copytool_'
  162 + cpy_tool['outmap'] = 'ctfindvar_py2.fits'
  163 + cpy_tool['outmodel'] = 'ctfindvar_py2.xml'
  164 + cpy_tool['logfile'] = 'ctfindvar_py2.log'
  165 + cpy_tool['chatter'] = 3
145 166 cpy_tool.logFileOpen()
146 167 cpy_tool.run()
147 168 cpy_tool.save()
148 169  
149 170 # Check result
150   - self._check_result_file('ctfindvar_test_copytool_signifmap.fits')
  171 + self._check_result_file('ctfindvar_py2.fits')
151 172  
152 173 # Check that clearing does not lead to an exception or segfault
153 174 ctfindvar.clear()
... ... @@ -158,17 +179,16 @@ class Test(test):
158 179 # Check ctfindvar result
159 180 def _check_result_file(self, filename):
160 181 """
161   - Check content of tool
  182 + Check ctfindvar result
162 183  
163 184 Parameters
164 185 ----------
165   - tool : `~ctools.ctfindvar`
166   - ctfindvar instance
  186 + filename : str
  187 + Output file
167 188 """
168   - print "checking if you amazing file is valid"
169   - # Read variability file
170   - fits = gammalib.GFits(filename)
171   - self.test_value(fits.size(), 3,
172   - 'Check for 3 rows in srcsig file')
  189 + # Read variability file
  190 + fits = gammalib.GFits(filename)
  191 + self.test_value(fits.size(), 3, 'Check for 3 extensions in output file')
  192 +
173 193 # Return
174 194 return
... ...