예제 #1
0
파일: geo.py 프로젝트: yaswant/ypylib
    def as_cartopy_crs(self):
        """Return this projection as a Cartopy CRS instance."""
        import iris.exceptions as iexcept
        import cartopy.crs as ccrs
        proj = ccrs.Geostationary(central_longitude=self.sub_lon,
                                  satellite_height=self.satellite_height,
                                  sweep_axis=self.sweep,
                                  globe=ccrs.Globe(semimajor_axis=self.req,
                                                   semiminor_axis=self.rpol,
                                                   ellipse=None))
        # Calculate extent
        proj.extent = None  # (x0, x1, y0, y1)
        try:
            proj.extent = (
                self.iris_cube().coord('projection_x_coordinate').points[-1],
                self.iris_cube().coord('projection_x_coordinate').points[0],
                self.iris_cube().coord('projection_y_coordinate').points[0],
                self.iris_cube().coord('projection_y_coordinate').points[-1]
            )
        except iexcept.CoordinateNotFoundError as err:
            log.warning(err)

        return proj
예제 #2
0
파일: __init__.py 프로젝트: yaswant/ypylib
    def plot(self,
             element,
             index=None,
             fix_unknown=False,
             show=False,
             valid_min=None,
             valid_max=None,
             **kw):
        """Plot requested data on a cylindrical map.

        Parameters
        ----------
        element : str
            A string specifying the field to show on map. Usually one from the
            request elements.
        index : int
            index of second dimension when retrieving multi-dim array
            eg, if extracted field is (N, d), where N is number of records
            and d is number of wavelengths then index=1 will plot the field
            corresponding to the second wavelength, and so on...
        show : bool, optional
            True displays the plot (default is False which return a pyplot
            object)
        valid_min : real
            Valid minimum physical value in the array. Setting this will mask
            any values < valid_min
        valid_max : real
            Valid maximim physical value in the array. Setting this will mask
            any values > valid_max

        Keywords:
            See ypylib.XYZ.mapdata() keyword parameters for adjusting plot
            style.

        Returns
        -------
        matplotlib.pyplot : object when ``show=False``

        """
        # extract data from metdb
        if self.mdb_extract is False and self.data is not None:
            data = self.data
            log.info('Skip MDB_EXTRACT.')
        else:
            data = self.extract(fix_unknown=fix_unknown)
        if data is None:
            return

        # show data on map
        host = ['[{}]'.format(self.hostname), ''][self.hostname is None]

        if isinstance(element, str) is False:
            log.warning('ELEMENT should be a string, but given\n%s', element)
            return

        if index is None or len(data[element].shape) == 1:
            plt_arr = data[element]
            element_str = element
        else:
            plt_arr = data[element][:, index]
            element_str = '{} [{}]'.format(element, str(index))

        # vmin = kw.get('vmin', plt_arr.min())
        # vmax = kw.get('vmax', plt_arr.max())
        vmin = [valid_min, plt_arr.min()][valid_min is None]
        vmax = [valid_max, plt_arr.max()][valid_max is None]

        if np.ma.is_masked(vmin) and np.ma.is_masked(vmax):
            log.warning('No valid data points to plot.')
            return

        if len(plt_arr.shape) > 1:
            log.warning('Cant decide which array to plot,')
            log.info('Please give me an index value (e.g. index=1).')
            return

        w = (plt_arr >= vmin) & (plt_arr <= vmax)
        if sum(w) == 0:
            log.warning('Number of constrained data points = 0. Abort plot.')
            return

        xyz = XYZ(data['LNGD'][w], data['LTTD'][w], plt_arr[w])

        # w = (data[element] >= vmin) & (data[element] <= vmax)
        # xyz = XYZ(data['LNGD'][w], data['LTTD'][w], data[element][w])

        # update plot title
        title = kw.get(
            'title', '{}:{} {}\n{}-{}'.format(self.subtype, element_str, host,
                                              self.start, self.stop))
        kw.update({'title': title})

        # print(self.extent)
        if self.area:
            kw.update({'limit': self.limit})

        # warnings.simplefilter("ignore", UserWarning)
        res = xyz.mapdata(**kw)

        if show:
            res.show()
        else:
            return res
예제 #3
0
파일: __init__.py 프로젝트: yaswant/ypylib
    def extract(self, verbose=False, fix_unknown=False):
        """Extract obs from MetDB.

        Parameters
        ----------
        verbose : bool
            increase verbosity
        fix_unknown : bool, optional
            Set data types for unknown elements e.g., merged back model fields
            to 32bit float.

        Returns
        -------
        numpy ndarray matching request

        """
        try:
            data = obs(Query.contact,
                       self.subtype,
                       self.keywords,
                       self.elements,
                       hostname=self.hostname)

        except ValueError as verr:
            if fix_unknown:
                elements = self.elements[len(Query.elements):]
                log.warning("Setting dtypes for %s as 'float'", elements)

                for e in elements:
                    self.set_element_dtype(e, 'float')
                data = obs(Query.contact,
                           self.subtype,
                           self.keywords,
                           self.elements,
                           hostname=self.hostname)
            else:
                log.error(
                    '%s\n** Hint for unknown elements: use '
                    'extract(fix_unknow=True) OR call '
                    'set_element_dtype(element, dtype) prior '
                    'to extract() to bypass this error **', verr)
                raise (verr)

        except IOError as err:
            if verbose:
                log.info('  %s:%s\n  %s - %s\n  %s', self.hostname, self.ddict,
                         self.start, self.stop, sorted(self.elements))
            raise (err)

        if self.constrain:
            # single field constrain
            # key = list(self.constrain.keys())[0]
            # value = list(self.constrain.values())[0]
            # data = data[data[key] == value]

            # iterate through all constained fields
            for key, val in self.constrain.items():
                data = data[data[key] == val]
            if len(data) == 0:
                log.error('Constrain results with no data.')
                print(self.constrain)
                return

        if self.keep:
            self.data = data

        return data
예제 #4
0
파일: geo.py 프로젝트: yaswant/ypylib
def plotx(filename,
          ax=None, cb_on=False, cb_bounds=None, cb_nticks=None, cb_unit='',
          cb_width=0.02, ccolor='w', coastline=True, cres='110m',
          dataset_path=None, draw_countries=False, dust_rgb=False,
          dust_quality=None, extent=None, figsize=(6, 6), gl_font=None,
          glw=0.5, list_all=False, MDI=None, projection=None, quick=False,
          satellite='MSG', save_fig=None, show_path=False, show_ticks=False,
          stride=(1, 1), tag_fig=None, tag_col='k', title=None, xglocs=None,
          yglocs=None, **kw):
    """Display 2D arrays from geostationary Slotstore file.

    Args:
        filename (str): Slotstore filename. An hdf5 file containing full-disc
            arrays of geostationary satellite or equivalent model data.
        ax (cartopy.mpl.geoaxes.GeoAxesSubplot, optional): Plot data on a
            specific geo axis.
        cb_on (bool, optional): Add colour bar to the plot.
        cb_bounds (array_like, optional): Specify discrete bounds for colorbar.
        cb_nticks (int, optional): Number of ticks to show in the colorbar.
        cb_unit (str, optional): Show data unit string on colorbar.
        cb_width (float, optional): Colour bar width.
        ccolor (str, optional): Coastline/boundary colour. Defaults to white.
        coastline (bool, optional): Show/hide coastlines. Defaults to True.
            Setting this to False will show the plot as a plain 2D array.
        cres (str, optional): Resolution of cartopy coastline/country boundary.
            Defaults to '110m'.  Accepted values are '10m', '50m', '110m'.
        dataset_path (str, optional): Full path to 2D array data in hdf5.
        draw_countries (bool, optional): Show country boundaries.
        dust_rgb (bool, optional): Plot Dust RGB, require BT at 3 SEVIRI IR
            window channels in the file. Note: dataset_path and dust_rgb
            keywords are mutually exclusive.
        dust_quality (int, optional): Mask plot based on dust confidence flag
            (value range: 1-7).
        extent (sequence, optional): Longitude and Latitude bounds to plot the
            data (lon0, lon1, lat0, lat1)
        figsize (tuple, optional): Figure size. Default is (6, 6)
        gl_font (dict, optional):  Font properties for grid labels default is
            {'family': 'monospace', 'size': 8, 'color': '#333333'}
        glw (float, optional): Width of grid lines.
        list_all (bool, optional): Show list of all available dataset in the
            file (no plot) and exit.
        MDI (number, optional): Missing Data Indicator.
        projection (cartopy crs, optional): Cartopy projection to use for
            mapping - one from the following
            Geostationary(), PlateCarree(), Mercator().
        quick (bool, optional): Quick plot. Equivalent to stride=(10, 10).
        satellite (str, optional): Satellite ID. Defaults to 'MSG'. Accepted
            satellite IDs are: 'GOES16', 'GOES-E', 'GOES17', 'GOES-W',
            'IODC'|'MSG_IODC', 'HIM8'.
        save_fig (str, optional): Save plot to a named file.
        show_path (bool, optional): Show filename and dataset path on plot.
        show_ticks (bool, optional): plot ticks and tick labels for non-mapped
            display.
        stride (tuple, optional): Skip pixels in x,y dimension, Default (1, 1)
            is to show at full resolution.
        tag_fig (str, optional): Add optional string to top left corner of
            figure. Useful for figures for articles.
        tag_col (str, optional): Colour of tag_fig text.
        title (str, optional): Figure title. Defaults to variable name from
            dataset_path (or 'DustRGB').
        xglocs (array_like, optional): Locations of x grid-lines.
        yglocs (array_like, optional): Locations of y grid-lines.
        **kw: accepted `imshow` keywords (see imshow).

    Returns:
        matplotlib.pyplot or None: plot object or None if figure saved to disc.

    Raises:
        NotImplementedError: If Projection is not one of the following
            Geostationary, PlateCarree, Mercator
        ValueError: If dataset_path is missing or incorrect or ambiguous.

    """
    import matplotlib.pyplot as plt
    import cartopy.feature as cfeature
    import cartopy.mpl.gridliner as cgrid
    from matplotlib import colors, ticker

    # defaults for MSG 0-deg service
    idx = 1  # index of datetime string in hdf filename
    c_lon = 0  # central geostationary longitude
    def_extent = (-80, 80, -80, 80)  # default map extent
    sat = satellite.upper()
    if sat in ('IODC', 'MSG_IODC'):
        # update defaults for IODC service
        idx = 2
        c_lon = 41.5
        def_extent = (-40, 120, -80, 80)
    if sat == 'HIM8':
        # update defaults for Himawari service
        c_lon = 140.7
        def_extent = (60, 220, -80, 80)
    if sat in ('GOES16', 'GOES-E'):
        c_lon = -75
        def_extent = (-155, 5, -80, 80)
    if sat == 'GOES15':
        c_lon = -135
        # def_extent = (-155, 5, -80, 80)
    if sat in ('GOES17', 'GOES-W'):
        c_lon = -137
        # def_extent = (-155, 5, -80, 80)

    h5 = h5Parse(filename)
    if list_all:
        h5.ls()
        return

    if quick:
        stride = (10, 10)

    apply_dust_quality = False
    if dust_quality:
        dconf = '/Product/GM/DustAOD/DustConfidence'
        try:
            dset = h5.get_data(dconf)
            dust_qual = dset[dconf][::-1, ::-1][::stride[0], ::stride[1]]
            apply_dust_quality = True
        except KeyError:
            log.warn('Dust_quality not available - filter wont be applied')

    if gl_font is None:
        gl_font = dict(family='monospace', size=8, color='#333333')

    # Get dust RGB array
    if dust_rgb and dataset_path is None:
        dataset_path = 'DustRGB'
        cb_on = False
        plot_array = get_dust_rgb(h5, stride=stride, satellite=satellite)

    elif dataset_path and dust_rgb is False:
        dset = h5.get_data(dataset_path)
        if len(list(dset.keys())) == 0:
            return
        plot_array = dset[dataset_path][::-1, ::-1][::stride[0], ::stride[1]]
        # print plot_array.dtype

        if plot_array.dtype in (
                np.int8, np.int16, np.intc, np.uint8, np.uint16, np.intp):
            MDI = [MDI, IMDI][MDI is None]
            plot_array = np.ma.masked_equal(plot_array, MDI)
            if apply_dust_quality:
                plot_array = np.ma.masked_less(dust_qual, dust_quality)
        else:
            MDI = [MDI, RMDI][MDI is None]
            plot_array[plot_array <= (MDI + RMDItol)] = np.nan
            if apply_dust_quality:
                plot_array = np.ma.where(
                    dust_qual >= dust_quality, plot_array, np.nan)

    elif dataset_path and dust_rgb:
        raise ValueError('dust_rgb must be False if dataset_path is given')
    else:
        raise ValueError(
            'Either dust_rgb or dataset_path must be specified.\n\n'
            '  msgview(filename, [dataset_path|dust_rgb=True], ..) OR\n'
            '  msgview(filename, list_all=True) to see all datasets.\n')

    # Get MSG datetime string from hdf5 filename
    msg_datetime = os.path.basename(filename).split('_')[idx]
    title_str = [msg_datetime, os.path.basename(dataset_path)]

    # Reassign 3-channel DustMask values for readable legend/title
    if title_str[1] in 'Dust':
        title_str[1] = 'DustMask'
        plot_array[plot_array < -0.5] = 0
        plot_array[plot_array == 300] = 1
        plot_array[plot_array == 400] = 2
        plot_array[plot_array == 500] = 3

    # Rename DustHeightError to DustPressureError
    if title_str[1] in 'DustHeightError':
        title_str[1] = 'DustPressureError'

    if title is None:
        title = '{}  {}'.format(title_str[0], title_str[1])

    # Start plot
    if ax:
        projection = ax.projection
    else:
        plt.figure(figsize=figsize)
        projection = [projection, ccrs.Geostationary()][projection is None]

    # Normalise colour based on discrete colour bounds (cb_bounds)?
    if cb_bounds:
        bounds = np.array(cb_bounds)
        norm = colors.BoundaryNorm(boundaries=bounds, ncolors=len(bounds))
    else:
        norm = None

    # combine plot_array and imshow keywords
    plot_array_kw = {
        'extent': geo_extent(satellite=satellite),
        'origin': 'upper',
        'norm': None,
        'interpolation': None}
    plot_array_kw.update(**kw)
    gridline_kw = dict(alpha=0.5, xlocs=xglocs, ylocs=yglocs, linestyle='-',
                       linewidth=glw)
    if coastline:
        if projection in (ccrs.PlateCarree(),
                          ccrs.PlateCarree(central_longitude=0),
                          ccrs.PlateCarree(central_longitude=180),
                          ccrs.Mercator(),
                          ccrs.Mercator(central_longitude=0),
                          ccrs.Mercator(central_longitude=180)):

            # Plot using rectangular projection
            if ax is None:
                ax = plt.axes(projection=projection)
            extent = [extent, def_extent][extent is None]
            ax.set_extent(extent, ccrs.PlateCarree())
            gl = ax.gridlines(draw_labels=True, **gridline_kw)
            gl.xlabels_top = False
            gl.ylabels_right = False
            gl.xformatter = cgrid.LONGITUDE_FORMATTER
            gl.yformatter = cgrid.LATITUDE_FORMATTER
            gl.xlabel_style, gl.ylabel_style = gl_font, gl_font
            im = ax.imshow(plot_array, transform=ccrs.Geostationary(c_lon),
                           **plot_array_kw)

        elif projection == ccrs.Geostationary():
            # Plot in Geostationary projection
            if ax is None:
                ax = plt.axes(projection=ccrs.Geostationary(c_lon))
            ax.set_global()
            gl = ax.gridlines(**gridline_kw)
            im = ax.imshow(plot_array, **plot_array_kw)

            if extent:
                ax.set_extent(extent, ccrs.PlateCarree())
        else:
            raise NotImplementedError(
                '{} projection not implemented.\n'
                'Please use one from the following:\n'
                '\tGeostationary\n\tPlateCarree or\n\tMercator\n'.format(
                    projection.__class__))

        ax.coastlines(resolution=cres, lw=0.5, color=ccolor)
    else:
        if extent:
            # TODO: implement geo-to-pixel for cut outs without map
            log.warning('Ignored extent. For geographic subsets please set '
                        'coastline=True.')
        ax_pos = [0.05, 0.1, 0.8, 0.8]
        if show_ticks:
            ax_pos = [0.1, 0.1, 0.8, 0.8]
        if ax is None:
            ax = plt.axes(ax_pos)

    # Add country borders
    if draw_countries:
        ax.add_feature(cfeature.BORDERS, lw=0.4, edgecolor=ccolor)

    pos = ax.get_position()

    # Tag a figure with additional string at top-left (useful for figure
    # panel numbering)
    if tag_fig:
        # title = '{} {}'.format(tag_fig, title)
        ax.text(0.01, 0.95, tag_fig, color=tag_col, fontsize=14,
                transform=ax.transAxes)

    # Draw title text
    ax.set_title(title, fontsize="13", loc='left')

    # Hide plot ticks and tick labels?
    if show_ticks is False:
        for ticx, ticy in list(
                zip(ax.xaxis.get_major_ticks(), ax.yaxis.get_major_ticks())):
            ticx.tick1On = ticx.tick2On = False
            ticx.label1On = ticx.label2On = False
            ticy.tick1On = ticy.tick2On = False
            ticy.label1On = ticy.label2On = False

    # Show image without coastline - simple 2D array imshow()
    if coastline is False:
        im = plt.imshow(plot_array, norm=norm, interpolation=None, **kw)
        ax.grid(lw=0.5, alpha=0.5)

    # Attach vertical colour bar
    if cb_on:
        cax = plt.axes([pos.x1, pos.y0, cb_width, pos.height])
        cb = plt.colorbar(mappable=im, cax=cax, format='%g')
        cb.ax.tick_params(direction='in')
        cb.ax.set_title('{}'.format(cb_unit))
        if cb_nticks:
            tick_locator = ticker.MaxNLocator(nbins=cb_nticks)
            cb.locator = tick_locator
            cb.update_ticks()
        if title_str[1] in 'DustMask':
            cb.ax.set_yticklabels(
                ['Dust', 'Ice cloud', 'Low cloud/\nSurface', 'Uncertain'],
                rotation='90', va='bottom')

    if show_path:
        # Show filename and HDF5 dataset path on plot
        textstr = '{}\n{}'.format(dataset_path, filename)
        plt.gcf().text(0.99, 0.01, textstr, fontsize=8, ha='right')

    if save_fig:
        # save figure as file to disk
        plt.savefig(save_fig)
        plt.close()
        log.info('%s saved.', save_fig)
    else:
        # return pyplot object
        return plt
예제 #5
0
파일: geo.py 프로젝트: yaswant/ypylib
    def inverse_projection(self, cols_and_lines=None, maxlon=None):
        """Generate lat, lon coords (4.4.3.2 of [1]).

        Method copied from Fortran:
            SpsMod_Coordinates/Sps_GeostationaryProjection.inc

        Args:
            cols_and_lines (None, optional): Description
            maxlon (None, optional): Description

        Returns:
            tuple: a two-element tuple containing same-shape 1D arrays for
            lons and lats (in degrees).

        """
        import warnings
        warnings.simplefilter('ignore', RuntimeWarning)

        if cols_and_lines is None:
            # Do full disk
            col_start = -self.coff + 1
            col_stop = self.nc + col_start
            cols = np.arange(col_start, col_stop)

            line_start = -self.loff + 1
            line_stop = self.nl + line_start
            lines = np.arange(line_start, line_stop)

            colsm, linesm = np.meshgrid(cols, lines)
            cols = colsm.flatten()
            lines = linesm.flatten()

        else:
            errmsg = ("cols_and_lines should be numpy array, 2 dims, second"
                      " of which should be of length 2")
            if (type(cols_and_lines) is not np.ndarray or
                    len(cols_and_lines.shape) != 2 or
                    cols_and_lines.shape[1] != 2):
                log.warning(errmsg)
                return

            cols = cols_and_lines[:, 0] - self.coff + 1
            lines = cols_and_lines[:, 1] - self.loff + 1

        # Generate intermediate coords
        x = np.deg2rad(cols * self.dx)
        y = np.deg2rad(lines * self.dy)

        sin_y = np.sin(y)
        sin2_y = np.sin(y) * np.sin(y)
        cos_y = np.cos(y)
        cos2_y = np.cos(y) * np.cos(y)

        sin_x = np.sin(x)
        cos_x = np.cos(x)

        # Solve quadratic to get the vector length, but check that the
        # discriminant is positive, else the point is off the earth disk.
        req = 1e-3 * self.req
        rpol = 1e-3 * self.rpol
        latf = req * req / (rpol * rpol)
        h = 1e-3 * self.satellite_height + req

        a = cos2_y + latf * sin2_y
        b = h * cos_x * cos_y
        sd = b * b - a * (h * h - req * req)

        space_mask = sd < 0
        sn = (b - np.sqrt(sd)) / a

        # Calculate components of position vector
        s1 = h - sn * cos_x * cos_y
        if self.sweep == 'x':
            s2 = sn * sin_x
            s3 = sn * sin_y * cos_x
        else:
            s2 = sn * sin_x * cos_y
            s3 = sn * sin_y

        # Convert to latitude and longitude
        sxy = np.sqrt(s1 * s1 + s2 * s2)
        lons = np.rad2deg(np.arctan(s2 / s1) + np.deg2rad(self.sub_lon))
        lats = np.rad2deg(np.arctan(latf * s3 / sxy))

        # Adjust longitudes if -180 to 180 range needed
        if maxlon is not None:
            lons[lons >= maxlon] = (lons[lons >= maxlon] - 360.0)

        lons[space_mask] = RMDI
        lats[space_mask] = RMDI
        lons = np.ma.array(lons, mask=space_mask)
        lats = np.ma.array(lats, mask=space_mask)

        return (lons, lats)
예제 #6
0
파일: geo.py 프로젝트: yaswant/ypylib
    def forward_projection(self, lons_and_lats):
        """Convert lons/lats to intermediate coords and col/line vals
        (4.4.4 of [1]). Assumes lons/lats are in degrees, and lons
        in range -180 to 180.

        Method copied from Fortran:
            SpsMod_Coordinates/Sps_GeostationaryProjection.inc
            Columns/lines are 1-based (not 0-based as in Python)

        Args:
            lons_and_lats (ndarray): 2D numpy array containing lons and lats
                in degrees, stacked 1D arrays so size of 2nd array is 2, with
                lons = lons_and_lats[:, 0], lats = lons_and_lats[:, 1].

        Returns:
            tuple: 4-element tuple of 1D arrays: x, y, columns, lines.

        """
        errmsg = ("lons_and_lats should be numpy array, 2 dims, second"
                  " of which should be of length 2")
        if (type(lons_and_lats) is not np.ndarray or
                len(lons_and_lats.shape) != 2 or
                lons_and_lats.shape[1] != 2):
            log.warning(errmsg)
            return

        lons_r = np.deg2rad(lons_and_lats[:, 0])
        lats_r = np.deg2rad(lons_and_lats[:, 1])

        # Geocentric latitude clat
        req = 1.0e-3 * self.req
        rpol = 1.0e-3 * self.rpol
        clatf = rpol * rpol / (req * req)
        h = 1.0e-3 * self.satellite_height + req
        rf = (req * req - rpol * rpol) / (req * req)

        clat = np.arctan(clatf * np.tan(lats_r))
        cos_clat = np.cos(clat)
        cos2_clat = cos_clat * cos_clat

        # Distance rl of point P from the centre of the earth
        rl = rpol / np.sqrt(1.0 - rf * cos2_clat)

        # Cartesian components of the vector rs (r1,r2,r3)
        # which points from the satellite to P.
        r1 = h - rl * cos_clat * np.cos(lons_r - np.deg2rad(self.sub_lon))
        r2 = -rl * cos_clat * np.sin(lons_r - np.deg2rad(self.sub_lon))
        r3 = rl * np.sin(clat)

        # Check to see if P is visible (the maximum viewing extent
        # is where a line from the satellite is tangent to the earth).
        rs = r1 * r1 + r2 * r2 + r3 * r3
        mask = np.logical_or.reduce((np.abs(lons_r) > np.pi,
                                     np.abs(lats_r) > np.pi / 2.0,
                                     (rs + rl * rl) > (h * h)))

        # Convert to planar coordinates
        if self.sweep == 'x':
            rn = np.sqrt(r1 * r1 + r3 * r3)
            x = np.where(mask, RMDI, np.arctan(-r2 / rn))
            y = np.where(mask, RMDI, np.arctan(r3 / r1))
        else:
            rn = np.sqrt(rs)
            x = np.where(mask, RMDI, np.arctan(-r2 / r1))
            y = np.where(mask, RMDI, np.arcsin(r3 / rn))

        # Deduce column / lines
        cols = np.where(mask, IMDI,
                        np.round(self.coff +
                                 np.rad2deg(x / self.dx))).astype('int64')
        lines = np.where(mask, IMDI,
                         np.round(self.loff +
                                  np.rad2deg(y / self.dy))).astype('int64')

        return (x, y, cols, lines)