| # |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| ''' Provides endpoints for running an OCW evaluation. ''' |
| |
| from datetime import timedelta, datetime |
| import inspect |
| import sys |
| import os |
| import json |
| |
| from bottle import Bottle, request, response |
| |
| from config import WORK_DIR |
| |
| import ocw.data_source.local as local |
| import ocw.data_source.rcmed as rcmed |
| import ocw.dataset_processor as dsp |
| from ocw.evaluation import Evaluation |
| from ocw.dataset import Bounds |
| import ocw.metrics as metrics |
| import ocw.plotter as plotter |
| |
| import numpy as np |
| |
| processing_app = Bottle() |
| |
| class EnableCors(object): |
| name = 'enable_cors' |
| api = 2 |
| |
| def apply(self, fn, context): |
| def _enable_cors(*args, **kwargs): |
| # set CORS headers |
| response.headers['Access-Control-Allow-Origin'] = '*' |
| response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS' |
| response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' |
| |
| if request.method != 'OPTIONS': |
| # actual request; reply with the actual response |
| return fn(*args, **kwargs) |
| |
| return _enable_cors |
| |
| processing_app.install(EnableCors()) |
| |
| @processing_app.route('/metrics/') |
| def retrieve_metrics(): |
| ''' Retrieve available metric names. |
| |
| **Example Return JSON Format** |
| |
| .. sourcecode:: javascript |
| |
| { |
| 'metrics': [ |
| 'MetricName1', |
| 'MetricName2', |
| ... |
| ] |
| } |
| ''' |
| valid_metrics = _get_valid_metric_options().keys() |
| output = json.dumps({'metrics': valid_metrics}) |
| response.content_type = 'application/json' |
| |
| if request.query.callback: |
| return '%s(%s)' % (request.query.callback, output) |
| return output |
| |
| @processing_app.route('/run_evaluation/', method=['POST', 'OPTIONS']) |
| def run_evaluation(): |
| ''' Run an OCW Evaluation. |
| |
| *run_evaluation* expects the Evaluation parameters to be POSTed in |
| the following format. |
| |
| .. sourcecode:: javascript |
| |
| { |
| reference_dataset: { |
| // Id that tells us how we need to load this dataset. |
| 'data_source_id': 1 == local, 2 == rcmed, |
| |
| // Dict of data_source specific identifying information. |
| // |
| // if data_source_id == 1 == local: |
| // { |
| // 'id': The path to the local file on the server for loading. |
| // 'var_name': The variable data to pull from the file. |
| // 'lat_name': The latitude variable name. |
| // 'lon_name': The longitude variable name. |
| // 'time_name': The time variable name |
| // 'name': Optional dataset name |
| // } |
| // |
| // if data_source_id == 2 == rcmed: |
| // { |
| // 'dataset_id': The dataset id to grab from RCMED. |
| // 'parameter_id': The variable id value used by RCMED. |
| // 'name': Optional dataset name |
| // } |
| 'dataset_info': {..} |
| }, |
| |
| // The list of target datasets to use in the Evaluation. The data |
| // format for the dataset objects should be the same as the |
| // reference_dataset above. |
| 'target_datasets': [{...}, {...}, ...], |
| |
| // All the datasets are re-binned to the reference dataset |
| // before being added to an experiment. This step (in degrees) |
| // is used when re-binning both the reference and target datasets. |
| 'spatial_rebin_lat_step': The lat degree step. Integer > 0, |
| |
| // Same as above, but for lon |
| 'spatial_rebin_lon_step': The lon degree step. Integer > 0, |
| |
| // The temporal resolution to use when doing a temporal re-bin |
| // This is a timedelta of days to use so daily == 1, monthly is |
| // (1, 31], annual/yearly is (31, 366], and full is anything > 366. |
| 'temporal_resolution': Integer in range(1, 999), |
| |
| // A list of the metric class names to use in the evaluation. The |
| // names must match the class name exactly. |
| 'metrics': [Bias, TemporalStdDev, ...] |
| |
| // The bounding values used in the Evaluation. Note that lat values |
| // should range from -180 to 180 and lon values from -90 to 90. |
| 'start_time': start time value in the format '%Y-%m-%d %H:%M:%S', |
| 'end_time': end time value in the format '%Y-%m-%d %H:%M:%S', |
| 'lat_min': The minimum latitude value, |
| 'lat_max': The maximum latitude value, |
| 'lon_min': The minimum longitude value, |
| 'lon_max': The maximum longitude value, |
| |
| // NOTE: At the moment, subregion support is fairly minimal. This |
| // will be addressed in the future. Ideally, the user should be able |
| // to load a file that they have locally. That would change the |
| // format that this data is passed. |
| 'subregion_information': Path to a subregion file on the server. |
| } |
| ''' |
| # TODO: validate input parameters and return an error if not valid |
| |
| eval_time_stamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') |
| data = request.json |
| |
| eval_bounds = { |
| 'start_time': datetime.strptime(data['start_time'], '%Y-%m-%d %H:%M:%S'), |
| 'end_time': datetime.strptime(data['end_time'], '%Y-%m-%d %H:%M:%S'), |
| 'lat_min': float(data['lat_min']), |
| 'lat_max': float(data['lat_max']), |
| 'lon_min': float(data['lon_min']), |
| 'lon_max': float(data['lon_max']) |
| } |
| |
| # Load all the datasets |
| ref_dataset = _process_dataset_object(data['reference_dataset'], eval_bounds) |
| |
| target_datasets = [_process_dataset_object(obj, eval_bounds) |
| for obj |
| in data['target_datasets']] |
| |
| # Normalize the dataset time values so they break on consistent days of the |
| # month or time of the day, depending on how they will be rebinned. |
| resolution = data['temporal_resolution'] |
| time_delta = timedelta(days=resolution) |
| |
| time_step = 'daily' if resolution == 1 else 'monthly' |
| ref_dataset = dsp.normalize_dataset_datetimes(ref_dataset, time_step) |
| target_datasets = [dsp.normalize_dataset_datetimes(ds, time_step) |
| for ds in target_datasets] |
| |
| # Subset the datasets |
| start = eval_bounds['start_time'] |
| end = eval_bounds['end_time'] |
| |
| # Normalize all the values to the first of the month if we're not |
| # dealing with daily data. This will ensure that a valid subregion |
| # isn't considered out of bounds do to a dataset's time values |
| # being shifted to the first of the month. |
| if time_step != 'daily': |
| if start.day != 1: |
| day_offset = start.day - 1 |
| start -= timedelta(days=day_offset) |
| |
| if end.day != 1: |
| day_offset = end.day - 1 |
| end -= timedelta(days=day_offset) |
| |
| subset = Bounds(eval_bounds['lat_min'], |
| eval_bounds['lat_max'], |
| eval_bounds['lon_min'], |
| eval_bounds['lon_max'], |
| start, |
| end) |
| |
| ref_dataset = dsp.safe_subset(subset, ref_dataset) |
| target_datasets = [dsp.safe_subset(subset, ds) |
| for ds |
| in target_datasets] |
| |
| # Do temporal re-bin based off of passed resolution |
| ref_dataset = dsp.temporal_rebin(ref_dataset, time_delta) |
| target_datasets = [dsp.temporal_rebin(ds, time_delta) |
| for ds |
| in target_datasets] |
| |
| # Do spatial re=bin based off of reference dataset + lat/lon steps |
| lat_step = data['spatial_rebin_lat_step'] |
| lon_step = data['spatial_rebin_lon_step'] |
| lat_bins, lon_bins = _calculate_new_latlon_bins(eval_bounds, |
| lat_step, |
| lon_step) |
| |
| ref_dataset = dsp.spatial_regrid(ref_dataset, lat_bins, lon_bins) |
| target_datasets = [dsp.spatial_regrid(ds, lat_bins, lon_bins) |
| for ds |
| in target_datasets] |
| |
| # Load metrics |
| loaded_metrics = _load_metrics(data['metrics']) |
| |
| # Prime evaluation object with data |
| evaluation = Evaluation(ref_dataset, target_datasets, loaded_metrics) |
| |
| # Run evaluation |
| evaluation.run() |
| |
| # Plot |
| _generate_evaluation_plots(evaluation, lat_bins, lon_bins, eval_time_stamp) |
| |
| return json.dumps({'eval_work_dir': eval_time_stamp}) |
| |
| def _process_dataset_object(dataset_object, eval_bounds): |
| ''' Convert an dataset object representation into an OCW Dataset |
| |
| The dataset_object must contain two pieces of information. The |
| `data_source_id` tells how to load the dataset, and the `dataset_info` |
| contains all the information necessary for the load. |
| |
| .. sourcecode: javascript |
| |
| // Id that tells us how we need to load this dataset. |
| 'data_source_id': 1 == local, 2 == rcmed, |
| |
| // Dict of data_source specific identifying information. |
| // |
| // if data_source_id == 1 == local: |
| // { |
| // 'id': The path to the local file on the server for loading. |
| // 'var_name': The variable data to pull from the file. |
| // 'lat_name': The latitude variable name. |
| // 'lon_name': The longitude variable name. |
| // 'time_name': The time variable name |
| // 'name': Optional dataset name |
| // } |
| // |
| // if data_source_id == 2 == rcmed: |
| // { |
| // 'dataset_id': The dataset id to grab from RCMED. |
| // 'parameter_id': The variable id value used by RCMED. |
| // 'name': Optional dataset name |
| // } |
| 'dataset_info': {..} |
| |
| :param dataset_object: Dataset information of the above form to be |
| loaded into an OCW Dataset object. |
| :type dataset_object: Dictionary |
| :param eval_bounds: The evaluation bounds for this Evaluation. These |
| are needed to load RCMED datasets. |
| :type eval_bounds: Dictionary |
| |
| :returns: dataset_object converted to an ocw.Dataset |
| |
| :raises KeyError: If dataset_object is malformed and doesn't contain the |
| keys `data_source_id` or `dataset_info`. |
| :raises ValueError: If the data_source_id isn't valid. |
| |
| ''' |
| source_id = int(dataset_object['data_source_id']) |
| dataset_info = dataset_object['dataset_info'] |
| |
| # If we should load with local |
| if source_id == 1: |
| return _load_local_dataset_object(dataset_info) |
| # If we should load with RCMED |
| elif source_id == 2: |
| return _load_rcmed_dataset_object(dataset_info, eval_bounds) |
| else: |
| cur_frame = sys._getframe().f_code |
| err = "{}.{}: Invalid data_source_id - {}".format( |
| cur_frame.co_filename, |
| cur_frame.co_name, |
| source_id |
| ) |
| raise ValueError(err) |
| |
| def _load_local_dataset_object(dataset_info): |
| ''' Create an ocw.dataset.Dataset object from supplied data. |
| |
| .. note: At the moment, data_source.local cannot take advantage of all the |
| supplied variable names. This functionality will be added in the future. |
| However, in the mean time, it is expected that the dataset_info object |
| still contain all the appropriate keys. |
| |
| :param dataset_info: The necessary data to load a local dataset with |
| ocw.data_source.local. Must be of the form: |
| { |
| 'dataset_id': The path to the local file for loading, |
| 'var_name': The variable data to pull from the file, |
| 'lat_name': The latitude variable name, |
| 'lon_name': The longitude variable name, |
| 'time_name': The time variable name |
| 'name': Optional dataset name |
| } |
| :type dataset_info: Dictionary |
| |
| :returns: An ocw.dataset.Dataset object containing the requested information. |
| |
| :raises KeyError: If the required keys aren't present in the dataset_info. |
| :raises ValueError: If data_source.local could not load the requested file. |
| ''' |
| path = dataset_info['dataset_id'] |
| var_name = dataset_info['var_name'] |
| lat_name = dataset_info['lat_name'] |
| lon_name = dataset_info['lon_name'] |
| time_name = dataset_info['time_name'] |
| # If a name is passed for the dataset, use it. Otherwise, use the file name. |
| name = (dataset_info['name'] |
| if 'name' in dataset_info.keys() |
| else path.split('/')[-1]) |
| |
| dataset = local.load_file(path, var_name) |
| dataset.name = name |
| return dataset |
| |
| def _load_rcmed_dataset_object(dataset_info, eval_bounds): |
| ''' Create an ocw.dataset.Dataset object from supplied data. |
| |
| :param dataset_info: The necessary data to load a RCMED dataset with |
| ocw.data_source.rcmed. Must be of the form: |
| { |
| 'dataset_id': The dataset id to grab from RCMED. |
| 'parameter_id': The variable id value used by RCMED. |
| 'name': Optional dataset name |
| } |
| :type dataset_info: Dictionary |
| |
| :param eval_bounds: The time, lat, and lon bounds values for this Evaluation. |
| Must be of the form: |
| { |
| 'start_time': request.query.start_time, |
| 'end_time': request.query.end_time, |
| 'lat_min': request.query.lat_min, |
| 'lat_max': request.query.lat_max, |
| 'lon_min': request.query.lon_min, |
| 'lon_max': request.query.lon_max |
| } |
| ;type eval_bounds: Dictionary |
| |
| :returns: An ocw.dataset.Dataset object containing the requested information. |
| |
| :raises KeyError: If the required keys aren't present in the dataset_info or |
| eval_bounds objects. |
| ''' |
| dataset = rcmed.parameter_dataset(int(dataset_info['dataset_id']), |
| int(dataset_info['parameter_id']), |
| eval_bounds['lat_min'], |
| eval_bounds['lat_max'], |
| eval_bounds['lon_min'], |
| eval_bounds['lon_max'], |
| eval_bounds['start_time'], |
| eval_bounds['end_time']) |
| |
| # If a name is passed for the dataset, use it. Otherwise, use the file name. |
| if 'name'in dataset_info.keys(): |
| name = dataset_info['name'] |
| else: |
| for m in rcmed.get_parameters_metadata(): |
| if m['parameter_id'] == str(dataset_info['parameter_id']): |
| name = m['longname'] |
| break |
| else: |
| # If we can't find a name for the dataset, default to something... |
| name = "RCMED dataset" |
| |
| dataset.name = name |
| |
| return dataset |
| |
| def _calculate_new_latlon_bins(eval_bounds, lat_grid_step, lon_grid_step): |
| ''' Calculate the new lat/lon ranges for spatial re-binning. |
| |
| :param eval_bounds: The time and lat/lon bounds for the evaluation. |
| Must be of the form: |
| { |
| 'lat_min': request.query.lat_min, |
| 'lat_max': request.query.lat_max, |
| 'lon_min': request.query.lon_min, |
| 'lon_max': request.query.lon_max |
| } |
| :type eval_bounds: Dictionary |
| :param lat_grid_step: The degree step between successive latitude values |
| in the newly created bins. |
| :type lat_grid_step: Integer > 0 |
| :param lon_grid_step: The degree step between successive longitude values |
| in the newly created bins. |
| :type lat_grid_step: Integer > 0 |
| |
| :returns: The new lat/lon value lists as a tuple of the form (new_lats, new_lons) |
| ''' |
| new_lats = np.arange(eval_bounds['lat_min'], |
| eval_bounds['lat_max'], |
| lat_grid_step) |
| new_lons = np.arange(eval_bounds['lon_min'], |
| eval_bounds['lon_max'], |
| lon_grid_step) |
| return (new_lats, new_lons) |
| |
| def _load_metrics(metric_names): |
| ''' Load and create an instance of each requested metric. |
| |
| :param metric_names: The names of the metrics that should be loaded and |
| instantiated from ocw.metrics for use in an evaluation. |
| :type metric_names: List |
| |
| :returns: A List of Metric objects |
| |
| :raises ValueError: If a metric name cannot be matched. |
| ''' |
| instantiated_metrics = [] |
| metrics_map = _get_valid_metric_options() |
| possible_metrics = metrics_map.keys() |
| |
| for metric in metric_names: |
| if metric not in possible_metrics: |
| cur_frame = sys._getframe().f_code |
| err = "{}.{}: Invalid metric name - {}".format( |
| cur_frame.co_filename, |
| cur_frame.co_name, |
| metric |
| ) |
| raise ValueError(err) |
| |
| instantiated_metrics.append(metrics_map[metric]()) |
| |
| return instantiated_metrics |
| |
| def _get_valid_metric_options(): |
| ''' Get valid metric options from the ocw.metrics module. |
| |
| :returns: A dictionary of metric (name, object) pairs |
| ''' |
| invalid_metrics = ['ABCMeta', 'Metric', 'UnaryMetric', 'BinaryMetric'] |
| |
| # Consider all Unary Metrics invalid. At the moment, the UI cannot handle |
| # running Unary Metrics. |
| unary_metrics = [cls.__name__ for cls in metrics.UnaryMetric.__subclasses__()] |
| invalid_metrics += unary_metrics |
| |
| return {name:obj |
| for name, obj in inspect.getmembers(metrics) |
| if inspect.isclass(obj) and name not in invalid_metrics} |
| |
| def _generate_evaluation_plots(evaluation, lat_bins, lon_bins, eval_time_stamp): |
| ''' Generate the Evaluation's plots |
| |
| .. note: This doesn't support graphing evaluations with subregion data. |
| |
| :param evaluation: A run Evaluation for which to generate plots. |
| :type evaluation: ocw.evaluation.Evaluation |
| :param lat_bins: The latitude bin values used in the evaluation. |
| :type lat_bins: List |
| :param lon_bins: The longitude bin values used in the evaluation. |
| :type lon_bins: List |
| :param eval_time_stamp: The time stamp for the directory where |
| evaluation results should be saved. |
| :type eval_time_stamp: Time stamp of the form '%Y-%m-%d_%H-%M-%S' |
| |
| :raises ValueError: If there aren't any results to graph. |
| ''' |
| # Create time stamp version-ed WORK_DIR for plotting |
| eval_path = os.path.join(WORK_DIR, eval_time_stamp) |
| os.makedirs(eval_path) |
| |
| # TODO: Should be able to check for None here... |
| if evaluation.results == [] and evaluation.unary_results == []: |
| cur_frame = sys._getframe().f_code |
| err = "{}.{}: No results to graph".format(cur_frame.co_filename, |
| cur_frame.co_name) |
| raise ValueError(err) |
| |
| if evaluation.ref_dataset: |
| grid_shape_dataset = evaluation.ref_dataset |
| else: |
| grid_shape_dataset = evaluation.target_datasets[0] |
| |
| grid_shape = _calculate_grid_shape(grid_shape_dataset) |
| |
| if evaluation.results != []: |
| for dataset_index, dataset in enumerate(evaluation.target_datasets): |
| for metric_index, metric in enumerate(evaluation.metrics): |
| results = evaluation.results[dataset_index][metric_index] |
| file_name = _generate_binary_eval_plot_file_path(evaluation, |
| dataset_index, |
| metric_index, |
| eval_time_stamp) |
| plot_title = _generate_binary_eval_plot_title(evaluation, |
| dataset_index, |
| metric_index) |
| plotter.draw_contour_map(results, |
| lat_bins, |
| lon_bins, |
| fname=file_name, |
| ptitle=plot_title, |
| gridshape=grid_shape) |
| |
| if evaluation.unary_results != []: |
| for metric_index, metric in enumerate(evaluation.unary_metrics): |
| cur_unary_results = evaluation.unary_results[metric_index] |
| for result_index, result in enumerate(cur_unary_results): |
| file_name = _generate_unary_eval_plot_file_path(evaluation, |
| result_index, |
| metric_index, |
| eval_time_stamp) |
| plot_title = _generate_unary_eval_plot_title(evaluation, |
| result_index, |
| metric_index) |
| |
| plotter.draw_contrough_map(results, |
| lat_bins, |
| lon_bins, |
| fname=file_name, |
| ptitle=plot_title, |
| gridshape=grid_shape) |
| |
| def _calculate_grid_shape(reference_dataset, max_cols=6): |
| ''' Calculate the plot grid shape given a reference dataset. |
| |
| :param reference_dataset: The dataset from which to strip out temporal |
| bin information and calculate grid shape. |
| :type reference_dataset: ocw.dataset.Dataset |
| :param max_cols: The maximum number of columns in the calculated grid shape. |
| Note that the calculated shape with always have max_cols as its column |
| count. |
| :type max_cols: Integer > 0 |
| |
| :returns: The grid shape to use as (num_rows, num_cols) |
| ''' |
| total_temporal_bins = reference_dataset.values.shape[0] |
| temporal_bins = total_temporal_bins |
| |
| num_rows = 1 |
| while temporal_bins > max_cols: |
| temporal_bins -= max_cols |
| num_rows += 1 |
| |
| return _balance_grid_shape(total_temporal_bins, num_rows, max_cols) |
| #return (num_rows, max_cols) |
| |
| def _balance_grid_shape(total_temporal_bins, num_rows, num_cols): |
| ''' Balance grid shape values to prevent large row/col discrepancies. |
| |
| Often times _calculate_grid_shape will result in values where there is a |
| large difference between row/column count. This tries to balance out the |
| shape so that it is as square as possible. |
| |
| :param total_temporal_bins: The total number of bins that the shape must |
| fit. |
| :type total_temporal_bins: Integer >= 1 |
| :params num_rows: The number of rows. |
| :type num_rows: Integer >= 1 |
| :params num_cols: The number of columns. |
| :type num_cols: Integer >= 1 |
| |
| :returns: The adjusted shape values so the difference between the number |
| of rows and the number of columns <= 1. |
| ''' |
| while True: |
| if abs(num_rows - num_cols) <= 1: |
| # We might be able to reduce both. |
| if total_temporal_bins < (num_rows - 1) * (num_cols - 1): |
| num_rows -= 1 |
| num_cols -= 1 |
| # If not, then we're nearly done. |
| else: |
| # We might end up with a grid that is slightly too large. We |
| # tend to favor larger column numbers rather than rows, so we'll |
| # try to drop another column to get a tighter grid. |
| if total_temporal_bins <= num_rows * (num_cols - 1): |
| num_cols -= 1 |
| |
| # Favor more columns or more rows in the final layout. |
| if num_rows > num_cols: |
| num_rows, num_cols = num_cols, num_rows |
| |
| break |
| else: |
| if num_rows > num_cols: |
| # When we have a delta >= 2, first we try to drop just one of the values. |
| if total_temporal_bins < (num_rows - 1) * num_cols: |
| num_rows -= 1 |
| # In certain cases we can't just drop a value yet we still have a delta |
| # that is >= 2. In that situation we need to trade a value between them. |
| elif total_temporal_bins < (num_rows - 1) * (num_cols + 1): |
| num_rows -= 1 |
| num_cols += 1 |
| else: |
| if total_temporal_bins < num_rows * (num_cols - 1): |
| num_cols -= 1 |
| elif total_temporal_bins < (num_rows + 1) * (num_cols - 1): |
| num_rows += 1 |
| num_cols -= 1 |
| |
| return (int(num_rows), int(num_cols)) |
| |
| def _generate_binary_eval_plot_file_path(evaluation, dataset_index, |
| metric_index, time_stamp): |
| ''' Generate a plot path for a given binary metric run. |
| |
| :param evaluation: The Evaluation object from which to pull name information. |
| :type evaluation: ocw.evaluation.Evaluation |
| :param dataset_index: The index of the target dataset to use when |
| generating the name. |
| :type dataset_index: Integer >= 0 < len(evaluation.target_datasets) |
| :param metric_index: The index of the metric to use when generating the name. |
| :type metric_index: Integer >= 0 < len(evaluation.metrics) |
| |
| :returns: The full path for the requested metric run. The paths will always |
| be placed in the WORK_DIR set for the web services. |
| ''' |
| plot_name = "{}_compared_to_{}_{}".format( |
| evaluation.ref_dataset.name.lower(), |
| evaluation.target_datasets[dataset_index].name.lower(), |
| evaluation.metrics[metric_index].__class__.__name__.lower() |
| ) |
| |
| timestamped_workdir = os.path.join(WORK_DIR, time_stamp) |
| return os.path.join(timestamped_workdir, plot_name) |
| |
| def _generate_unary_eval_plot_file_path(evaluation, dataset_index, |
| metric_index, time_stamp): |
| ''' Generate a plot path for a given unary metric run. |
| |
| :param evaluation: The Evaluation object from which to pull name information. |
| :type evaluation: ocw.evaluation.Evaluation |
| :param dataset_index: The index of the target dataset to use when |
| generating the name. |
| :type dataset_index: Integer >= 0 < len(evaluation.target_datasets) |
| :param metric_index: The index of the metric to use when generating the name. |
| :type metric_index: Integer >= 0 < len(evaluation.metrics) |
| |
| :returns: The full path for the requested metric run. The paths will always |
| be placed in the WORK_DIR set for the web services. |
| ''' |
| metric = evaluation.unary_metrics[metric_index] |
| timestamped_workdir = os.path.join(WORK_DIR, time_stamp) |
| |
| # Unary metrics can be run over both the reference dataset and the target |
| # datasets. It's possible for an evaluation to only have one and not the |
| # other. If there is a reference dataset then the 0th result index refers to |
| # the result of the metric being run on the reference dataset. Any future |
| # indexes into the target dataset list must then be offset by one. If |
| # there's no reference dataset then we don't have to bother with any of this. |
| if evaluation.ref_dataset: |
| if dataset_index == 0: |
| plot_name = "{}_{}".format( |
| evaluation.ref_dataset.name.lower(), |
| metric.__class__.__name__.lower() |
| ) |
| |
| return os.path.join(timestamped_workdir, plot_name) |
| else: |
| dataset_index -= 1 |
| |
| plot_name = "{}_{}".format( |
| evaluation.target_datasets[dataset_index].name.lower(), |
| metric.__class__.__name__.lower() |
| ) |
| |
| return os.path.join(timestamped_workdir, plot_name) |
| |
| def _generate_binary_eval_plot_title(evaluation, dataset_index, metric_index): |
| ''' Generate a plot title for a given binary metric run. |
| |
| :param evaluation: The Evaluation object from which to pull name information. |
| :type evaluation: ocw.evaluation.Evaluation |
| :param dataset_index: The index of the target dataset to use when |
| generating the name. |
| :type dataset_index: Integer >= 0 < len(evaluation.target_datasets) |
| :param metric_index: The index of the metric to use when generating the name. |
| :type metric_index: Integer >= 0 < len(evaluation.metrics) |
| |
| :returns: The plot title for the requested metric run. |
| ''' |
| return "{} of {} compared to {}".format( |
| evaluation.metrics[metric_index].__class__.__name__, |
| evaluation.ref_dataset.name, |
| evaluation.target_datasets[dataset_index].name |
| ) |
| |
| def _generate_unary_eval_plot_title(evaluation, dataset_index, metric_index): |
| ''' Generate a plot title for a given unary metric run. |
| |
| :param evaluation: The Evaluation object from which to pull name information. |
| :type evaluation: ocw.evaluation.Evaluation |
| :param dataset_index: The index of the target dataset to use when |
| generating the name. |
| :type dataset_index: Integer >= 0 < len(evaluation.target_datasets) |
| :param metric_index: The index of the metric to use when generating the name. |
| :type metric_index: Integer >= 0 < len(evaluation.metrics) |
| |
| :returns: The plot title for the requested metric run. |
| ''' |
| |
| # Unary metrics can be run over both the reference dataset and the target |
| # datasets. It's possible for an evaluation to only have one and not the |
| # other. If there is a reference dataset then the 0th result index refers to |
| # the result of the metric being run on the reference dataset. Any future |
| # indexes into the target dataset list must then be offset by one. If |
| # there's no reference dataset then we don't have to bother with any of this. |
| if evaluation.ref_dataset: |
| if dataset_index == 0: |
| return "{} of {}".format( |
| evaluation.unary_metrics[metric_index].__class__.__name__, |
| evaluation.ref_dataset.name |
| ) |
| else: |
| dataset_index -= 1 |
| |
| return "{} of {}".format( |
| evaluation.unary_metrics[metric_index].__class__.__name__, |
| evaluation.target_datasets[dataset_index].name |
| ) |