Line data Source code
1 : /*
2 : * SDDoubleCircleGainCal.cpp
3 : *
4 : * Created on: May 31, 2016
5 : * Author: nakazato
6 : */
7 :
8 : //#include "SDDoubleCircleGainCal.h"
9 : #include "SDDoubleCircleGainCalImpl.h"
10 :
11 : #include <casacore/casa/Arrays/Vector.h>
12 : #include <casacore/casa/Arrays/ArrayMath.h>
13 : #include <casacore/casa/IO/ArrayIO.h>
14 : #include <casacore/scimath/Functionals/Interpolate1D.h>
15 : #include <casacore/scimath/Functionals/ScalarSampledFunctional.h>
16 : #include <casacore/casa/BasicSL/Constants.h>
17 : #include <casacore/casa/Logging/LogIO.h>
18 : #include <casacore/casa/Quanta/Quantum.h>
19 :
20 : #include <casacore/casa/Utilities/Sort.h>
21 :
22 : #include <cassert>
23 : #include <cmath>
24 :
25 : using namespace casacore;
26 :
27 : #define LOG logger_ << LogOrigin("SDDoubleCircleGainCal", __FUNCTION__, WHERE)
28 : #define POSTLOG LogIO::POST
29 :
30 : namespace { // anonymous namespace START
31 : // primary beam size based on observing frequency and antenna diameter in radian
32 30 : inline Double getPrimaryBeamSize(Double const observing_frequency,
33 : Double const antenna_diameter) {
34 30 : Double beam_size = -1.0;
35 30 : constexpr Double kFactorALMA = 1.13; // measured factor for ALMA
36 30 : Double const speed_of_light = C::c;
37 : //Double const rad2arcsec = 180.0 / C::pi * 3600.0;
38 :
39 30 : if (observing_frequency <= 0.0 || antenna_diameter <= 0.0) {
40 0 : beam_size = 0.0;
41 : } else {
42 30 : beam_size = kFactorALMA * speed_of_light // * rad2arcsec
43 30 : / (antenna_diameter * observing_frequency);
44 : }
45 30 : return beam_size;
46 : }
47 :
48 : // default smoothing size based on the radius of central region
49 : // radius will be given in unit of radian but the following formula
50 : // seems to assume radius in unit of arcsec
51 41 : inline Int getDefaultSmoothingSize(Double const radius) {
52 41 : auto const radius_in_arcsec = Quantity(radius, "rad").getValue("arcsec");
53 41 : return 2 * static_cast<Int>(round(radius_in_arcsec / 1.5)) + 1;
54 : }
55 :
56 : // average
57 0 : inline Double average(size_t const index_from, size_t const index_to,
58 : Vector<Double> const &data) {
59 0 : Double sum = 0.0;
60 0 : assert(index_from <= index_to);
61 : //cout << "number of data to be averaged " << index_to - index_from << endl;
62 0 : for (size_t i = index_from; i < index_to; ++i) {
63 0 : sum += data[i];
64 : }
65 0 : return sum / static_cast<Double>(index_to - index_from);
66 : }
67 3092 : inline Double average(size_t const index_from, size_t const index_to,
68 : Vector<Double> const &data, Vector<Bool> const &flag) {
69 3092 : Double sum = 0.0;
70 3092 : size_t count = 0;
71 3092 : assert(index_from <= index_to);
72 : //cout << "number of data to be averaged " << index_to - index_from << endl;
73 215816 : for (size_t i = index_from; i < index_to; ++i) {
74 212724 : if (flag[i] == false) {
75 212520 : sum += data[i];
76 212520 : count++;
77 : }
78 : }
79 :
80 3092 : if (count == 0) {
81 2 : return 0.0;
82 : }
83 :
84 3090 : return sum / static_cast<Double>(count);
85 : }
86 :
87 : // smoothing
88 0 : inline void smooth(Int const smooth_size, Vector<Double> const &data,
89 : Vector<Double> &smoothed_data) {
90 : // TODO replace with sakura function
91 0 : assert(data.nelements() == smoothed_data.nelements());
92 0 : size_t num_data = data.nelements();
93 0 : if (smooth_size < 2 || static_cast<size_t>(smooth_size) >= num_data) {
94 : //cout << "no smoothing" << endl;
95 0 : smoothed_data = data;
96 : } else {
97 0 : size_t left_edge = (smooth_size + 1) / 2 - 1;
98 0 : size_t right_edge = smooth_size / 2 + 1;
99 0 : for (size_t i = 0; i < left_edge; ++i) {
100 0 : size_t l = 0;
101 0 : size_t r = i + right_edge;
102 : //cout << "i = " << i << ": l, r = " << l << "," << r << endl;
103 0 : smoothed_data[i] = average(l, r, data);
104 : }
105 0 : for (size_t i = left_edge; i < num_data - right_edge; ++i) {
106 0 : size_t l = i - left_edge;
107 0 : size_t r = i + right_edge;
108 : //cout << "i = " << i << ": l, r = " << l << "," << r << endl;
109 0 : smoothed_data[i] = average(l, r, data);
110 : }
111 0 : for (size_t i = num_data - right_edge; i < num_data; ++i) {
112 0 : size_t l = i - left_edge;
113 0 : size_t r = num_data;
114 : //cout << "i = " << i << ": l, r = " << l << "," << r << endl;
115 0 : smoothed_data[i] = average(l, r, data);
116 : }
117 : }
118 0 : }
119 :
120 32 : inline void smooth(Int const smooth_size, Vector<Double> const &data,
121 : Vector<Bool> const &flag, Vector<Double> &smoothed_data) {
122 : // TODO replace with sakura function
123 32 : assert(data.nelements() == smoothed_data.nelements());
124 32 : size_t num_data = data.nelements();
125 32 : if (smooth_size < 2 || static_cast<size_t>(smooth_size) >= num_data) {
126 : //cout << "no smoothing" << endl;
127 0 : smoothed_data = data;
128 : } else {
129 32 : size_t left_edge = (smooth_size + 1) / 2 - 1;
130 32 : size_t right_edge = smooth_size / 2 + 1;
131 1408 : for (size_t i = 0; i < left_edge; ++i) {
132 1376 : size_t l = 0;
133 1376 : size_t r = i + right_edge;
134 : //cout << "i = " << i << ": l, r = " << l << "," << r << endl;
135 1376 : if (flag[i] == true) {
136 86 : smoothed_data[i] = data[i];
137 : } else {
138 1290 : smoothed_data[i] = average(l, r, data, flag);
139 : }
140 : }
141 512 : for (size_t i = left_edge; i < num_data - right_edge; ++i) {
142 480 : size_t l = i - left_edge;
143 480 : size_t r = i + right_edge;
144 : //cout << "i = " << i << ": l, r = " << l << "," << r << endl;
145 480 : if (flag[i] == true) {
146 30 : smoothed_data[i] = data[i];
147 : } else {
148 450 : smoothed_data[i] = average(l, r, data, flag);
149 : }
150 : }
151 1440 : for (size_t i = num_data - right_edge; i < num_data; ++i) {
152 1408 : size_t l = i - left_edge;
153 1408 : size_t r = num_data;
154 : //cout << "i = " << i << ": l, r = " << l << "," << r << endl;
155 1408 : if (flag[i] == true) {
156 88 : smoothed_data[i] = data[i];
157 : } else {
158 1320 : smoothed_data[i] = average(l, r, data, flag);
159 : }
160 : }
161 : }
162 32 : }
163 :
164 : // interpolation
165 : // unused
166 : /*
167 : inline void interpolateLinear(Vector<Double> const &x0,
168 : Vector<Double> const &y0, Vector<Double> const &x1, Vector<Double> &y1) {
169 : // TODO replace with sakura function (need to add double precision version of interpolation function)
170 : Interpolate1D<Double, Double> interpolator(
171 : ScalarSampledFunctional<Double>(x0), ScalarSampledFunctional<Double>(y0),
172 : true, true);
173 : interpolator.setMethod(Interpolate1D<Double, Double>::linear);
174 : for (size_t i = 0; i < x1.nelements(); ++i) {
175 : y1[i] = interpolator(x1[i]);
176 : }
177 : }
178 : */
179 : // utility
180 36 : inline size_t toUnsigned(ssize_t const v) {
181 36 : assert(v >= 0);
182 36 : return static_cast<size_t>(v);
183 : }
184 : } // anonymous namespace END
185 :
186 : namespace casa { // namespace casa START
187 19 : SDDoubleCircleGainCalImpl::SDDoubleCircleGainCalImpl() :
188 19 : central_region_(-1.0), do_smooth_(false), smooth_size_(-1), observing_frequency_(
189 19 : 0.0), antenna_diameter_(0.0), logger_() {
190 19 : }
191 :
192 19 : SDDoubleCircleGainCalImpl::~SDDoubleCircleGainCalImpl() {
193 19 : }
194 :
195 30 : Double SDDoubleCircleGainCalImpl::getPrimaryBeamSize() const {
196 : // ::getPrimaryBeamSize returns the size in radian
197 30 : return ::getPrimaryBeamSize(observing_frequency_, antenna_diameter_);
198 : }
199 :
200 41 : Int SDDoubleCircleGainCalImpl::getDefaultSmoothingSize() const {
201 41 : Int default_size = -1;
202 41 : if (central_region_ > 0.0) {
203 40 : default_size = ::getDefaultSmoothingSize(central_region_);
204 : } else {
205 1 : default_size = ::getDefaultSmoothingSize(0.5 * getPrimaryBeamSize());
206 : }
207 41 : return default_size;
208 : }
209 :
210 0 : void SDDoubleCircleGainCalImpl::calibrate(Cube<Float> const &data,
211 : Vector<Double> const &time, Matrix<Double> const &direction,
212 : Vector<Double> &gain_time, Cube<Float> &gain) {
213 :
214 : // radius of the central region
215 0 : Double radius = getRadius();
216 :
217 0 : LOG << "radius = " << radius << POSTLOG;
218 :
219 : // select data within radius
220 0 : auto const data_shape = data.shape();
221 0 : size_t const num_pol = ::toUnsigned(data_shape[0]);
222 0 : size_t const num_chan = ::toUnsigned(data_shape[1]);
223 0 : assert(time.nelements() == uInt(data_shape[2]));
224 0 : assert(direction.shape()[1] == data_shape[2]);
225 0 : assert(direction.shape()[0] == 2);
226 0 : findDataWithinRadius(radius, time, data, direction, gain_time, gain);
227 0 : size_t num_gain = gain_time.nelements();
228 0 : LOG << "num_gain = " << num_gain << POSTLOG;
229 :
230 : //LOG << "indices within radius: " << within_radius << POSTLOG;
231 :
232 0 : if (num_gain < 100) {
233 0 : LOG << LogIO::WARN << "Probably not enough points for gain calibration: "
234 0 : << num_gain << endl << "Skipping..." << POSTLOG;
235 0 : gain_time.resize();
236 0 : gain.resize();
237 0 : return;
238 : }
239 :
240 : // for each spectral data
241 0 : Vector<Double> work_data(num_gain);
242 0 : Vector<Double> smoothed_data;
243 0 : if (do_smooth_) {
244 0 : smoothed_data.resize(num_gain);
245 : }
246 0 : for (size_t ipol = 0; ipol < num_pol; ++ipol) {
247 0 : for (size_t ichan = 0; ichan < num_chan; ++ichan) {
248 0 : for (size_t idata = 0; idata < num_gain; ++idata) {
249 0 : work_data[idata] = gain(ipol, ichan, idata);
250 : }
251 :
252 : // LOG << "work_data[" << ipol << "," << ichan << "]=" << work_data
253 : // << POSTLOG;
254 :
255 : // smoothing if necessary
256 0 : if (do_smooth_) {
257 0 : Int smooth_size = getEffectiveSmoothingSize();
258 0 : if (smooth_size < 2 || static_cast<size_t>(smooth_size) >= num_gain) {
259 0 : LOG << LogIO::WARN
260 : << "data is not smoothed since smoothing size is invalid: "
261 : << smooth_size << " (number of data " << num_gain << ")"
262 0 : << POSTLOG;
263 : }
264 0 : smooth(smooth_size, work_data, smoothed_data);
265 : } else {
266 0 : LOG << "no smoothing" << POSTLOG;
267 0 : smoothed_data.reference(work_data);
268 : }
269 :
270 0 : LOG << LogIO::DEBUGGING << "smoothed_data[" << ipol << "," << ichan
271 0 : << "]=" << smoothed_data << POSTLOG;
272 :
273 0 : LOG << LogIO::DEBUGGING << "mean value = " << mean(smoothed_data)
274 0 : << POSTLOG;
275 :
276 : // derive gain factor: mean(smoothed_data) / smoothed_data
277 0 : work_data = mean(smoothed_data) / smoothed_data;
278 :
279 : // LOG << "gfactor[" << ipol << "," << ichan << "]=" << work_data
280 : // << POSTLOG;
281 :
282 : // conversion for G type calibration
283 0 : work_data = 1.0 / sqrt(work_data);
284 :
285 : // LOG << "fparam[" << ipol << "," << ichan << "]=" << work_data
286 : // << POSTLOG;
287 :
288 0 : for (size_t idata = 0; idata < num_gain; ++idata) {
289 0 : gain(ipol, ichan, idata) = work_data[idata];
290 : }
291 : }
292 : }
293 0 : }
294 :
295 0 : void SDDoubleCircleGainCalImpl::calibrate(Cube<Float> const &data,
296 : Cube<Bool> const &flag, Vector<Double> const &time,
297 : Matrix<Double> const &direction, Vector<Double> &gain_time,
298 : Cube<Float> &gain, Cube<Bool> &gain_flag) {
299 :
300 : // radius of the central region
301 0 : Double radius = getRadius();
302 :
303 0 : LOG << "radius = " << radius << POSTLOG;
304 :
305 : // select data within radius
306 0 : auto const data_shape = data.shape();
307 0 : assert(time.nelements() == uInt(data_shape[2]));
308 0 : assert(direction.shape()[1] == data_shape[2]);
309 0 : assert(direction.shape()[0] == 2);
310 0 : findDataWithinRadius(radius, time, data, flag, direction, gain_time, gain,
311 : gain_flag);
312 0 : doCalibrate(gain_time, gain, gain_flag);
313 0 : }
314 :
315 18 : void SDDoubleCircleGainCalImpl::doCalibrate(Vector<Double> &gain_time,
316 : Cube<Float> &gain, Cube<Bool> &gain_flag) {
317 :
318 18 : size_t const num_gain = gain_time.nelements();
319 18 : LOG << "num_gain = " << num_gain << POSTLOG;
320 :
321 18 : size_t const num_pol = ::toUnsigned(gain.shape()[0]);
322 18 : size_t const num_chan = ::toUnsigned(gain.shape()[1]);
323 18 : assert(gain.shape()[2] == num_gain);
324 :
325 : //LOG << "indices within radius: " << within_radius << POSTLOG;
326 :
327 18 : if (num_gain < 100) {
328 4 : LOG << LogIO::WARN << "Probably not enough points for gain calibration: "
329 2 : << num_gain << endl << "Skipping..." << POSTLOG;
330 2 : gain_time.resize();
331 2 : gain.resize();
332 2 : gain_flag.resize();
333 2 : return;
334 : }
335 :
336 : // for each spectral data
337 16 : Vector<Double> work_data(num_gain);
338 16 : Vector<Bool> work_flag(num_gain);
339 16 : Vector<Double> smoothed_data(num_gain);
340 48 : for (size_t ipol = 0; ipol < num_pol; ++ipol) {
341 64 : for (size_t ichan = 0; ichan < num_chan; ++ichan) {
342 3296 : for (size_t idata = 0; idata < num_gain; ++idata) {
343 3264 : work_data[idata] = gain(ipol, ichan, idata);
344 3264 : work_flag[idata] = gain_flag(ipol, ichan, idata);
345 : }
346 :
347 : // LOG << "work_data[" << ipol << "," << ichan << "]=" << work_data
348 : // << POSTLOG;
349 :
350 : // smoothing if necessary
351 32 : if (do_smooth_) {
352 32 : Int smooth_size = getEffectiveSmoothingSize();
353 32 : if (smooth_size < 2 || static_cast<size_t>(smooth_size) >= num_gain) {
354 0 : LOG << LogIO::WARN
355 : << "data is not smoothed since smoothing size is invalid: "
356 : << smooth_size << " (number of data " << num_gain << ")"
357 0 : << POSTLOG;
358 : }
359 32 : smooth(smooth_size, work_data, work_flag, smoothed_data);
360 : } else {
361 0 : LOG << "no smoothing" << POSTLOG;
362 0 : smoothed_data = work_data;
363 : }
364 :
365 : // LOG << LogIO::DEBUGGING << "smoothed_data[" << ipol << "," << ichan
366 : // << "]=" << smoothed_data << POSTLOG;
367 :
368 : // derive gain factor: mean(smoothed_data) / smoothed_data
369 : //work_data = mean(smoothed_data) / smoothed_data;
370 32 : auto mean_value = ::average(0, num_gain, smoothed_data, work_flag);
371 :
372 : //work_data = mean_value / smoothed_data;
373 32 : if (mean_value < 0.0) {
374 0 : LOG << LogIO::WARN
375 : << "Negative reference value for gain calibration is found. "
376 0 : << "No valid calibration solution will be provided" << POSTLOG;
377 0 : work_data = 0.0;
378 0 : work_flag = True;
379 : } else {
380 3296 : for (size_t idata = 0; idata < num_gain; ++idata) {
381 3264 : if (work_data[idata] != 0.0) {
382 3264 : work_data[idata] = mean_value / smoothed_data[idata];
383 : }
384 : else {
385 0 : work_data[idata] = 0.0;
386 0 : work_flag[idata] = True;
387 : }
388 : }
389 :
390 64 : LOG << LogIO::DEBUGGING << "mean value = " << mean_value
391 32 : << " (simple mean " << mean(smoothed_data) << ")" << POSTLOG;
392 : }
393 :
394 : // LOG << LogIO::DEBUGGING << "gfactor[" << ipol << "," << ichan << "]=" << work_data
395 : // << POSTLOG;
396 :
397 : // conversion for G type calibration
398 : //work_data = 1.0 / sqrt(work_data);
399 3296 : for (size_t idata = 0; idata < num_gain; ++idata) {
400 3264 : if (work_data[idata] > 0.0) {
401 3060 : work_data[idata] = 1.0 / sqrt(work_data[idata]);
402 : } else {
403 204 : work_data[idata] = 0.0;
404 204 : work_flag[idata] = True;
405 : }
406 : }
407 :
408 : // LOG << LogIO::DEBUGGING << "fparam[" << ipol << "," << ichan << "]=" << work_data
409 : // << POSTLOG;
410 :
411 3296 : for (size_t idata = 0; idata < num_gain; ++idata) {
412 3264 : gain(ipol, ichan, idata) = work_data[idata];
413 3264 : gain_flag(ipol, ichan, idata) = work_flag[idata];
414 : }
415 : }
416 : }
417 16 : }
418 :
419 18 : Double SDDoubleCircleGainCalImpl::getRadius() {
420 : // radius of the central region
421 18 : Double radius = central_region_;
422 18 : if (radius <= 0.0) {
423 : // use default value: primary beam size
424 2 : radius = 0.5 * getPrimaryBeamSize();
425 : }
426 18 : if (radius <= 0.0) {
427 0 : ostringstream ss;
428 0 : ss << "Size of central region is not properly set: " << radius;
429 0 : LOG << LogIO::SEVERE << ss.str() << POSTLOG;
430 0 : throw AipsError(ss.str());
431 0 : }
432 :
433 18 : return radius;
434 : }
435 :
436 41 : Int SDDoubleCircleGainCalImpl::getEffectiveSmoothingSize() {
437 41 : LOG << "do smoothing with size " << smooth_size_ << POSTLOG;
438 :
439 41 : Int smooth_size = smooth_size_;
440 41 : if (smooth_size_ < 0) {
441 41 : smooth_size = getDefaultSmoothingSize();
442 82 : LOG << "default smoothing size will be used: " << smooth_size
443 41 : << POSTLOG;
444 : }
445 :
446 41 : return smooth_size;
447 : }
448 :
449 0 : void SDDoubleCircleGainCalImpl::findDataWithinRadius(Double const radius,
450 : Vector<Double> const &time, Cube<Float> const &data,
451 : Matrix<Double> const &direction, Vector<Double> &gain_time,
452 : Cube<Float> &gain) {
453 0 : Cube<Bool> flag(data.shape(), False);
454 0 : Cube<Bool> gain_flag;
455 0 : findDataWithinRadius(radius, time, data, flag, direction, gain_time, gain,
456 : gain_flag);
457 0 : }
458 :
459 0 : void SDDoubleCircleGainCalImpl::findDataWithinRadius(Double const radius,
460 : Vector<Double> const &time, Cube<Float> const &data, Cube<Bool> const &flag,
461 : Matrix<Double> const &direction, Vector<Double> &gain_time,
462 : Cube<Float> &gain, Cube<Bool> &gain_flag) {
463 0 : size_t num_data = ::toUnsigned(direction.shape()[1]);
464 : // find data within radius
465 0 : Vector<size_t> data_index(num_data);
466 0 : size_t num_gain = 0;
467 0 : for (size_t i = 0; i < num_data; ++i) {
468 0 : Double x = direction(0, i);
469 0 : Double y = direction(1, i);
470 0 : Double r2 = x * x + y * y;
471 0 : if (r2 <= radius * radius) {
472 0 : data_index[num_gain] = i;
473 0 : num_gain++;
474 : }
475 : }
476 :
477 : // store data for calibration
478 0 : gain_time.resize(num_gain);
479 0 : IPosition gain_shape(data.shape());
480 0 : gain_shape[2] = num_gain;
481 0 : gain.resize(gain_shape);
482 0 : gain_flag.resize(gain_shape);
483 0 : for (size_t i = 0; i < num_gain; ++i) {
484 0 : size_t j = data_index[i];
485 0 : gain_time[i] = time[j];
486 0 : gain.xyPlane(i) = data.xyPlane(j);
487 0 : gain_flag.xyPlane(i) = flag.xyPlane(j);
488 : }
489 0 : }
490 :
491 9 : bool SDDoubleCircleGainCalImpl::findTimeRange(Vector<Double> const &time,
492 : Vector<Double> const &interval, Matrix<Double> const &direction,
493 : TimeRangeList &timerange) {
494 : // radius of the central region
495 9 : Double radius = getRadius();
496 :
497 : // find data within radius
498 9 : size_t num_all_data = time.nelements();
499 9 : Vector<size_t> data_index(num_all_data);
500 9 : size_t num_data = 0;
501 40518 : for (size_t i = 0; i < num_all_data; ++i) {
502 40509 : Double x = direction(0, i);
503 40509 : Double y = direction(1, i);
504 40509 : Double r2 = x * x + y * y;
505 40509 : if (r2 <= radius * radius) {
506 829 : data_index[num_data] = i;
507 829 : num_data++;
508 : }
509 : }
510 :
511 : // list of time and interval
512 9 : Vector<Double> time_sel(num_data);
513 9 : Vector<Double> interval_sel(num_data);
514 838 : for (size_t i = 0; i < num_data; ++i) {
515 829 : time_sel[i] = time[data_index[i]];
516 829 : interval_sel[i] = interval[data_index[i]];
517 : }
518 :
519 : // sort
520 9 : Sort s(time_sel.data(), num_data);
521 9 : Vector<uInt> index_vector;
522 9 : uInt num_sorted = s.sort(index_vector, num_data);
523 9 : if (num_sorted != index_vector.nelements()) {
524 0 : return false;
525 : }
526 :
527 : // conversion from the list of timestamp to TimeRangeList
528 9 : timerange.clear();
529 9 : Double time_from = time_sel[index_vector[0]] - 0.5 * interval_sel[index_vector[0]];
530 9 : Double time_to = time_sel[index_vector[0]] + 0.5 * interval_sel[index_vector[0]];
531 9 : auto iter = index_vector.begin();
532 829 : for (++iter; iter != index_vector.end(); ++iter) {
533 820 : Double current_from = time_sel[*iter] - 0.5 * interval_sel[*iter];
534 820 : Double current_to = time_sel[*iter] + 0.5 * interval_sel[*iter];
535 820 : if (abs(current_from - time_to) < time_to * C::dbl_epsilon) {
536 0 : time_to = current_to;
537 : } else {
538 820 : TimeRange range(time_from, time_to);
539 820 : timerange.push_back(range);
540 820 : time_from = current_from;
541 820 : time_to = current_to;
542 : }
543 : }
544 9 : TimeRange range(time_from, time_to);
545 9 : timerange.push_back(range);
546 :
547 9 : return true;
548 9 : }
549 :
550 : //void SDDoubleCircleGainCalImpl::apply(Vector<Double> const &gain_time,
551 : // Cube<Float> const &gain, Vector<Double> const &time, Cube<Float> &data) {
552 : // // TODO implement
553 : // // not necessary to implement? reuse G type application?
554 : //}
555 : }// namespace casa END
|