def get_predicts(num: Union[int, list], bands: tuple,
                     predicted_values: list, results: dict) -> list:
        """
        Return the model prediction values in the time series for a particular band or bands

        Args:
            num:

        Returns:
            A list of segment models

        """
        # Check for type int, create list if true
        if isinstance(num, int):
            num = [num]

        try:
            _predicts = [
                predicted_values[m * len(bands) + n] for n in num
                for m in range(len(results["change_models"]))
            ]

        except (IndexError, TypeError) as e:
            log.error('Exception: %s' % e, exc_info=True)

            _predicts = []

        return _predicts
Beispiel #2
0
    def export_data(self):
        """
        Export the currently plotted data to an output CSV that will be saved in the specified working directory.

        """
        data = dict()

        for item in self.item_list:
            if item in aliases.keys():
                group = aliases[item]

                for key in group:
                    data[key] = self.ard_observations.pixel_ard[key]

        try:
            data['qa'] = self.ard_observations.pixel_ard['qas']

            data['dates'] = self.ard_observations.pixel_ard['dates']

        except KeyError as _e:
            log.error('Exception: %s' % _e, exc_info=True)

        data = pd.DataFrame(data).sort_values('dates').reset_index(drop=True)

        data['dates'] = data['dates'].apply(
            lambda x: dt.datetime.fromordinal(x))

        data.to_csv(
            os.path.join(
                self.working_directory,
                f'{get_time()}_{self.geo_info.pixel_coord_ul.x}_{self.geo_info.pixel_coord_ul.y}.csv'
            ))

        return None
Beispiel #3
0
    def show_model_params(self, results, geo):
        """
        Print the model results out to the GUI QPlainTextEdit widget

        Args:
            results: Class instance containing change model results and parameters
            geo: Class instance containing geographic info

        """
        log.info(
            "Plotting for tile H{:02}V{:02} at point ({}, {}) meters".format(
                geo.H, geo.V, geo.coord.x, geo.coord.y))

        try:
            self.ui.PlainTextEdit_results.appendPlainText(
                "\n\nBegin Date: {}".format(results.begin))
            log.info("Begin Date: {}".format(results.begin))

            self.ui.PlainTextEdit_results.appendPlainText(
                "End Date: {}\n".format(results.end))
            log.info("End Date: {}".format(results.end))

            for num, result in enumerate(results.results["change_models"]):
                self.ui.PlainTextEdit_results.appendPlainText(
                    "Result: {}".format(num + 1))
                log.info("Result: {}".format(num + 1))

                self.ui.PlainTextEdit_results.appendPlainText(
                    "Start Date: {}".format(
                        dt.datetime.fromordinal(result["start_day"])))
                log.info("Start Date: {}".format(
                    dt.datetime.fromordinal(result["start_day"])))

                self.ui.PlainTextEdit_results.appendPlainText(
                    "End Date: {}".format(
                        dt.datetime.fromordinal(result["end_day"])))
                log.info("End Date: {}".format(
                    dt.datetime.fromordinal(result["end_day"])))

                self.ui.PlainTextEdit_results.appendPlainText(
                    "Break Date: {}".format(
                        dt.datetime.fromordinal(result["break_day"])))
                log.info("Break Date: {}".format(
                    dt.datetime.fromordinal(result["break_day"])))

                self.ui.PlainTextEdit_results.appendPlainText("QA: {}".format(
                    result["curve_qa"]))
                log.info("QA: {}".format(result["curve_qa"]))

                self.ui.PlainTextEdit_results.appendPlainText(
                    "Change prob: {}\n".format(result["change_probability"]))
                log.info("Change prob: {}".format(
                    result["change_probability"]))

        except (ValueError, TypeError) as _e:
            log.error('Exception: %s' % _e, exc_info=True)

            pass
Beispiel #4
0
    def close_ard(self):
        """
        If plotting for a new HV tile, close the previous ARD Viewer window if one was previously opened

        Returns:
            None

        """
        try:
            self.ard.exit()

        except AttributeError as _e:
            log.error('Exception: %s' % _e, exc_info=True)
Beispiel #5
0
    def show_ard(self, clicked_item):
        """
        Display the ARD image clicked on the plot

        Args:
            clicked_item: <QListWidgetItem> Passed automatically by the itemClicked method of the QListWidget

        Returns:
            None
        """
        match = re.search(r'\d{4}-\D{3}-\d{2}', clicked_item.text()).group()

        date = dt.datetime.strptime(match, '%Y-%b-%d')

        try:
            if self.ard:

                # Update with the previously selected color channels so they are displayed in the new ARD viewer
                self.store_r = self.ard.r
                self.store_g = self.ard.g
                self.store_b = self.ard.b

                self.close_ard()

            self.ard = ChipsViewerX(x=self.geo_info.coord.x,
                                    y=self.geo_info.coord.y,
                                    date=date,
                                    url=self.merlin_url,
                                    subplot=self.plot_window.b,
                                    geo=self.geo_info,
                                    r=self.store_r,
                                    g=self.store_g,
                                    b=self.store_b,
                                    outdir=self.working_directory)

            self.ard.update_plot_signal.connect(self.update_plot)

        except (AttributeError, IndexError) as _e:
            log.error("Display ARD raised an exception: %s" % _e,
                      exc_info=True)
    def get_modeled_index(ard, results, predicted_values):
        """
        Calculate the model-predicted index curves

        Returns:

        """
        bands = ('blue', 'green', 'red', 'nir', 'swir1', 'swir2', 'thermal')
        indices = ('ndvi', 'msavi', 'evi', 'savi', 'ndmi', 'nbr', 'nbr2')

        modeled = dict()

        for key in ard.keys():
            if key in indices:
                new_key = f'{key}-modeled'

                modeled[new_key] = list()

                call = index_functions[key]['func']

                inds = index_functions[key]['inds']

                try:
                    for m in range(len(results['change_models'])):
                        args = tuple([
                            predicted_values[m * len(bands) + ind]
                            for ind in inds
                        ])

                        modeled[new_key].append(call(*args))

                except (AttributeError, TypeError) as e:
                    log.error('Exception: %s' % e, exc_info=True)

                    modeled[new_key].append([])

        return modeled
Beispiel #7
0
    def save_fig(self):
        """
        Save the current matplotlib figure to a PNG file

        """
        if not os.path.exists(self.ui.LineEdit_outputDir.text()):
            os.makedirs(self.ui.LineEdit_outputDir.text())

        fname = self.fname_generator()

        # Overwrite the .png if it already exists
        if os.path.exists(fname):
            try:
                os.remove(fname)

            except (IOError, PermissionError) as _e:
                log.error('Exception: %s' % _e, exc_info=True)

        # Make sure the timeseries plot is set as the current figure
        plt.figure(f'timeseries_figure_{self.fig_num}')

        plt.savefig(fname, bbox_inches="tight", dpi=150)

        log.debug("Plot figure saved to file {}".format(fname))
Beispiel #8
0
    def plot(self):
        """
        TODO: Add descriptive information

        """
        if self.plot_window:
            self.plot_window.close()

        self.fig_num += 1

        # <list> The bands and/or indices selected for plotting
        self.item_list = [
            str(i.text()) for i in self.ui.ListWidget_items.selectedItems()
        ]

        self.geo_info = GeoInfo(x=self.ui.LineEdit_x1.text(),
                                y=self.ui.LineEdit_y1.text(),
                                units=UNITS[self.selected_units]["unit"])

        self.cache_data = read_cache(self.geo_info, self.cache_data)

        self.ard_observations = ARDData(geo=self.geo_info,
                                        url=self.merlin_url,
                                        items=self.item_list,
                                        cache=self.cache_data)

        self.cache_data = update_cache(self.cache_data,
                                       self.ard_observations.cache,
                                       self.ard_observations.key)

        try:
            self.ccd_results = CCDReader(
                tile=self.geo_info.tile,
                chip_coord=self.geo_info.chip_coord_ul,
                pixel_coord=self.geo_info.pixel_coord_ul,
                json_dir=self.ccd_directory)

        except (IndexError, AttributeError, TypeError, ValueError,
                FileNotFoundError) as _e:
            log.error('Exception: %s' % _e, exc_info=True)

            self.ccd_results = None

        try:
            self.class_results = SegmentClasses(
                chip_coord_ul=self.geo_info.chip_coord_ul,
                class_dir=self.class_directory,
                rc=self.geo_info.chip_pixel_rowcol,
                tile=self.geo_info.tile)

        except (IndexError, AttributeError, TypeError, ValueError,
                FileNotFoundError) as _e:
            log.error('Exception: %s' % _e, exc_info=True)

            self.class_results = None

        self.plot_specs = PlotSpecs(ard=self.ard_observations.pixel_ard,
                                    change=self.ccd_results,
                                    segs=self.class_results,
                                    items=self.item_list,
                                    begin=self.begin,
                                    end=self.end)

        self.ui.PushButton_export.setEnabled(True)

        # Display change model information for the entered coordinates
        self.show_model_params(results=self.plot_specs, geo=self.geo_info)
        """
        fig <matplotlib.figure> Matplotlib figure object containing all of the artists
        
        artist_map <dict> mapping of each specific PathCollection artist to it's underlying dataset
        
        lines_map <dict> mapping of artist lines and points to the legend lines
        
        axes <ndarray> 2D array of matplotlib.axes.Axes objects
        
        """
        self.fig, self.artist_map, self.lines_map, self.axes = make_plots.draw_figure(
            data=self.plot_specs,
            items=self.item_list,
            fig_num=self.fig_num,
            config=self.plotconfig.opts)

        if not os.path.exists(self.ui.LineEdit_outputDir.text()):
            os.makedirs(self.ui.LineEdit_outputDir.text())

        # Generate the ESRI point shapefile
        temp_shp = self.fname_generator(ext=".shp")
        root, name = os.path.split(temp_shp)
        root = root + os.sep + "shp"

        self.get_shp(coords=self.geo_info.coord,
                     out_shp="{}{}{}".format(root, os.sep, name))

        # Show the figure in an interactive window
        self.plot_window = PlotWindow(fig=self.fig,
                                      axes=self.axes,
                                      artist_map=self.artist_map,
                                      lines_map=self.lines_map)

        self.plot_window.selected_obs.connect(self.connect_plot_selection)

        self.plot_window.change_symbology.connect(self.change_symbology)

        # Make these buttons available once a figure has been created
        self.ui.PushButton_clear.setEnabled(True)

        self.ui.PushButton_saveFigure.setEnabled(True)
Beispiel #9
0
import numpy as np
from osgeo import ogr, osr
from PyQt5.QtWidgets import QMainWindow, QFileDialog
from PyQt5 import QtCore

# Tell matplotlib to use the QT5Agg Backend
matplotlib.use('Qt5Agg')

sys.excepthook = exc_handler

try:
    CONFIG = yaml.load(
        open(pkg_resources.resource_filename('lcmap_tap', 'config.yaml')))

except FileNotFoundError as e:
    log.error('Exception: %s' % e, exc_info=True)

    log.critical(
        'Configuration file not present, TAP Tool will not be able to retrieve data.  Exiting'
    )

    sys.exit(1)


def get_time():
    """
    Return the current time stamp

    Returns:
        A formatted string containing the current date and time
Beispiel #10
0
    def save_img(self):
        """

        Returns:

        """
        date = self.date.strftime('%Y%m%d')

        r = self.ui.ComboBox_red.currentText().lower()
        g = self.ui.ComboBox_green.currentText().lower()
        b = self.ui.ComboBox_blue.currentText().lower()

        try:
            outdir = self.working_dir

            if r == b and r == g:
                outfile = os.path.join(outdir, f'{r}_{date}_{get_time()}.png')

            else:
                outfile = os.path.join(outdir,
                                       f'{r}_{g}_{b}_{date}_{get_time()}.png')

            fig, ax = plt.subplots(figsize=(10, 10),
                                   num=f'ard_figure_{self.fig_num}')

            # Make sure that the ARD figure is active
            plt.figure(f'ard_figure_{self.fig_num}')

            plt.axis('off')

            ax.grid(False)

            center = self.chips.tile_geo.chip_coord_ul

            _date = dt.datetime.fromordinal(
                self.chips.grid[center]['data'][0][1]['dates'][
                    self.chips.grid[center]['ind']]).strftime('%Y-%m-%d')

            title = f'Date: {_date}'

            text = f'X: {self.x}\nY: {self.y}'

            ax.set_title(title)

            ax.imshow(self.chips.rgb, interpolation='nearest')

            ax.scatter(x=self.col,
                       y=self.row,
                       marker='s',
                       facecolor='none',
                       color='yellow',
                       s=15,
                       linewidth=1)

            ax.text(0,
                    -.01,
                    text,
                    horizontalalignment='left',
                    verticalalignment='top',
                    transform=ax.transAxes)

            plt.savefig(outfile, bbox_inches='tight', dpi=200)

            log.debug("Plot figure saved to file {}".format(outfile))

            plt.gcf().clear()

            self.fig_num += 1

        except (TypeError, ValueError) as e:
            log.error('ARD save_img raised exception: %s' % e, exc_info=True)
def draw_figure(data: PlotSpecs, items: list, fig_num: int, config: dict) -> \
        Tuple[matplotlib.figure.Figure, dict, dict, ndarray]:
    """

    Args:
        data: an instance of the PlotSpecs class, contains all of the plotting attributes and data
        items: A list of strings representing the subplots to be plotted
        fig_num: Will be used as a figure identifier
        config: Plot symbology settings

    Returns:
        fig: A matplotlib.figure.Figure object
        artist_map: A dictionary mapping the data series to plot artists, used for referencing and interactivity
        lines_map: A dictionary mapping the legend lines to the plot artists they represent, used for interactivity
        axes: Using squeeze=False, returns a 2D numpy array of matplotlib.axes.Axes objects

    """
    def get_legend_handle(**kwargs):
        """
        A helper function to generate legend handles

        Args:
            **kwargs: Line2D keyword arguments

        Returns:

        """
        return mlines.Line2D([], [], **kwargs)

    plt.style.use('ggplot')

    leg_config = config['LEG_DEFAULTS']
    m_config = config['DEFAULTS']

    # get year values for labeling plots
    year1 = str(dt.datetime.fromordinal(data.dates[-1]))[:4]
    year2 = str(dt.datetime.fromordinal(data.dates[0]))[:4]

    # List of years in time series
    years = range(int(year1), int(year2) + 2, 1)

    # list of datetime objects with YYYY-MM-dd pattern using January 1 for month and day
    datetimes = [dt.datetime(yx, 1, 1) for yx in years]

    total_mask = data.qa_mask

    fill_out = data.fill_mask[~data.date_mask]

    if data.segment_classes is not None:
        class_results = dict()

        if not isinstance(data.segment_classes, list):
            data.segment_classes = [data.segment_classes]

        for ind, result in enumerate(data.segment_classes):
            if len(result['class_probs']) == 9:
                class_ind = np.argmax(result['class_probs'])

            else:
                # The older classification results have an additional class '0' so indices are off by 1
                class_ind = np.argmax(result['class_probs']) + 1

            class_label = NAMES[class_ind]

            if class_label not in class_results:
                class_results[class_label] = {
                    'starts': [result['start_day']],
                    'ends': [result['end_day']]
                }

            else:
                class_results[class_label]['starts'].append(
                    result['start_day'])
                class_results[class_label]['ends'].append(result['end_day'])

    else:
        class_results = None
    """
    plot_data is a dict whose keys are band names, index names, or a combination of both
    plot_data[key][0] contains the observed values
    plot_data[key][1] contains the model predicted values

    """
    plot_data = get_plot_items(data=data, items=items)
    """
    Create an empty dict to contain the mapping of data series to artists
    artist_map[key][0] contains the x-series
    artist_map[key][1] contains the y-series
    artist_map[key][2] contains the subplot name
    The keys that are subplot names will contain an empty point used for displaying which point is selected on the plot
    All other keys are the PathCollections returned by matplotlib.axes.Axes.scatter

    """
    artist_map = {}
    """
    Create an empty dict to contain the mapping of legend lines to plot artists
    
    """
    lines_map = {}
    """
    squeeze=False allows for plt.subplots to have a single subplot, must specify the column index as well
    when referencing a subplot because will always return a 2D array
    e.g. axes[num, 0] for subplot 'num'"""
    fig, axes = plt.subplots(nrows=len(plot_data),
                             ncols=1,
                             figsize=(18, len(plot_data) * 5),
                             dpi=65,
                             squeeze=False,
                             sharex='all',
                             sharey='none',
                             num=f'timeseries_figure_{fig_num}')
    """
    Define list objects that will contain the matplotlib artist objects within all subplots
    
    """
    end_lines = list()
    break_lines = list()
    start_lines = list()
    model_lines = list()
    date_lines = list()

    all_obs_points = list()
    all_out_points = list()
    all_mask_points = list()
    all_empty_points = list()

    class_lines = dict()
    class_handles = None

    for num, b in enumerate(plot_data.keys()):
        """
        Make lists to contain references to the specific artist objects for the current subplot.
        
        """
        obs_points = list()
        out_points = list()
        mask_points = list()
        empty_point = list()
        class_handles = list()

        # Give each subplot a title
        axes[num, 0].set_title('{}'.format(b))
        """ 
        Create an empty plot to use for displaying which point is clicked later on 
        
        """
        empty_point.append(axes[num, 0].plot([], [],
                                             **m_config['highlight_pick']))
        """ 
        Store a reference to the empty point which will be used to display clicked points on the plot
        
        """
        artist_map[b] = empty_point[0]

        all_empty_points.append(empty_point[0][0])
        """
        Plot the observed values within the PyCCD time range
        
        """
        obs = axes[num, 0].scatter(
            x=data.dates_in[total_mask[data.date_mask]],
            y=plot_data[b][0][data.date_mask][total_mask[data.date_mask]],
            **m_config['clear_obs'])

        obs_points.append(obs)

        all_obs_points.append(obs)
        """
        There's only ever one item in the *_points lists-a PathCollection artist-but it makes it easier to use with
        the 2D Lines because those are lists too.  See the plotwindow.py module.
        
        """
        artist_map[obs_points[0]] = [
            data.dates_in[total_mask[data.date_mask]],
            plot_data[b][0][data.date_mask][total_mask[data.date_mask]], b
        ]
        """
        Observed values outside of the PyCCD time range
        
        """
        out = axes[num,
                   0].scatter(x=data.dates_out[fill_out],
                              y=plot_data[b][0][~data.date_mask][fill_out],
                              **m_config['out_obs'])

        out_points.append(out)

        all_out_points.append(out)

        artist_map[out_points[0]] = [
            data.dates_out[fill_out],
            plot_data[b][0][~data.date_mask][fill_out], b
        ]
        """
        Plot the observed values masked out by PyCCD
        
        """
        mask = axes[num, 0].scatter(
            x=data.dates_in[~total_mask[data.date_mask]],
            y=plot_data[b][0][data.date_mask][~total_mask[data.date_mask]],
            **m_config['mask_obs'])

        mask_points.append(mask)

        all_mask_points.append(mask)

        artist_map[mask_points[0]] = [
            data.dates_in[~total_mask[data.date_mask]],
            plot_data[b][0][data.date_mask][~total_mask[data.date_mask]], b
        ]
        """
        plot the model start, end, and break dates
        
        """
        for ind, s in enumerate(data.start_dates):
            lines1 = axes[num, 0].axvline(s, **m_config['start_lines'])

            end_lines.append(lines1)

        for ind, e in enumerate(data.end_dates):
            lines3 = axes[num, 0].axvline(e, **m_config['end_lines'])

            start_lines.append(lines3)

        for ind, br in enumerate(data.break_dates):
            lines2 = axes[num, 0].axvline(br, **m_config['break_lines'])

            break_lines.append(lines2)
        """
        Draw the predicted curves
        
        """
        if data.results is not None:
            for c in range(0, len(data.results["change_models"])):
                lines5, = axes[num, 0].plot(
                    data.prediction_dates[c * len(data.bands)],
                    plot_data[b][1][c], **m_config['model_lines'])

                model_lines.append(lines5)
        """
        Draw horizontal color bars representing class assignments
        
        """
        if class_results is not None:
            for key in class_results.keys():
                if key not in class_lines:
                    class_lines[key] = list()

                for ind, item in enumerate(class_results[key]['starts']):
                    lines6 = axes[num, 0].hlines(
                        y=0,
                        xmin=item,
                        xmax=class_results[key]['ends'][ind],
                        linewidth=6,
                        colors=COLORS[key])

                    class_lines[key].append(lines6)

                class_handles.append(
                    get_legend_handle(linewidth=6,
                                      color=COLORS[key],
                                      label=key))
        """
        Set values for the y-axis limits
        
        """
        if b in data.index_lookup.keys():
            # Potential dynamic range values
            # ymin = min(plot_data[b][0][data.date_mask][total_mask]) - 0.15
            # ymax = max(plot_data[b][0][data.date_mask][total_mask]) + 0.1

            # Preferred static range values
            ymin = -1.01  # Band ratio or index min
            ymax = 1.01  # Band ratio or index max

        elif b == "Thermal":
            ymin = -2500
            ymax = 6500

        else:  # Spectral bands
            # Potential dynamic range values
            # ymin = min(plot_data[b][0][data.date_mask][total_mask]) - 700
            # ymax = max(plot_data[b][0][data.date_mask][total_mask]) + 500

            # Preferred static range values
            ymin = -100
            ymax = 6500

        # Set the y-axis limits
        axes[num, 0].set_ylim([ymin, ymax])
        """
        Display the x and y values where the cursor is placed on a subplot
        
        """
        axes[
            num,
            0].format_coord = lambda xcoord, ycoord: "({0:%Y-%m-%d}, ".format(
                dt.datetime.fromordinal(int(xcoord))) + "{0:f})".format(ycoord)
        """
        Plot a vertical line at January 1 of each year on the time series
        
        """
        for date in datetimes:
            lines7 = axes[num, 0].axvline(date,
                                          visible=False,
                                          **m_config['date_lines'])

            date_lines.append(lines7)

        # Set the axis face color
        axes[num, 0].patch.set_facecolor(m_config['background']['color'])

        # With sharex=True, set all x-axis tick labels to visible
        axes[num, 0].tick_params(axis='both',
                                 which='both',
                                 labelsize=12,
                                 labelbottom=True)
    """
    Create custom legend handles
    
    """
    empty_leg = get_legend_handle(**leg_config['highlight_pick'])

    obs_leg = get_legend_handle(**leg_config['clear_obs'])

    mask_leg = get_legend_handle(**leg_config['mask_obs'])

    out_leg = get_legend_handle(**leg_config['out_obs'])

    end_leg = get_legend_handle(**leg_config['end_lines'])

    break_leg = get_legend_handle(**leg_config['break_lines'])

    start_leg = get_legend_handle(**leg_config['start_lines'])

    model_leg = get_legend_handle(**leg_config['model_lines'])

    date_leg = get_legend_handle(**leg_config['date_lines'])

    handles = [
        empty_leg, obs_leg, mask_leg, out_leg, end_leg, break_leg, start_leg,
        model_leg, date_leg
    ]

    labels = [
        "Selected", "Clear", "Masked", "Unused", "End Date", "Break Date",
        "Start Date", "Model Fit", "Datelines"
    ]

    lines = [
        all_empty_points, all_obs_points, all_mask_points, all_out_points,
        end_lines, break_lines, start_lines, model_lines, date_lines
    ]
    """
    Add whichever land cover classes are present to the legend handles and labels
    
    """
    if len(class_handles) > 0:
        for c in class_handles:
            handles.append(c)

    if class_results is not None:
        for cl in class_results.keys():
            labels.append(cl)

    leg = axes[0, 0].legend(handles=handles,
                            labels=labels,
                            ncol=1,
                            loc="upper left",
                            bbox_to_anchor=(1.00, 1.00),
                            borderaxespad=0.)

    if len(class_lines) > 0:
        for key in class_lines.keys():
            lines.append(class_lines[key])

    for legline, origline in zip(leg.get_lines(), lines):
        # Set a tolerance of 5 pixels
        legline.set_picker(5)

        # Map the artist to the corresponding legend line
        lines_map[legline] = origline

    try:
        # Fill in the figure canvas
        fig.tight_layout()

    except ValueError as e:
        log.error('Exception: %s' % e, exc_info=True)

    # Make room for the legend
    fig.subplots_adjust(right=0.9)

    return fig, artist_map, lines_map, axes