diff --git a/.gitattributes b/.gitattributes index 540936775fc05646fb55638ba57dbde45fc36b4a..65929653f8ee137bd0b4287cdb2057c1db64e44f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -40,13 +40,16 @@ CAL/CalibrationProcessing/lib/__init__.py -text CAL/CalibrationProcessing/lib/process_holography_dataset.py -text CAL/CalibrationProcessing/lib/processing/__init__.py -text CAL/CalibrationProcessing/lib/processing/averaging.py -text -CAL/CalibrationProcessing/lib/processing/matrices.py -text +CAL/CalibrationProcessing/lib/processing/normalize.py -text +CAL/CalibrationProcessing/lib/processing/solver.py -text CAL/CalibrationProcessing/test/CMakeLists.txt -text +CAL/CalibrationProcessing/test/__init__.py -text +CAL/CalibrationProcessing/test/processing/CMakeLists.txt -text +CAL/CalibrationProcessing/test/processing/t_processing.py -text +CAL/CalibrationProcessing/test/processing/t_processing.run -text +CAL/CalibrationProcessing/test/processing/t_processing.sh -text CAL/CalibrationProcessing/test/profiling_processing.py -text -CAL/CalibrationProcessing/test/t_processing.py -text -CAL/CalibrationProcessing/test/t_processing.run -text -CAL/CalibrationProcessing/test/t_processing.sh -text -CAL/CalibrationProcessing/test/test_utils.py -text +CAL/CalibrationProcessing/test/utils.py -text CEP/Calibration/BBSControl/include/BBSControl/CommandHandlerEstimator.h -text CEP/Calibration/BBSControl/include/BBSControl/CommandHandlerReducer.h -text CEP/Calibration/BBSControl/include/BBSControl/OptionParser.h -text diff --git a/CAL/CalibrationCommon/lib/datacontainers/__init__.py b/CAL/CalibrationCommon/lib/datacontainers/__init__.py index 33d69fa5d3fde3cbfe6a3f2510ebe61d266ae825..7418ea83e6c5a1715534210508bd2b282fdf9f90 100644 --- a/CAL/CalibrationCommon/lib/datacontainers/__init__.py +++ b/CAL/CalibrationCommon/lib/datacontainers/__init__.py @@ -1,8 +1,8 @@ +__all__ = ['HolographyDataset', 'HolographyObservation', 'HolographySpecification', 'HolographyMeasurementSet'] + from .holography_specification import HolographySpecification from .holography_observation import HolographyObservation from .holography_measurementset import HolographyMeasurementSet from .holography_dataset import HolographyDataset - -__all__ = ['HolographyDataset', 'HolographyObservation', 'HolographySpecification', 'HolographyMeasurementSet'] \ No newline at end of file diff --git a/CAL/CalibrationProcessing/lib/CMakeLists.txt b/CAL/CalibrationProcessing/lib/CMakeLists.txt index 38c673e25925f17c0af28a11d64af92dba1972c3..4434b356d113279e9ed80303475315ad096851e8 100644 --- a/CAL/CalibrationProcessing/lib/CMakeLists.txt +++ b/CAL/CalibrationProcessing/lib/CMakeLists.txt @@ -20,8 +20,9 @@ python_install( __init__.py processing/averaging.py - processing/matrices.py + processing/normalize.py + processing/solver.py processing/__init__.py DESTINATION lofar/calibration) -lofar_add_bin_script(process_holography_dataset.py RENAME process_holography_dataset) \ No newline at end of file +lofar_add_bin_script(process_holography_dataset.py RENAME process_holography_dataset) diff --git a/CAL/CalibrationProcessing/lib/processing/__init__.py b/CAL/CalibrationProcessing/lib/processing/__init__.py index a26b7d7eb0ddb5d3ebae74aacc521aea1e25953b..90a414f8660ea4a6a269820a8fb54ee89bff8572 100644 --- a/CAL/CalibrationProcessing/lib/processing/__init__.py +++ b/CAL/CalibrationProcessing/lib/processing/__init__.py @@ -1,2 +1,2 @@ from .averaging import * -from .matrices import * \ No newline at end of file +from .normalize import * \ No newline at end of file diff --git a/CAL/CalibrationProcessing/lib/processing/averaging.py b/CAL/CalibrationProcessing/lib/processing/averaging.py index 11d031c92c4dd6a7010710aca76867df6f21f198..56f518a660750b827996e6f12613ac68cf9f7127 100644 --- a/CAL/CalibrationProcessing/lib/processing/averaging.py +++ b/CAL/CalibrationProcessing/lib/processing/averaging.py @@ -1,8 +1,9 @@ import logging import numpy - from lofar.calibration.common.datacontainers import HolographyDataset +from lofar.calibration.common.datacontainers.holography_dataset_definitions \ + import HDS_data_sample_type logger = logging.getLogger(__name__) @@ -178,7 +179,7 @@ def average_datatable_by_time_interval(data_table_in, time_interval): def _average_dataset_by_function(dataset, function, *parameters): out_data = {} - for reference_station, data_per_reference_station in dataset.data.items(): + for reference_station, data_per_reference_station in dataset.items(): if reference_station not in out_data: out_data[reference_station] = dict() @@ -234,16 +235,17 @@ def average_dataset_by_sample(input_data_set, window_size): logger.error(text) raise ValueError(text) else: - text = "Cannot average data by samples! The input data is of the invalid type %s." % (type(input_data_set)) - logger.error(text) - raise ValueError(text) + text = "Cannot average data by samples! The input data is of the invalid type %s." % ( + type(input_data_set)) + logger.error(text) + raise ValueError(text) if isinstance(int, window_size) is False or window_size < 1: text = "Cannot average data by samples! The window size must be an integer value bigger than 0." logger.error(text) raise ValueError(text) - out_data = _average_dataset_by_function(dataset, average_datatable_by_sample, window_size) + out_data = _average_dataset_by_function(dataset.data, average_datatable_by_sample, window_size) if dataset.derived_data is None: dataset.derived_data = dict() @@ -279,16 +281,17 @@ def average_dataset_by_time(input_data_set, time_interval): logger.error(text) raise ValueError(text) else: - text = "Cannot average data by time! The input data is of the invalid type %s." % (type(input_data_set)) - logger.error(text) - raise ValueError(text) + text = "Cannot average data by time! The input data is of the invalid type %s." % ( + type(input_data_set)) + logger.error(text) + raise ValueError(text) if isinstance(float, time_interval) is False or time_interval < 0: text = "Cannot average data by samples! The time interval must be positive." logger.error(text) raise ValueError(text) - out_data = _average_dataset_by_function(dataset, average_datatable_by_time_interval, + out_data = _average_dataset_by_function(dataset.data, average_datatable_by_time_interval, time_interval) if dataset.derived_data is None: dataset.derived_data = dict() @@ -298,6 +301,12 @@ def average_dataset_by_time(input_data_set, time_interval): return dataset +def average_data_by_sample(input_data_table, samples): + out_data = _average_dataset_by_function(input_data_table, average_datatable_by_sample, + samples) + return out_data + + def _compute_size_of_new_array(array_size, window_size): """ Round up the array size given a window size. @@ -315,3 +324,87 @@ def _compute_size_of_new_array(array_size, window_size): # add one if the len of the data_array is not divisible by the sample_width new_array_size += 1 if array_size % window_size else 0 return new_array_size + + +def _weigthed_average_data_per_polarization(input_dataset, output_dataset, + real_weights, imag_weights, polarization): + mask = input_dataset['flag'] == False + try: + output_dataset[polarization] = numpy.average( + input_dataset[polarization][mask].real, + weights=real_weights[polarization][mask]) \ + + 1.j * numpy.average( + input_dataset[polarization][mask].imag, + weights=imag_weights[polarization][mask]) + except ZeroDivisionError as e: + logger.warning('error in weighted average : %s', e) + output_dataset['flag'] = True + +def weighted_average_dataset_per_station(dataset, input_data_table): + """ + + :param dataset: + :type dataset: HolographyDataset + :param input_data_table: + :return: + """ + stations = dataset.reference_stations + n_stations = len(stations) + frequencies = dataset.frequencies + beams = dataset.beamlets + + result_ALL = dict() + result = dict(all=result_ALL) + for frequency in frequencies: + result_per_frequency = dict() + result_ALL[frequency] = result_per_frequency + + for beam in beams: + beam_str = str(beam) + result_per_beam = numpy.zeros(1, dtype=HDS_data_sample_type) + + + average_per_beam_station = numpy.zeros(n_stations, dtype=HDS_data_sample_type) + std_real_per_beam_station = numpy.zeros(n_stations, dtype=HDS_data_sample_type) + std_imag_per_beam_station = numpy.zeros(n_stations, dtype=HDS_data_sample_type) + + for i, station in enumerate(stations): + data_per_station_frequency_beam = \ + input_data_table[station][str(frequency)][beam_str] + average_per_beam_station['XX'][i] = data_per_station_frequency_beam['mean']['XX'] + average_per_beam_station['XY'][i] = data_per_station_frequency_beam['mean']['XY'] + average_per_beam_station['YX'][i] = data_per_station_frequency_beam['mean']['YX'] + average_per_beam_station['YY'][i] = data_per_station_frequency_beam['mean']['YY'] + + average_per_beam_station['l'][i] = data_per_station_frequency_beam['mean']['l'] + average_per_beam_station['m'][i] = data_per_station_frequency_beam['mean']['m'] + + std_real_per_beam_station['XX'][i] = \ + data_per_station_frequency_beam['std']['XX_real'] + std_real_per_beam_station['XY'][i] = \ + data_per_station_frequency_beam['std']['XY_real'] + std_real_per_beam_station['YX'][i] = \ + data_per_station_frequency_beam['std']['YX_real'] + std_real_per_beam_station['YY'][i] = \ + data_per_station_frequency_beam['std']['YY_real'] + + std_imag_per_beam_station['XX'][i] = \ + data_per_station_frequency_beam['std']['XX_imag'] + std_imag_per_beam_station['XY'][i] = \ + data_per_station_frequency_beam['std']['XY_imag'] + std_imag_per_beam_station['YX'][i] = \ + data_per_station_frequency_beam['std']['YX_imag'] + std_imag_per_beam_station['YY'][i] = \ + data_per_station_frequency_beam['std']['YY_imag'] + + for polarization in ['XX', 'XY', 'YX', 'YY']: + _weigthed_average_data_per_polarization(average_per_beam_station, + result_per_beam, + std_real_per_beam_station, + std_imag_per_beam_station, + polarization) + result_per_beam['l'] = numpy.average(average_per_beam_station['l']) + result_per_beam['m'] = numpy.average(average_per_beam_station['m']) + + result_per_frequency[beam_str] = dict(mean=result_per_beam) + return result diff --git a/CAL/CalibrationProcessing/lib/processing/matrices.py b/CAL/CalibrationProcessing/lib/processing/matrices.py deleted file mode 100644 index 232678327b5c67240c464230cb97d96c8c26104d..0000000000000000000000000000000000000000 --- a/CAL/CalibrationProcessing/lib/processing/matrices.py +++ /dev/null @@ -1,29 +0,0 @@ -import logging - -import numpy - -logger = logging.getLogger(__name__) - - -def extract_crosscorrelation_matrices_from_HDS_datatable(datatable): - new_shape = (2, 2, datatable.shape[0]) - new_array = numpy.zeros(shape=new_shape, dtype=numpy.complex) - new_array[0, 0, :] = datatable['XX'] - new_array[0, 1, :] = datatable['XY'] - new_array[1, 0, :] = datatable['YX'] - new_array[1, 1, :] = datatable['YY'] - - return new_array - - -def invert_crosscorrelation_matrices(cross_correlation_matrices): - """ - Invert the matrices along the last axis - :param cross_correlation_matrices: matrices to invert - :type cross_correlation_matrices: numpy.ndarray - :return: - """ - assert cross_correlation_matrices.ndim >= 3 - - return numpy.linalg.inv(cross_correlation_matrices) - diff --git a/CAL/CalibrationProcessing/lib/processing/normalize.py b/CAL/CalibrationProcessing/lib/processing/normalize.py new file mode 100644 index 0000000000000000000000000000000000000000..ba43ad48b83ae12937e14e114a2869e36d32209d --- /dev/null +++ b/CAL/CalibrationProcessing/lib/processing/normalize.py @@ -0,0 +1,135 @@ +import logging + +import numpy + +logger = logging.getLogger(__name__) + +__POLARIZATION_TO_INDEX = { + 'XX': (0,0), + 'XY': (0,1), + 'YX': (1,0), + 'YY': (1,1) +} + + +def normalize_beams_by_central_one(input_data, central_beamlet_number): + """ + + :param central_beamlet_number: number of the central beamleat + :type central_beamlet_number: str + :param input_data: input data to be processed + :type input_data: dict(dict(numpy.ndarray)) + :return: + """ + normalized_data = dict() + for station in input_data: + normalized_data_per_station = dict() + normalized_data[station] = normalized_data_per_station + + data_per_station = input_data[station] + + for frequency in data_per_station: + data_per_station_per_frequency = data_per_station[frequency] + + normalized_data_per_station_frequency = \ + normalize_crosscorrelation_beams(data_per_station_per_frequency, + central_beamlet_number) + + normalized_data_per_station[frequency] = normalized_data_per_station_frequency + return normalized_data + + +def normalize_crosscorrelation_beams(data_per_station_frequency, + central_beamlet): + """ + It normalize the crosscorrelation per each beam given the normalization array computed + from the central beam + :param data_per_station_frequency: + :param normalization_array: + :return: + """ + + """ + TO avoid copying the data in a different array and then copy the data back + I compute directly the product of the two array of matrices + + the resulting cross polarization as a function of time are: + XX = XX_b * XX_n + XY_b * YX_n + XY = XX_b * XY_n + XY_b * YY_n + YX = YX_b * XX_n + YY_b * YX_n + YY = YX_b * XY_n + YY_b * YY_n + + where b is the beam and n is the normalization array + """ + is_invertion_matrix_valid = True + normalization_array = None + try: + normalization_array = invert_central_beam_crosscorrelations(data_per_station_frequency, + central_beamlet) + except numpy.linalg.LinAlgError as e: + logger.warning('error inverting central beamlet crosscorrelation for beamlet %s: %s', + central_beamlet, + e) + print('error inverting central beamlet crosscorrelation for beamlet %s: %s' % ( + central_beamlet, + e)) + is_invertion_matrix_valid = False + + normalized_data_per_beam = dict() + for beam in data_per_station_frequency: + + data_per_beam = data_per_station_frequency[beam] + normalized_data = numpy.array(data_per_beam) + normalized_data_per_beam[beam] = normalized_data + + if is_invertion_matrix_valid == False: + normalized_data['flag'] = True + continue + + normalized_data['XX'] = \ + data_per_beam['XX'] * normalization_array[__POLARIZATION_TO_INDEX['XX']] + \ + data_per_beam['XY'] * normalization_array[__POLARIZATION_TO_INDEX['YX']] + normalized_data['XY'] = \ + data_per_beam['XX'] * normalization_array[__POLARIZATION_TO_INDEX['XY']] + \ + data_per_beam['XY'] * normalization_array[__POLARIZATION_TO_INDEX['YY']] + normalized_data['YX'] = \ + data_per_beam['YX'] * normalization_array[__POLARIZATION_TO_INDEX['XX']] + \ + data_per_beam['YY'] * normalization_array[__POLARIZATION_TO_INDEX['YX']] + normalized_data['YY'] = \ + data_per_beam['YX'] * normalization_array[__POLARIZATION_TO_INDEX['XY']] + \ + data_per_beam['YY'] * normalization_array[__POLARIZATION_TO_INDEX['YY']] + return normalized_data_per_beam + + +def invert_central_beam_crosscorrelations(data_per_station_per_frequency, central_beamlet_number): + data_per_central_beamlet = __extract_crosscorrelation_matrices_from_data( + data_per_station_per_frequency[central_beamlet_number]) + + return __invert_crosscorrelation_matrices(data_per_central_beamlet) + + +def __extract_crosscorrelation_matrices_from_data(data_per_station_frequency_beam): + new_shape = (data_per_station_frequency_beam.shape[0], 2, 2) + new_array = numpy.zeros(shape=new_shape, dtype=numpy.complex) + + new_array[:, 0, 0] = data_per_station_frequency_beam['XX'] + new_array[:, 0, 1] = data_per_station_frequency_beam['XY'] + new_array[:, 1, 0] = data_per_station_frequency_beam['YX'] + new_array[:, 1, 1] = data_per_station_frequency_beam['YY'] + + return new_array + + +def __invert_crosscorrelation_matrices(cross_correlation_matrices): + """ + Invert the matrices along the last axis + :param cross_correlation_matrices: matrices to invert + :type cross_correlation_matrices: numpy.ndarray + :return: + """ + assert cross_correlation_matrices.ndim >= 3 + + result_array = numpy.rollaxis(numpy.linalg.inv(cross_correlation_matrices), 0, 3) + + return result_array + diff --git a/CAL/CalibrationProcessing/lib/processing/solver.py b/CAL/CalibrationProcessing/lib/processing/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..9068eda32d97c0010effd695fb83a1b93a94b195 --- /dev/null +++ b/CAL/CalibrationProcessing/lib/processing/solver.py @@ -0,0 +1,169 @@ +import logging + +import lofar.calibration.common.datacontainers as datacontainers +from scipy.constants import c as LIGHT_SPEED + +logger = logging.getLogger(__name__) + +import numpy + +__FRQ_TO_PHASE = 2. * numpy.pi / LIGHT_SPEED + + +def _rotate_antenna_coordinates(dataset): + """ + + :param dataset: + :type dataset: datacontainers.HolographyDataset + :return: + """ + station_position = numpy.array(dataset.target_station_position) + antenna_field_position = numpy.array(dataset.antenna_field_position) + + antenna_position_offset = numpy.array([station_position - antenna_position for + antenna_position in antenna_field_position]) + + station_rotation_matrix = dataset.rotation_matrix + return numpy.dot(station_rotation_matrix, antenna_position_offset.T).T + + +def _compute_expected_phase_delay(l, m, x, y, frequency): + """ + Convert the antenna position (x,y) and the pointing cosines (l,m) into a phase delay for + the given frequency + :param l: + :param m: + :param x: + :param y: + :param frequency: + :return: + """ + phase = (x * l + y * m) * __FRQ_TO_PHASE * frequency + return phase + + +def _compute_pointing_matrices_per_station_frequency_beam(dataset, datatable, frequency): + """ + + :param dataset: + :type dataset: datacontainers.HolographyDataset + :param datatable: + :type table to reduce: + :param frequency: + :type frequency: float + :return: + """ + + rotated_coordinates = _rotate_antenna_coordinates(dataset) + n_antennas = len(dataset.antenna_field_position) + one_over_n_antennas = 1. / float(n_antennas) + n_beams = len(datatable) + + pointing_matrix = numpy.matrix(numpy.zeros((n_beams, n_antennas), dtype=complex)) + + for i in range(n_beams): + for j in range(n_antennas): + l = datatable[str(i)]['mean']['l'][0] + m = datatable[str(i)]['mean']['m'][0] + x, y = rotated_coordinates[j, 0:2] + phase = _compute_expected_phase_delay(l, m, x, y, frequency) + pointing_matrix[i, j] = numpy.exp(-1.j * phase) * one_over_n_antennas + + return pointing_matrix + + +def _solve_gains_complex(visibilities, matrix): + """ + To solve a complex linear system of equation it is possible to rewrite it in a equivalent + real system. + + [re1 + i + im1 , re2 + i + im2 ; [ re1, -im1, re2, -im2] + ~ [ im1, re1, im2, re2] + re3 + i + im3 , re4 + i + im4 ] [ re3, -im3, re4, -im4] + [ re3, re3, re$, -im4] + + :param matrix: + :param visibilities: + :return: + """ + matrix_real = numpy.matrix(numpy.zeros((matrix.shape[0]*2, matrix.shape[1]*2))) + matrix_real[0::2, 0::2] = matrix.real + matrix_real[0::2, 1::2] = -matrix.imag + matrix_real[1::2, 0::2] = matrix.real + matrix_real[1::2, 1::2] = matrix.imag + + visibilities_real = numpy.matrix(numpy.zeros(visibilities.shape[0] * 2)).T + visibilities_real[0::2] = visibilities.real + visibilities_real[1::2] = visibilities.imag + + result = _solve_gains(visibilities_real, matrix_real) + if result['flag'] == False: + noise = result['relative_error'] + + noise = numpy.sqrt(noise[0::2].A1**2. + noise[1::2].A1**2.) + result["relative_error"] = noise + return result + + +def _solve_gains(visibilities, matrix): + + try: + gains, residual, _, _ = numpy.linalg.lstsq(matrix, visibilities, rcond=None) + noise = abs(matrix * gains - visibilities) / abs(visibilities) + + return dict(gains=gains, residual=residual, relative_error=noise, + flag=False) + except Exception as e: + logger.warning('error solving for the gains: %s', e) + __empty = dict(gains=numpy.array(numpy.nan), + residual=numpy.array(numpy.nan), + relative_error=numpy.array(numpy.nan), + flag=numpy.array(True)) + return __empty + + +def _solve_gains_least_square(dataset, datatable, frequency, direct_complex=False): + """ + SOLVE THE EQUATION M * G = V FOR G + :param dataset: + :type dataset: datacontainers.HolographyDataset + :param datatable: + :param frequency: + :type frequency: float + :return: + """ + matrix = _compute_pointing_matrices_per_station_frequency_beam(dataset, datatable, frequency) + n_beams = len(datatable) + + result = dict() + for polarization in ['XX', 'XY', 'YX', 'YY']: + visibilities = numpy.matrix([datatable[str(i)]['mean'][polarization] for i in range(n_beams)]) + if direct_complex == True: + result[polarization] = _solve_gains(visibilities, matrix) + else: + result[polarization] = _solve_gains_complex(visibilities, matrix) + + return result + + +def solve_gains_per_datatable(dataset, datatable, **kwargs): + """ + + :param dataset: + :type dataset: datacontainers.HolographyDataset + :param datatable: + :type dataset: dict(dict(numpy.ndarray)) + :return: + """ + result = dict() + + for station in datatable: + result_per_station = dict() + result[station] = result_per_station + data_per_station = datatable[station] + for frequency in data_per_station: + data_per_frequency = data_per_station[frequency] + result_per_station[str(frequency)] = \ + _solve_gains_least_square(dataset, data_per_frequency, float(frequency), **kwargs) + + return result diff --git a/CAL/CalibrationProcessing/test/CMakeLists.txt b/CAL/CalibrationProcessing/test/CMakeLists.txt index e9e6ce8708ff4c0823725d09dc7040b96fd3dac0..7a639e742fde607a7c5f9c397986e1acec82bbd8 100644 --- a/CAL/CalibrationProcessing/test/CMakeLists.txt +++ b/CAL/CalibrationProcessing/test/CMakeLists.txt @@ -19,6 +19,11 @@ include(LofarCTest) -lofar_add_test(t_processing) +python_install( + __init__.py + utils.py + DESTINATION lofar/calibration/testing) + +add_subdirectory(processing) diff --git a/CAL/CalibrationProcessing/test/__init__.py b/CAL/CalibrationProcessing/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CAL/CalibrationProcessing/test/processing/CMakeLists.txt b/CAL/CalibrationProcessing/test/processing/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e9e6ce8708ff4c0823725d09dc7040b96fd3dac0 --- /dev/null +++ b/CAL/CalibrationProcessing/test/processing/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2018 ASTRON (Netherlands Institute for Radio Astronomy) +# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. + +# $Id$ +include(LofarCTest) + + +lofar_add_test(t_processing) + + diff --git a/CAL/CalibrationProcessing/test/t_processing.py b/CAL/CalibrationProcessing/test/processing/t_processing.py similarity index 64% rename from CAL/CalibrationProcessing/test/t_processing.py rename to CAL/CalibrationProcessing/test/processing/t_processing.py index 83c2f1bbd1e78dc529d626dd73337e99ee113dc7..3319852cf0c502e5971f3dd8c02905ae8fc3a973 100755 --- a/CAL/CalibrationProcessing/test/t_processing.py +++ b/CAL/CalibrationProcessing/test/processing/t_processing.py @@ -1,21 +1,72 @@ import logging import unittest +import os from lofar.calibration.common.datacontainers import HolographyDataset from lofar.calibration.processing import average_dataset_by_sample, average_values_by_sample from lofar.calibration.common.datacontainers.holography_dataset_definitions import HDS_data_sample_type -from lofar.calibration.processing.matrices import extract_crosscorrelation_matrices_from_HDS_datatable +from lofar.calibration.processing.normalize import __extract_crosscorrelation_matrices_from_data + +from lofar.calibration.testing.utils import create_holography_dataset +import lofar.calibration.testing.utils as utils logger = logging.getLogger('t_processing') import numpy +import matplotlib.pyplot as plt # READ doc/Holography_Data_Set.md! It contains the location from which the # test data must be downloaded and this file generated. SAMPLE_DATASET = '/data/test/HolographyObservation/CS001HBA0.hdf5' +def is_sample_dataset_available(): + return os.path.exists(SAMPLE_DATASET) + + +class TestSignalGenerator(unittest.TestCase): + def test_gaussian_noise_generation(self): + test_average_value = 5. + 12j + test_real_std = 12 + test_imag_std = 3 + test_sample_size = 100 + gaussian_generator = utils.TestComplexGaussianNoise(test_average_value, + test_real_std, + test_imag_std) + sample_noise = gaussian_generator.generate(test_sample_size, seed=5) + self.assertGreater(numpy.std(sample_noise.imag)/test_imag_std, .9) + self.assertGreater(numpy.std(sample_noise.imag)/test_imag_std, .9) + self.assertGreater(abs(numpy.average(sample_noise).real / test_average_value.real), .9) + self.assertGreater(abs(numpy.average(sample_noise).imag / test_average_value.imag), .9) + + def test_signal_generator(self): + test_imag_coeff = 3.j + test_real_coeff = 2. + expected_dependencies_imag_real = test_imag_coeff/test_real_coeff + linear_function = lambda x: 2 * x + 3.j * x + + test_average_value = 5. + 12j + test_real_std = 12 + test_imag_std = 3 + test_time_samples = numpy.linspace(0, 100, 1000).tolist() + gaussian_generator = utils.TestComplexGaussianNoise(test_average_value, + test_real_std, + test_imag_std) + + signal_generator = utils.TestSignal(linear_function, gaussian_generator) + sample_signal = signal_generator.generate(test_time_samples) + + coefficients = numpy.polyfit(sample_signal.real, sample_signal.imag, 1) + m, q = coefficients[0], coefficients[1] + self.assertGreater(abs(m/expected_dependencies_imag_real), .9 ) + class TestProcessing(unittest.TestCase): + sample_dataset = None def setUp(self): - pass + self.sample_dataset = create_holography_dataset() + + def test_sample_dataset(self): + for reference_station in self.sample_dataset.reference_stations: + self.assertIn(reference_station, self.sample_dataset.data) + def test_average_array_by_sample_real_values(self): """ @@ -51,6 +102,7 @@ class TestProcessing(unittest.TestCase): numpy.testing.assert_almost_equal(result['std_real'], expected_result_std.real) numpy.testing.assert_almost_equal(result['std_imag'], expected_result_std.real) + @unittest.skipIf(is_sample_dataset_available(), 'missing test dataset') def test_average_datatable_by_sample(self): holography_dataset = HolographyDataset.load_from_file(SAMPLE_DATASET) averaged_data = average_dataset_by_sample(SAMPLE_DATASET, 2).derived_data[ @@ -85,7 +137,7 @@ class TestProcessing(unittest.TestCase): test_data['YX'] = numpy.random.randn(3) + 1.j * numpy.random.randn(3) test_data['YY'] = numpy.random.randn(3) + 1.j * numpy.random.randn(3) - matrices_from_data = extract_crosscorrelation_matrices_from_HDS_datatable(test_data) + matrices_from_data = __extract_crosscorrelation_matrices_from_data(test_data) numpy.testing.assert_almost_equal(test_data['XX'], matrices_from_data[0, 0, :]) numpy.testing.assert_almost_equal(test_data['XY'], matrices_from_data[0, 1, :]) diff --git a/CAL/CalibrationProcessing/test/t_processing.run b/CAL/CalibrationProcessing/test/processing/t_processing.run similarity index 100% rename from CAL/CalibrationProcessing/test/t_processing.run rename to CAL/CalibrationProcessing/test/processing/t_processing.run diff --git a/CAL/CalibrationProcessing/test/t_processing.sh b/CAL/CalibrationProcessing/test/processing/t_processing.sh similarity index 100% rename from CAL/CalibrationProcessing/test/t_processing.sh rename to CAL/CalibrationProcessing/test/processing/t_processing.sh diff --git a/CAL/CalibrationProcessing/test/profiling_processing.py b/CAL/CalibrationProcessing/test/profiling_processing.py index 0ecbcf3989c3b5a40bb671b496b457b3b3f603ce..ea0bbaf9fa01bb181c9f7d0f69db94f712684b3e 100644 --- a/CAL/CalibrationProcessing/test/profiling_processing.py +++ b/CAL/CalibrationProcessing/test/profiling_processing.py @@ -54,6 +54,7 @@ class profile_averaging_per_sample(ProfilingTest): def process(self): processing.average_dataset_by_sample(SAMPLE_DATASET, 2) + if __name__ == '__main__': profile_holography_dataset_read().run().print_results() profile_averaging_per_sample().run().print_results() diff --git a/CAL/CalibrationProcessing/test/test_utils.py b/CAL/CalibrationProcessing/test/test_utils.py deleted file mode 100644 index ee96891cef17a8a7473da52f59de6df3b7bdafc6..0000000000000000000000000000000000000000 --- a/CAL/CalibrationProcessing/test/test_utils.py +++ /dev/null @@ -1,16 +0,0 @@ - -from lofar.calibration.common.datacontainers.holography_dataset import HolographyDataset - - -def create_holography_dataset(target_station='CS001', - reference_stations=('RS301', 'RS302'), - pointings=( - - ) - ): - """ - Creates a mock Holography Dataset that can be used for testing - :param target_station: - :param reference_stations: - :return: - """ \ No newline at end of file diff --git a/CAL/CalibrationProcessing/test/utils.py b/CAL/CalibrationProcessing/test/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..d0696d0fde2a9221ff18a0126626cbbc42e7fd9c --- /dev/null +++ b/CAL/CalibrationProcessing/test/utils.py @@ -0,0 +1,255 @@ +import numpy +from collections import defaultdict + +from lofar.calibration.common.datacontainers.holography_dataset import HolographyDataset +from lofar.calibration.common.datacontainers.holography_dataset_definitions import HDS_coordinate_type, HDS_data_sample_type + +DEFAULT_TARGET_STATION = 'CS001' +DEFAULT_TARGET_STATION_POSITION = numpy.array([0, 0, 0]) + +DEFAULT_REFERENCE_STATIONS = ('RS301', 'RS302') +DEFAULT_SOURCE_NAME = 'test source' +DEFAULT_SOURCE_POINTING = numpy.array([(1.5, 0.9, 'J2000')], HDS_coordinate_type) +DEFAULT_SAS_IDS = numpy.array([123456]) +DEFAULT_SOURCE_POSITION = numpy.array([3826896.19174 , 460979.502113, 5064658.231]) +DEFAULT_BEAMLETS = numpy.array([0, 1, 2, 3 , 4]) +DEFAULT_FREQUENCIES = [150000.0] +DEFAULT_POINTINGS = {str(DEFAULT_FREQUENCIES[0]): { + '0': numpy.array([(1.5, 0.9, 'J2000')], HDS_coordinate_type), + '1': numpy.array([(2.0, 0.9, 'J2000')], HDS_coordinate_type), + '2': numpy.array([(1.0, 0.9, 'J2000')], HDS_coordinate_type), + '3': numpy.array([(1.0, 0.8, 'J2000')], HDS_coordinate_type), + '4': numpy.array([(1.0, 1, 'J2000')], HDS_coordinate_type)}} + +DEFAULT_ANTENNA_FIELD_POSITION = numpy.array([[1, 0, 1], + [-1, 0, 1], + [+1, 0, 1]]) + +DEFAULT_ROTATION_MATRIX = numpy.array([[-1.19595e-01, 9.92823e-01, 3.30000e-05], + [-7.91954e-01, -9.54190e-02, 6.03078e-01], + [ 5.98753e-01, 7.20990e-02, 7.97682e-01]]) + +DEFAULT_MODE = 5 +DEFAULT_TIME_SAMPLE = [1, 2, 3, 4, 5, 6] + + +class TestComplexGaussianNoise(): + def __init__(self, average, std_real, std_imag): + """ + :param average: average value + :type average: complex + :param std_real: standard deviation for the real part + :type std_real: float + :param std_imag: standard deviation for the imaginary part + :type std_imag: float + """ + self.average = average + self.std_real = std_real + self.std_imag = std_imag + + def generate(self, sample_size, seed=None): + """ + Generate a series of random values + :param sample_size: + :type sample_size: int + :param seed: + :type seed: int + :return: + """ + if seed: + numpy.random.seed(seed) + + real = self.std_real * numpy.random.randn(sample_size) + imag = self.std_imag * numpy.random.randn(sample_size) * 1.j + + return real + imag + self.average + + +class TestSignal(): + def __init__(self, function, noise): + """ + + :param function: + :type function: function + :param noise: + :type noise: TestComplexGaussianNoise + """ + self.function = function + self.noise = noise + + def generate(self, timesamples, seed=None): + """ + + :param timesamples: list of time samples in which to compute the signal + :type timesamples: Iterable[float] + :param seed: seed for the gaussian signal generator + :type seed: None|int + :return: the generated sample signal + """ + n_samples = len(timesamples) + result = numpy.zeros(n_samples, dtype=complex) + for i, timesample in enumerate(timesamples): + result[i] = self.function(timesample) + result += self.noise.generate(n_samples, seed) + + return result + + +DEFAULT_AVERAGE_VALUES_PER_STATION_AND_POINTING = { + "RS301": { + '150000.0': { + '0': { + "mean": 1+5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + }, + '1': { + "mean": 1 + 5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + }, + '2': { + "mean": 1 + 5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + }, + '3': { + "mean": 1 + 5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + }, + '4': { + "mean": 1 + 5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + } + } + }, + "RS302": { + '150000.0': { + '0': { + "mean": 1+5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + }, + '1': { + "mean": 1 + 5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + }, + '2': { + "mean": 1 + 5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + }, + '3': { + "mean": 1 + 5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + }, + '4': { + "mean": 1 + 5j, + "std_imag": 1, + "std_real": 2, + "real_coeff": 0, + "imag_coeff": 0 + } + } + } +} + + +def recursive_default_dict_generator(): + return defaultdict(recursive_default_dict_generator) + + +def generate_single_pointing_dataset(timesamples, parameters): + """ + + :param timesamples: + :type timesamples: list + :return: + """ + dataset = numpy.zeros(len(timesamples), dtype=HDS_data_sample_type) + return dataset + + +def generate_sample_data(holography_dataset, + timesamples = DEFAULT_TIME_SAMPLE, + data_statistical_characteristics=DEFAULT_AVERAGE_VALUES_PER_STATION_AND_POINTING): + """ + Generates a sample data to start testing the processing step + :param holography_dataset: dataset prefilled with the header parameters + :type holography_dataset: HolographyDataset + :param timesamples: + + :return: + """ + holography_dataset.data = recursive_default_dict_generator() + + for reference_station in holography_dataset.reference_stations: + for frequency in holography_dataset.frequencies: + frequency_string = str(frequency) + for beamlet in holography_dataset.beamlets: + beamlet_string = str(beamlet) + holography_dataset.data[reference_station][frequency_string][beamlet_string] =\ + generate_single_pointing_dataset(timesamples, + data_statistical_characteristics[reference_station][frequency_string][beamlet_string]) + + + + +def create_holography_dataset(target_station_name=DEFAULT_TARGET_STATION, + target_station_position = DEFAULT_TARGET_STATION_POSITION, + reference_stations=DEFAULT_REFERENCE_STATIONS, + timesamples = DEFAULT_TIME_SAMPLE, + pointings=DEFAULT_POINTINGS, + sas_ids = DEFAULT_SAS_IDS, + source_name=DEFAULT_SOURCE_NAME, + source_position=DEFAULT_SOURCE_POINTING, + mode=DEFAULT_MODE, + beamlets=DEFAULT_BEAMLETS, + antenna_field_positions=DEFAULT_ANTENNA_FIELD_POSITION, + frequencies=DEFAULT_FREQUENCIES, + data_statistical_characteristics=DEFAULT_AVERAGE_VALUES_PER_STATION_AND_POINTING + ): + """ + Creates a mock Holography Dataset that can be used for testing + """ + dataset = HolographyDataset() + dataset.mode = mode + dataset.target_station_position = target_station_position + dataset.target_station_name = target_station_name + dataset.beamlets = beamlets + dataset.source_position = source_position + dataset.reference_stations = reference_stations + dataset.antenna_field_position = antenna_field_positions + dataset.source_name = source_name + dataset.sas_ids = sas_ids + dataset.ra_dec = pointings + dataset.central_beamlets = dataset.find_central_beamlets(source_position, + pointings, + frequencies, + beamlets) + dataset.frequencies = frequencies + + generate_sample_data(dataset, timesamples) + return dataset