Pickle базовая карта фигура - PullRequest
       29

Pickle базовая карта фигура

0 голосов
/ 12 февраля 2020

Я пытаюсь сохранить basemap object для повторного использования, используя pickle.

У меня есть более 100 фигур для создания, и basemap занимает много времени.

Я следил за предыдущими постами по аналогичной проблеме, Сохранение фигур Matplotlib с использованием Pickle и Pickle: сохранение + загрузка рисунков MPL в файл .

Но всякий раз, когда я пробую свой собственный код, после сохранения pickle obj я получаю RuntimeError: Can not put single artist in more than one figure.

Ниже приведен фрагмент моей функции построения графиков

import os
import time

from dateutil.parser import parse
import numpy as np
import pickle

class ImageSaving:
    __slots__ = 'label', 'unit', 'clim', 'cmp', 'data', 'date', 'filename', 'lat', 'log10', \
                'lon', 'nColours', 'var', 'title', 'contour', 'points', 'levels', 'cbar'

    def __init__(self, filename: str, var: str, data: np.ma.array = None, lon: np.array = None, cbar: bool = False,
                lat: np.array = None, log10: bool = False, title: str = None, date=None, cmp: str = None, 
                clim: list = None, label: str = None, contour: bool = True, points: np.array = None):
        for slot in self.__slots__:
            setattr(self, slot, None)
        self.log10 = log10
        self.data = data
        self.lon = lon
        self.lat = lat
        self.date = date
        self.cmp = cmp
        self.cbar = cbar
        self.clim = clim
        self.label = label
        self.contour = contour
        self.points = points
        self.var = var
        self.title = title
        self.filename = filename

    def _colourbar_sets(self):

        if self.cmp is None:
            self.cmp = 'jet'

        self.nColours = 150
        if 'temp' in self.var.lower():
            # Colour bar limits and number of colours in the cb
            if self.clim is None:
                self.clim = -2, 30
            # cb labels
            if self.label is None:
                self.label = 'Temperature'
            self.unit = u'\u00B0C'
            # cb tick labels

        elif 'chl' in self.var:
            # Colour bar limits and number of colours in the cb
            if self.clim is None:
                self.clim = 0.09, 100

            # cb labels
            if self.label is None:
                self.label = 'Chlorophyll-{} Concentration'.format(r'$\it{a}$')
            self.unit = 'mg m$^{-3}$'

        elif 'sal' in self.var:
            # Colour bar limits and number of colours in the cb
            if self.clim is None:
                self.clim = 25, 34

            # cb labels
            if self.label is None:
                self.label = 'Salinity'
            self.unit = 'psu'


    def save_fig(self):
        import matplotlib
        matplotlib.use('Agg')

        from matplotlib import (pyplot as plot, colors)
        from mpl_toolkits import basemap as bsm
        from mpl_toolkits.axes_grid1 import make_axes_locatable

        self._colourbar_sets()
        cb_location, cb_orientation = "right", 'vertical'
        y0, y1 = 36.7, 37.12
        x0, x1 = 136.9, 137.55

        font_size, lw, pad = 20, 1, 0.05
        lw *= 4
        pad *= 3
        font_size *= 3
        plot.rcParams.update({'font.size': font_size + 4})
        plot.rcParams['axes.linewidth'] = lw
        plot.rcParams['xtick.major.size'] = 8
        plot.rcParams['xtick.major.width'] = lw
        plot.rcParams['ytick.major.size'] = 8
        plot.rcParams['ytick.major.width'] = lw
        plot.rcParams['axes.titlepad'] = 20

        # fig = plot.figure(figsize=(26, 25), dpi=50)
        fig, ax = plot.subplots(1, 1, figsize=(26, 25), dpi=50)

        t = time.process_time()
        basename = os.path.basename(self.filename)
        lat_ts = round((self.lat.min() + self.lat.max()) / 2)
        parallels = np.arange(np.floor(y0), np.ceil(y1) + 1, 1)
        meridians = np.arange(np.floor(x0), np.ceil(x1), 1)

        bsm_pickle = 'bsm_pickle'
        if os.path.isfile(bsm_pickle):
            with open(bsm_pickle, 'rb') as bmp:
                m = pickle.load(bmp)

            m.ax = ax
            m.drawcoastlines(linewidth=1, ax=ax)
            m.drawparallels(parallels, labels=[True, False, False, False], fontsize=font_size, linewidth=lw)
            m.drawmeridians(meridians, labels=[0, 0, 0, 1], fontsize=font_size, linewidth=lw)
            m.fillcontinents()  # Fill the continents
            m.drawmapboundary(fill_color='black', linewidth=lw)  # Fill the globe with a blue color

        else:
            m = bsm.Basemap(projection='merc', lat_ts=lat_ts, resolution='f', llcrnrlat=y0,
                            urcrnrlat=y1, llcrnrlon=x0, urcrnrlon=x1, ax=ax)
            m.drawparallels(parallels, labels=[True, False, False, False], fontsize=font_size, linewidth=lw)
            m.drawmeridians(meridians, labels=[0, 0, 0, 1], fontsize=font_size, linewidth=lw)
            m.fillcontinents()  # Fill the continents
            m.drawmapboundary(fill_color='black', linewidth=lw)  # Fill the globe with a blue color
            m.drawcoastlines(linewidth=1)

        if os.path.isfile(bsm_pickle) is False:
            with open(bsm_pickle, 'wb') as bmp:
                pickle.dump(m, bmp)

        if len(self.lon.shape) == 1:
            lon2d, lat2d = np.meshgrid(self.lon, self.lat)
            xx, yy = m(lon2d, lat2d)
        else:
            xx, yy = m(self.lon, self.lat)

        cmn, cmx = self.clim
        cmp = self.cmp
        if self.cmp.endswith('.dat'):
            chl_cmp_dict, _ = custom_cmap(self.cmp)
            cmp = 'custom_cmp'
            cmap = colors.LinearSegmentedColormap(cmp, chl_cmp_dict)
            plot.register_cmap(cmap=cmap)

        n_colors = self.nColours
        bounds = np.linspace(cmn, cmx, self.nColours)
        if self.log10 is True:
            nrm = colors.LogNorm(*self.clim)
        else:
            nrm = colors.BoundaryNorm(boundaries=bounds, ncolors=self.nColours)

        #  //////////////////////////////////////  Map data onto image  \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
        if self.data.shape[0] == 1:
            self.data = np.squeeze(self.data, axis=0)
        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        mesh = m.pcolormesh(xx, yy, self.data, shading='flat', norm=nrm, cmap=plot.get_cmap(cmp, n_colors))
        # add contours
        if self.contour is True:
            levels = np.linspace(cmn, cmx, 5)
            if self.levels is not None:
                levels = self.levels

            cnt = m.contour(xx, yy, self.data, levels=levels, colors='k', linewidths=lw)
            if self.levels is None:
                cnt.clabel(inline=True, fmt='%.2f', fontsize=35)
            else:
                cnt.clabel(inline=True, fontsize=35)

        if self.points is not None:
            px, py = m(self.points[:,0], self.points[:,1])
            m.scatter(px, py, s=100, c='w')
            # markerfacecoloralt='black', fillstyle='full', linestyle=None)

        # Set visualization range
        mesh.set_clim(vmin=cmn, vmax=cmx)

        if self.cbar is True:
            divider = make_axes_locatable(plot.gca())
            cax = divider.append_axes(cb_location, size="3%", pad=pad)
            cbr = plot.colorbar(mesh, cax=cax, orientation=cb_orientation, format='%.2f')

            cb_label, cb_label_unit = self.label, self.unit

            cbr.ax.tick_params(labelsize=font_size)
            if (self.label is not None) and (cb_label_unit is None):
                cbr.set_label("%s" % cb_label, size=font_size)
            if cb_label_unit is not None:
                cbr.set_label("%s [%s]" % (cb_label, cb_label_unit), size=font_size)

            # Put title
            if self.title is not None:
                plot.title(self.title, fontsize=font_size)
            elif self.date is not None:
                plot.title(self.date.strftime('%d %b %Y'), fontsize=font_size)
        else:
            # Put title
            if self.title is not None:
                plot.title(self.title, fontsize=font_size)
            elif self.date is not None:
                lab = "%s [%s]" % (self.label, self.unit)
                plot.title(f"{self.date.strftime('%d %b %Y')}\n{lab}", fontsize=font_size)

        plot.tight_layout()
        # File save
        fig.savefig(self.filename, dpi=100, bbox_inches='tight')
        plot.clf()

        elapsed_time = time.process_time() - t
        mnt, sec = divmod(elapsed_time, 60)
        _, mnt = divmod(mnt, 60)
        space = 45 - len(f'ImageSaved: {basename}')
        print(f'ImageSaved: {basename} {"." * space} {int(mnt)} minutes{sec:7.3f} seconds')
        plot.close()

. И обратная трассировка

 c:\users\eligio\appdata\local\programs\python\python37\lib\site-packages\mpl_toolkits\basemap\__init__.py in drawcoastlines(self, linewidth, linestyle, color, antialiased, ax, zorder)
   1855         self.set_axes_limits(ax=ax)
   1856         # clip to map limbs
-> 1857         coastlines,c = self._cliplimb(ax,coastlines)
   1858         return coastlines
   1859 

c:\users\eligio\appdata\local\programs\python\python37\lib\site-packages\mpl_toolkits\basemap\__init__.py in _cliplimb(self, ax, coll)
   1810         c = self._mapboundarydrawn
   1811         if c not in ax.patches:
-> 1812             p = ax.add_patch(c)
   1813             #p.set_clip_on(False)
   1814         try:

~\AppData\Roaming\Python\Python37\site-packages\matplotlib\axes\_base.py in add_patch(self, p)
   1966         Add a `~.Patch` to the axes' patches; return the patch.
   1967         """
-> 1968         self._set_artist_props(p)
   1969         if p.get_clip_path() is None:
   1970             p.set_clip_path(self.patch)

~\AppData\Roaming\Python\Python37\site-packages\matplotlib\axes\_base.py in _set_artist_props(self, a)
    915     def _set_artist_props(self, a):
    916         """set the boilerplate props for artists added to axes"""
--> 917         a.set_figure(self.figure)
    918         if not a.is_transform_set():
    919             a.set_transform(self.transData)

~\AppData\Roaming\Python\Python37\site-packages\matplotlib\artist.py in set_figure(self, fig)
    688         # to more than one Axes
    689         if self.figure is not None:
--> 690             raise RuntimeError("Can not put single artist in "
    691                                "more than one figure")
    692         self.figure = fig

RuntimeError: Can not put single artist in more than one figure

Пример данных можно найти здесь . Первая строка, столбец - долгота, широта. Я получаю данные в numpy, как показано ниже

filename = 'temp_20040611_01z_data.txt'
data = []
with open(filename, 'rb') as txt:
    for j, line in enumerate(txt.readlines()):
        line = line.decode().strip().split()
        data.append([np.single(col) for col in line])

data = np.array(data)       
lon = data[0, 1:]
lat data[1:, 0]
data = data[1:, 1:]

И, наконец, вызываю функцию построения графиков следующим образом

f = filename.split('_')
ImageSaving(filename=filename, 
            clim=[data.min(), data.max()], 
            data=data, lon=lon, lat=lat, log10=False, 
            date=parse(f[1]), var=f[0].title()).save_fig()

Я понятия не имею, что мне следует сделать, чтобы это работало как это может у меня много времени. Любая помощь в решении этого вопроса высоко ценится. Заранее спасибо, что нашли время помочь.

...