예제 #1
0
    def test_grid_by_width(self):
        """
        Test error-free creation and return of a valid grid, with dimensions
        provided as cell widths.
        """
        grid = GridDimensions((180, 360), "width")

        dims_by_width = grid.dims_by_width()
        dims_by_width_expected = (180, 360)
        self.assertEqual(dims_by_width, dims_by_width_expected)
예제 #2
0
    def test_grid_count_to_width(self):
        """
        Test error-free creation and return of a valid grid with dimensions
        provided as cell counts, and the correct conversion of the grid to
        a representation in cell widths.
        """
        grid = GridDimensions((45, 180), "count")

        dims_by_width = grid.dims_by_width()
        dims_by_width_expected = (4, 2)
        self.assertEqual(dims_by_width, dims_by_width_expected)
예제 #3
0
    def test_tiny_grid_cells(self):
        """
        Test correct conversion between grid representations when grid cell
        widths are very small.
        """
        grid = GridDimensions((2880, 2880), "count")

        dims_by_width = grid.dims_by_width()
        dims_by_width_expected = (0.0625, 0.125)

        dims_by_count = grid.dims_by_count()
        dims_by_count_expected = (2880, 2880)

        self.assertEqual(dims_by_width, dims_by_width_expected)
        self.assertEqual(dims_by_count, dims_by_count_expected)
예제 #4
0
    def test_max_grid_dimensions(self):
        """
        Test that a grid is successfully created when the maximum valid cell
        widths are provided.
        """
        grid = GridDimensions((180, 360), "width")

        dims_by_width = grid.dims_by_width()
        dims_by_width_expected = (180, 360)

        dims_by_count = grid.dims_by_count()
        dims_by_count_expected = (1, 1)

        self.assertEqual(dims_by_width, dims_by_width_expected)
        self.assertEqual(dims_by_count, dims_by_count_expected)
예제 #5
0
    def test_float_grid_dims(self):
        """
        Test the error-free creation of a valid grid with floating point
        grid cell widths, and sensible conversion to a representation in
        cell counts.
        """
        grid = GridDimensions((0.5, 1.5), "width")

        dims_by_width = grid.dims_by_width()
        dims_by_width_expected = (0.5, 1.5)

        dims_by_count = grid.dims_by_count()
        dims_by_count_expected = (360, 240)

        self.assertEqual(dims_by_width, dims_by_width_expected)
        self.assertEqual(dims_by_count, dims_by_count_expected)
예제 #6
0
class ModelImageRenderer:
    """
    A converter between gridded climate data and image visualizations of the
    data. Displays data in the form of nested lists or arrays as an overlay
    on a world map projection.
    """
    def __init__(self: 'ModelImageRenderer', data: np.ndarray) -> None:
        """
        Instantiate a new ModelImageReader.

        Data to display is provided through the data parameter. This parameter
        may either by an array, or an array-like structure such as a nested
        list. the There must be three dimensions to the data: time first,
        followed by latitude and longitude.

        :param data:
            An array-like structure of numeric values, representing temperature
            over a globe
        """
        self._data = data
        self._grid = GridDimensions((len(data), len(data[0])), "count")

        # Some parameters for the visualization are also left as attributes.
        self._continent_linewidth = 0.5
        self._lat_long_linewidth = 0.1

    def save_image(
        self: 'ModelImageRenderer',
        out_path: str,
        min_max_grades: Tuple[int, int] = (-60, 60)) -> None:
        """
        Produces a .PNG formatted image file containing the gridded data
        overlaid on a map projection.

        The map is displayed in equirectangular projection, labelled with
        lines of latitude and longitude at the intersection between
        neighboring grid cells. The grid cells are filled in with a colour
        denoting the magnitude of the temperature at that cell.

        The optional min_max_grades parameter specifies the lower bound and
        the upper bound of the range of values for which different colours
        are assigned. Any grid cell with a temperature value below the first
        element of min_max_grades will be assigned the same colour. The same
        applies to any cell with a temperature greater than min_max_grades[1].
        The default boundaries are -60 and 40.

        The image is saved at the specified absolute or relative filepath.

        :param out_path:
            The location where the image file is created
        :param min_max_grades:
            A tuple containing the boundary values for the colorbar shown in
            the image file
        """
        if len(min_max_grades) != 2:
            raise ValueError("Color grade boundaries must be given in a tuple"
                             "of length 2 (is length {})".format(
                                 len(min_max_grades)))
        # Create an empty world map in equirectangular projection.
        map = Basemap(llcrnrlat=-90,
                      llcrnrlon=-180,
                      urcrnrlat=90,
                      urcrnrlon=180)
        map.drawcoastlines(linewidth=self._continent_linewidth)

        # Construct a grid from the horizontal and vertical sizes of the cells.
        grid_by_width = self._grid.dims_by_width()

        lat_val = -90.0
        lats = []
        while lat_val <= 90:
            lats.append(lat_val)
            lat_val += grid_by_width[0]

        lon_val = -180.0
        lons = []
        while lon_val <= 180:
            lons.append(lon_val)
            lon_val += grid_by_width[1]

        map.drawparallels(lats, linewidth=self._lat_long_linewidth)
        map.drawmeridians(lons, linewidth=self._lat_long_linewidth)
        x, y = map(lons, lats)

        # Overlap the gridded data on top of the map, and display a colour
        # legend with the appropriate boundaries.
        img = map.pcolormesh(x, y, self._data, cmap=plt.cm.get_cmap("jet"))
        map.colorbar(img)
        plt.clim(min_max_grades[0], min_max_grades[1])
        # Save the image and clear added components from memory
        plt.savefig(out_path)
        plt.close()
예제 #7
0
class ModelImageRenderer:
    """
    A converter between gridded climate data and image visualizations of the
    data. Displays data in the form of nested lists or arrays as an overlay
    on a world map projection.
    """
    def __init__(self: 'ModelImageRenderer',
                 data: np.ndarray) -> None:
        """
        Instantiate a new ModelImageReader.

        Data to display is provided through the data parameter. This parameter
        may either by an array, or an array-like structure such as a nested
        list. the There must be three dimensions to the data: time first,
        followed by latitude and longitude.

        :param data:
            An array-like structure of numeric values, representing temperature
            over a globe
        """
        self._data = data
        self._grid = GridDimensions((len(data), len(data[0])), "count")

        # Some parameters for the visualization are also left as attributes.
        self._continent_linewidth = 0.5
        self._lat_long_linewidth = 0.1

    def save_image(self: 'ModelImageRenderer',
                   out_path: str,
                   min_max_grades: Tuple[float, float] = (-8, 8)) -> None:
        """
        Produces a .PNG formatted image file containing the gridded data
        overlaid on a map projection.

        The map is displayed in equirectangular projection, labelled with
        lines of latitude and longitude at the intersection between
        neighboring grid cells. The grid cells are filled in with a colour
        denoting the magnitude of the temperature at that cell.

        The optional min_max_grades parameter specifies the lower bound and
        the upper bound of the range of values for which different colours
        are assigned. Any grid cell with a temperature value below the first
        element of min_max_grades will be assigned the same colour. The same
        applies to any cell with a temperature greater than min_max_grades[1].
        The default boundaries are -60 and 40.

        The image is saved at the specified absolute or relative filepath.

        :param out_path:
            The location where the image file is created
        :param min_max_grades:
            A tuple containing the boundary values for the colorbar shown in
            the image file
        """
        if min_max_grades is not None and len(min_max_grades) != 2:
            raise ValueError("Color grade boundaries must be given in a tuple"
                             "of length 2 (is length {})"
                             .format(len(min_max_grades)))
        # Create an empty world map in equirectangular projection.
        map = Basemap(llcrnrlat=-90, llcrnrlon=-180,
                      urcrnrlat=90, urcrnrlon=180)
        map.drawcoastlines(linewidth=self._continent_linewidth)

        # Construct a grid from the horizontal and vertical sizes of the cells.
        grid_by_width = self._grid.dims_by_width()

        lat_val = -90.0
        lats = []
        while lat_val <= 90:
            lats.append(lat_val)
            lat_val += grid_by_width[0]

        lon_val = -180.0
        lons = []
        while lon_val <= 180:
            lons.append(lon_val)
            lon_val += grid_by_width[1]

        map.drawparallels(lats, linewidth=self._lat_long_linewidth)
        map.drawmeridians(lons, linewidth=self._lat_long_linewidth)
        x, y = map(lons, lats)

        # Overlap the gridded data on top of the map, and display a colour
        # legend with the appropriate boundaries.
        img_bin = map.pcolormesh(x, y, self._data, cmap=plt.cm.get_cmap("jet"))

        map.colorbar(img_bin)
        plt.clim(min_max_grades[0], min_max_grades[1])

        fig = plt.gcf()
        fig.canvas.draw()

        pixels = fig.canvas.tostring_rgb()
        img = np.fromstring(pixels, dtype=np.uint8, sep='')
        img = img.reshape(fig.canvas.get_width_height()[::-1] + (3,))
        img = img[118:-113, 80:-30, :]

        alphas = np.ones(img.shape[:2], dtype=np.uint8) * 255
        alphas[:, -65:-57] = 0
        alphas[:9, :-43] = 0
        alphas[-8:, :-43] = 0

        for i in range(img.shape[0]):
            for j in range(img.shape[1] - 43, img.shape[1]):
                pixel = tuple(img[i, j, :])
                # if sum(pixel) == 765:
                if sum(pixel) < 450 and j >= img.shape[1] - 33:
                    img[i, j, :] = [147, 149, 152]
                elif j >= img.shape[1] - 33 or i < 9 or i > img.shape[0] - 9:
                    alphas[i, j] = 0

        img = np.dstack((img, alphas))
        plt.imsave(fname=out_path, arr=img)
        plt.close()