Skip to content
Snippets Groups Projects
tasks.py 9.73 KiB
Newer Older
import matplotlib

matplotlib.use('agg')
import matplotlib.pyplot as plt
import numpy
from celery import shared_task
from django.conf import settings
from .models.rtsm import MODE_TO_COMPONENT, RTSMErrorSummary, RTSMSummaryPlot

logger = logging.getLogger(__name__)


class Metadata:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            self.__setattr__(key, value)


MODE_STR = {1: 'LBA Low 10..90 MHz',
            2: 'LBA Low 30..90',
            3: 'LBA High 10..90',
            4: 'LBA High 30..90',
            5: 'HBA 110..190',
            6: 'HBA 170..230',
            7: 'HBA 210..250'}


class ObservationMetadata(Metadata):
    station_name = 'CSXXXC'

    start_datetime = '1212-12-12 12:12:12'
    end_datetime = '1212-12-12 12:12:12'
    observation_id = 'XXXXXX'
    samples = 120

    @staticmethod
    def __format_datetime(datetime):
        return datetime.strftime("%Y-%m-%d %H:%M:%S")

    def set_start_time(self, start_time):
        self.start_datetime = ObservationMetadata.__format_datetime(start_time)
    def set_end_time(self, end_time):
        self.end_datetime = ObservationMetadata.__format_datetime(end_time)


def set_style():
    plt.minorticks_on()
    plt.ylim(55, 160)
    plt.grid()
    #    plt.rcParams['svg.fonttype'] = 'none'
    plt.rcParams['lines.linewidth'] = 1
    plt.rcParams['figure.autolayout'] = True
    plt.rcParams['axes.labelsize'] = 'large'
    plt.rcParams['path.simplify'] = True
    plt.rcParams['savefig.transparent'] = False
    plt.rcParams['savefig.bbox'] = 'tight'

    plt.rcParams['legend.loc'] = 'upper right'
    plt.rcParams['legend.frameon'] = True
    plt.rcParams['legend.framealpha'] = 1
    plt.rcParams['legend.edgecolor'] = 'black'


def rcu_mode_to_antenna(rcu, mode):
    component_name = MODE_TO_COMPONENT[mode]

    antenna = rcu // 2
    if component_name in ['LBH', 'HBA']:
        polarization = 'X' if rcu % 2 == 0 else 'Y'
    elif component_name == 'LBL':
        antenna = rcu // 2 + 48
        polarization = 'Y' if rcu % 2 == 0 else 'X'
    else:
        raise ValueError('Unknown component %s' % component_name)

    return dict(polarization=polarization, antenna=antenna)


def render_summary_text(error_metadata, observation_metadata):
    """
    Render the summary text to include in the picture
    :param error_metadata: the metadata information regarding the error
    :param observation_metadata: the metadata information regarding the observation
    :return:
    """
    rendered_text = 'Station Name: {:<25s}'.format(observation_metadata.station_name)
    rendered_text += 'Faults      : {}\n'.format(error_metadata.error_type)

    rendered_text += 'ObsID       : {:<25d}'.format(observation_metadata.observation_id)
    rendered_text += 'Antenna     : {antenna} {polarization}\n' \
        .format(**rcu_mode_to_antenna(error_metadata.rcu, error_metadata.mode))

    rendered_text += 'Start       : {:<25s}'.format(observation_metadata.start_datetime)
    rendered_text += 'RCU         : {}\n'.format(error_metadata.rcu)

    rendered_text += 'Stop        : {:<25s}'.format(observation_metadata.end_datetime)
    rendered_text += 'Samples     : {count:d}/{samples:d}\n'.format(count=error_metadata.count,
                                                                    samples=observation_metadata.samples)

    rendered_text += 'Mode        : {:<1d} {:<23s}'.format(error_metadata.mode,
                                                           MODE_STR[error_metadata.mode])
    rendered_text += 'Badness     : {:.2f} %\n'.format(
        (100. * error_metadata.count) / observation_metadata.samples)
    return rendered_text


def produce_plot(observation_metadata,
                 error_metadata,
                 list_bad_spectra, list_good_specta, path):
    """
    Produces a plot given the observation metadata the error summary metadata and
    the list of the bad spectra for the given (error, rcu) and the average spectrum
    of the rest of the array
    :param observation_metadata: the metadata information regarding the observation
    :param error_metadata: the metadata information regarding the error
    :param list_bad_spectra: a list containing the bad spectra for the given error, rcu couple
    :param list_good_specta: the average spectrum of the rest of the array
    :param path: the path where to store the file
    """
    plt.figure(figsize=(12, 9))
    set_style()

    plt.xlabel('MHz')
    plt.ylabel('dB')

    frequency_sampling = len(list_bad_spectra[0][0])
    n_spectra = len(list_bad_spectra)
    frequency = numpy.linspace(error_metadata.start_frequency,
                               error_metadata.stop_frequency,
                               frequency_sampling)
    bad_spectra_cube = numpy.zeros((n_spectra, frequency_sampling))
    average_spectra_cube = numpy.zeros((n_spectra, frequency_sampling))

    for i, spectrum in enumerate(list_bad_spectra):
        bad_spectra_cube[i, :] = spectrum[0]
    for i, spectrum in enumerate(list_good_specta):
        average_spectra_cube[i, :] = spectrum[0]

    min_bad_spectrum = numpy.min(bad_spectra_cube, axis=0)
    max_bad_spectrum = numpy.max(bad_spectra_cube, axis=0)
    average_bad_spectrum = numpy.median(bad_spectra_cube, axis=0)

    average_good_spectrum = numpy.median(average_spectra_cube, axis=0)

    plt.fill_between(frequency, min_bad_spectrum, max_bad_spectrum, color='red',
                     alpha=.3)

    plt.plot(frequency, average_bad_spectrum, color='red', label='median bad spectra')

    plt.plot(frequency, average_good_spectrum, color='blue', label='median all spectra')

    summary_text = render_summary_text(observation_metadata=observation_metadata,
                                       error_metadata=error_metadata)

    plt.text(0.02, .98, summary_text, bbox=dict(facecolor='yellow', alpha=0.2),
             family='monospace', verticalalignment='top',
             horizontalalignment='left',
             transform=plt.axes().transAxes)
    plt.legend()
    plt.savefig(path)
    plt.close()


def generate_summary_plot_for_error(error_summary_id):
    """
    Given a error summary id generates the plot in the specific location given by the settings
    parameter URL_TO_STORE_RTSM_PLOTS and the file name derived in the following fashion
    %(observation_id)d_%(station_name)s_rcu%(rcu)d_%(error_type)s.png

    Finally stores in the database the file name to directly access the file

    :param error_summary_id: database id of the error summary
    :return: the database id of the RTSMSummaryPlot instance
    :raise Exception: raise if there is an exception
    """
    basePath = settings.URL_TO_STORE_RTSM_PLOTS

    error_summary = RTSMErrorSummary.objects.get(pk=error_summary_id)
    summary_plot = error_summary.summary_plot.first()
    if summary_plot is not None:
        if summary_plot.uri == None:
            return 'another worker is taken care of the task...'

    summary_plot = RTSMSummaryPlot(error_summary=error_summary, uri=None)
    summary_plot.save()

    try:
        observation_metadata = ObservationMetadata()
        observation = error_summary.observation
        observation_metadata.observation_id = observation.observation_id
        observation_metadata.station_name = observation.station.name
        observation_metadata.set_start_time(observation.start_datetime)
        observation_metadata.set_end_time(observation.end_datetime)

        observation_metadata.samples = observation.samples
        errors = observation.errors.filter(mode=error_summary.mode,
                                           rcu=error_summary.rcu,
                                           error_type=error_summary.error_type)

        list_bad_spectra = errors.values_list('spectra__bad_spectrum')
        list_good_spectra = errors.values_list('spectra__average_spectrum')
        file_name = '%(observation_id)d_%(station_name)s_rcu%(rcu)d_%(error_type)s.png' % dict(
            observation_id=observation_metadata.observation_id,
            station_name=observation_metadata.station_name,
            rcu=error_summary.rcu,
            error_type=error_summary.error_type
        )

        full_path = os.path.join(basePath, file_name)
        produce_plot(observation_metadata,
                     error_summary,
                     list_bad_spectra, list_good_spectra,
                     full_path)
        summary_plot.uri = file_name
        summary_plot.save()
    except Exception as e:
        logger.exception('exception %s occurred skipping...', e)
        summary_plot.delete()
        raise e


@shared_task()
def check_error_summary_plot(error_summary_id):
    """
    Checks if the error summary plot is presents otherwise it generates one
    :param self: shared_task
    :type self: celery.shared_task()
    :param error_summary_id:  database id of the RTSMErrorSummary to check
    :return:
    """
    logger.debug('looking for rtsm error summary %s', error_summary_id)
    error_summary = RTSMErrorSummary.objects.get(pk=error_summary_id)
    summary_plot = error_summary.summary_plot.first()
    logger.debug('summary error found %s', summary_plot)

    if summary_plot is None:
        return generate_summary_plot_for_error(error_summary_id)
    else:
        base_path = settings.URL_TO_STORE_RTSM_PLOTS
        if summary_plot.uri is None:
            return 'another worker is taking care of the task...'
        full_path = os.path.join(base_path, summary_plot.uri)
        if not os.path.exists(full_path):
            return generate_summary_plot_for_error(error_summary_id)
        elif os.path.isdir(full_path):
            raise Exception('%s is a directory' % full_path)
        else:
            logger.debug('summary error %s is complete no need to generate additional plot',
                         summary_plot.pk)