コード例 #1
0
def readgssi(infile,
             outfile=None,
             verbose=False,
             antfreq=None,
             frmt='python',
             plotting=False,
             figsize=7,
             dpi=150,
             stack=1,
             x='seconds',
             z='nanoseconds',
             histogram=False,
             colormap='gray',
             colorbar=False,
             zero=[None, None, None, None],
             gain=1,
             freqmin=None,
             freqmax=None,
             reverse=False,
             bgr=False,
             win=0,
             dewow=False,
             normalize=False,
             specgram=False,
             noshow=False,
             spm=None,
             start_scan=0,
             num_scans=-1,
             epsr=None,
             title=True,
             zoom=[0, 0, 0, 0]):
    """
    This is the primary directive function. It coordinates calls to reading, filtering, translation, and plotting functions, and should be used as the overarching processing function in most cases.

    :param str infile: Input DZT data file
    :param str outfile: Base output file name for plots, CSVs, and other products. Defaults to :py:data:`None`, which will cause the output filename to take a form similar to the input. The default will let the file be named via the descriptive naming function :py:data:`readgssi.functions.naming()`.
    :param bool verbose: Whether or not to display (a lot of) information about the workings of the program. Defaults to :py:data:`False`. Can be helpful for debugging but also to see various header values and processes taking place.
    :param int antfreq: User setting for antenna frequency. Defaults to :py:data:`None`, which will cause the program to try to determine the frequency from the antenna name in the header of the input file. If the antenna name is not in the dictionary :py:data:`readgssi.constants.ANT`, the function will try to determine the frequency by decoding integers in the antenna name string.
    :param str frmt: The output format to be passed to :py:mod:`readgssi.translate`. Defaults to :py:data:`None`. Presently, this can be set to :py:data:`frmt='csv'`, :py:data:`'numpy'`, :py:data:`'gprpy'`, or :py:data:`'object'` (which will return the header dictionary, the image arrays, and the gps coordinates as objects). Plotting will not interfere with output (i.e. you can output to CSV and plot a PNG in the same command).
    :param bool plotting: Whether to plot the radargram using :py:func:`readgssi.plot.radargram`. Defaults to :py:data:`False`.
    :param int figsize: Plot size in inches to be passed to :py:func:`readgssi.plot.radargram`.
    :param int dpi: Dots per inch (DPI) for figure creation.
    :param int stack: Number of consecutive traces to stack (horizontally) using :py:func:`readgssi.arrayops.stack`. Defaults to 1 (no stacking). Especially good for handling long radar lines. Algorithm combines consecutive traces together using addition, which reduces noise and enhances signal. The more stacking is done, generally the clearer signal will become. The tradeoff is that you will reduce the length of the X-axis. Sometimes this is desirable (i.e. for long survey lines).
    :param str x: The units to display on the x-axis during plotting. Defaults to :py:data:`x='seconds'`. Acceptable values are :py:data:`x='distance'` (which sets to meters), :py:data:`'km'`, :py:data:`'m'`, :py:data:`'cm'`, :py:data:`'mm'`, :py:data:`'kilometers'`, :py:data:`'meters'`, etc., for distance; :py:data:`'seconds'`, :py:data:`'s'`, :py:data:`'temporal'` or :py:data:`'time'` for seconds, and :py:data:`'traces'`, :py:data:`'samples'`, :py:data:`'pulses'`, or :py:data:`'columns'` for traces.
    :param str z: The units to display on the z-axis during plotting. Defaults to :py:data:`z='nanoseconds'`. Acceptable values are :py:data:`z='depth'` (which sets to meters), :py:data:`'m'`, :py:data:`'cm'`, :py:data:`'mm'`, :py:data:`'meters'`, etc., for depth; :py:data:`'nanoseconds'`, :py:data:`'ns'`, :py:data:`'temporal'` or :py:data:`'time'` for seconds, and :py:data:`'samples'` or :py:data:`'rows'` for samples.
    :param bool histogram: Whether to plot a histogram of array values at plot time.
    :type colormap: :py:class:`str` or :class:`matplotlib.colors.Colormap`
    :param colormap: Plot using a Matplotlib colormap. Defaults to :py:data:`gray` which is colorblind-friendly and behaves similarly to the RADAN default, but :py:data:`seismic` is a favorite of many due to its diverging nature.
    :param bool colorbar: Whether to display a graded color bar at plot time.
    :param list[int,int,int,int] zero: A list of values representing the amount of samples to slice off each channel. Defaults to :py:data:`None` for all channels, which will end up being set as :py:data:`[2,2,2,2]` for a four-channel file (2 is the number of rows down that GSSI stores mark information in).
    :param int gain: The amount of gain applied to plots. Defaults to 1. Gain is applied as a ratio of the standard deviation of radargram values to the value set here.
    :param int freqmin: Minimum frequency value to feed to the vertical triangular FIR bandpass filter :py:func:`readgssi.filtering.triangular`. Defaults to :py:data:`None` (no filter).
    :param int freqmax: Maximum frequency value to feed to the vertical triangular FIR bandpass filter :py:func:`readgssi.filtering.triangular`. Defaults to :py:data:`None` (no filter).
    :param bool reverse: Whether to read the array backwards (i.e. flip horizontally; :py:func:`readgssi.arrayops.flip`). Defaults to :py:data:`False`. Useful for lining up travel directions of files run opposite each other.
    :param int bgr: Background removal filter applied after stacking (:py:func:`readgssi.filtering.bgr`). Defaults to :py:data:`False` (off). :py:data:`bgr=True` must be accompanied by a valid value for :py:data:`win`.
    :param int win: Window size for background removal filter (:py:func:`readgssi.filtering.bgr`). If :py:data:`bgr=True` and :py:data:`win=0`, the full-width row average will be subtracted from each row. If :py:data:`bgr=True` and :py:data:`win=50`, a moving window will calculate the average of 25 cells on either side of the current cell, and subtract that average from the cell value, using :py:func:`scipy.ndimage.uniform_filter1d` with :py:data:`mode='constant'` and :py:data:`cval=0`. This is useful for removing non-uniform horizontal average, but the tradeoff is that it creates ghost data half the window size away from vertical figures, and that a window size set too low will obscure any horizontal layering longer than the window size.
    :param bool dewow: Whether to apply a vertical dewow filter (experimental). See :py:func:`readgssi.filtering.dewow`.
    :param bool normalize: Distance normalization (:py:func:`readgssi.arrayops.distance_normalize`). Defaults to :py:data:`False`.
    :param bool specgram: Produce a spectrogram of a trace in the array using :py:func:`readgssi.plot.spectrogram`. Defaults to :py:data:`False` (if :py:data:`True`, defaults to a trace roughly halfway across the profile). This is mostly for debugging and is not currently accessible from the command line.
    :param bool noshow: If :py:data:`True`, this will suppress the matplotlib interactive window and simply save a file. This is useful for processing many files in a folder without user input.
    :param float spm: User-set samples per meter. This overrides the value read from the header, and typically doesn't need to be set if the samples per meter value was set correctly at survey time. This value does not need to be set if GPS input (DZG file) is present and the user sets :py:data:`normalize=True`.
    :param int start_scan: zero based start scan to read data from. Defaults to zero.
    :param int num_scans: number of scans to read from the file, Defaults to -1, which reads from start_scan to end of file.
    :param float epsr: Epsilon_r, otherwise known as relative permittivity, or dielectric constant. This determines the speed at which waves travel through the first medium they encounter. It is used to calculate the profile depth if depth units are specified on the Z-axis of plots.
    :param bool title: Whether to display descriptive titles on plots. Defaults to :py:data:`True`.
    :param list[int,int,int,int] zoom: Zoom extents to set programmatically for matplotlib plots. Must pass a list of four integers: :py:data:`[left, right, up, down]`. Since the z-axis begins at the top, the "up" value is actually the one that displays lower on the page. All four values are axis units, so if you are working in nanoseconds, 10 will set a limit 10 nanoseconds down. If your x-axis is in seconds, 6 will set a limit 6 seconds from the start of the survey. It may be helpful to display the matplotlib interactive window at full extents first, to determine appropriate extents to set for this parameter. If extents are set outside the boundaries of the image, they will be set back to the boundaries. If two extents on the same axis are the same, the program will default to plotting full extents for that axis.
    :rtype: header (:py:class:`dict`), radar array (:py:class:`numpy.ndarray`), gps (False or :py:class:`pandas.DataFrame`)
    """

    if infile:
        # read the file
        try:
            if verbose:
                fx.printmsg('reading...')
                fx.printmsg('input file:         %s' % (infile))
            r = readdzt(infile,
                        gps=normalize,
                        spm=spm,
                        start_scan=start_scan,
                        num_scans=num_scans,
                        epsr=epsr,
                        verbose=verbose)
            # time zero per channel
            r[0]['timezero'] = [None, None, None, None]
            for i in range(r[0]['rh_nchan']):
                try:
                    r[0]['timezero'][i] = int(list(zero)[i])
                except (TypeError, IndexError):
                    fx.printmsg(
                        'WARNING: no time zero specified for channel %s, defaulting to 2'
                        % i)
                    r[0]['timezero'][i] = 2
            # print a bunch of header info
            if verbose:
                fx.printmsg('success. header values:')
                header_info(r[0], r[1])
        except IOError as e:  # the user has selected an inaccessible or nonexistent file
            fx.printmsg(
                "ERROR: DZT file is inaccessable or does not exist at %s" %
                (os.path.abspath(infile)))
            raise IOError(e)
        infile_ext = os.path.splitext(infile)[1]
        infile_basename = os.path.splitext(infile)[0]
    else:
        raise IOError('ERROR: no input file specified')

    rhf_sps = r[0]['rhf_sps']
    rhf_spm = r[0]['rhf_spm']
    line_dur = r[0]['sec']
    for chan in list(range(r[0]['rh_nchan'])):
        try:
            ANT[r[0]['rh_antname'][chan]]
        except KeyError as e:
            print(
                '--------------------WARNING - PLEASE READ---------------------'
            )
            fx.printmsg(
                'WARNING: could not read frequency for antenna name %s' % e)
            if antfreq:
                fx.printmsg('using user-specified antenna frequency.')
                r[0]['antfreq'] = antfreq
            else:
                fx.printmsg(
                    'WARNING: trying to use frequency of %s MHz (estimated)...'
                    % (r[0]['antfreq'][chan]))
            fx.printmsg('more info: rh_ant=%s' % (r[0]['rh_ant']))
            fx.printmsg('           known_ant=%s' % (r[0]['known_ant']))
            fx.printmsg(
                "please submit a bug report with this warning, the antenna name and frequency"
            )
            fx.printmsg('at https://github.com/iannesbitt/readgssi/issues/new')
            fx.printmsg(
                'or send via email to ian (dot) nesbitt (at) gmail (dot) com.')
            fx.printmsg(
                'if possible, please attach a ZIP file with the offending DZT inside.'
            )
            print(
                '--------------------------------------------------------------'
            )

    # create a list of n arrays, where n is the number of channels
    arr = r[1].astype(np.int32)
    chans = list(range(r[0]['rh_nchan']))

    # set up list of arrays
    img_arr = arr[:r[0]['rh_nchan'] * r[0][
        'rh_nsamp']]  # test if we understand data structure. arrays should be stacked nchan*nsamp high
    new_arr = {}
    for ar in chans:
        a = []
        a = img_arr[(ar) * r[0]['rh_nsamp']:(ar + 1) *
                    r[0]['rh_nsamp']]  # break apart
        new_arr[ar] = a[r[0]['timezero'][ar]:, :int(
            img_arr.shape[1])]  # put into dict form

    img_arr = new_arr  # overwrite
    del arr, new_arr

    for ar in img_arr:
        """
        filter and construct an output file or plot from the current channel's array
        """
        if verbose:
            fx.printmsg('beginning processing for channel %s (antenna %s)' %
                        (ar, r[0]['rh_antname'][ar]))
        # execute filtering functions if necessary
        if normalize:
            r[0], img_arr[ar], r[2] = arrayops.distance_normalize(
                header=r[0], ar=img_arr[ar], gps=r[2], verbose=verbose)
        if dewow:
            # dewow
            img_arr[ar] = filtering.dewow(ar=img_arr[ar], verbose=verbose)
        if freqmin and freqmax:
            # vertical triangular bandpass
            img_arr[ar] = filtering.triangular(ar=img_arr[ar],
                                               header=r[0],
                                               freqmin=freqmin,
                                               freqmax=freqmax,
                                               zerophase=True,
                                               verbose=verbose)
        if stack != 1:
            # horizontal stacking
            img_arr[ar], stack = arrayops.stack(ar=img_arr[ar],
                                                stack=stack,
                                                verbose=verbose)
        else:
            stack = 1
        if bgr:
            # background removal
            img_arr[ar] = filtering.bgr(ar=img_arr[ar],
                                        header=r[0],
                                        win=win,
                                        verbose=verbose)
        else:
            win = None
        if reverse:
            # read array backwards
            img_arr[ar] = arrayops.flip(img_arr[ar], verbose=verbose)

        ## file naming
        # name the output file
        if ar == 0:  # first channel?
            orig_outfile = outfile  # preserve the original
        else:
            outfile = orig_outfile  # recover the original

        if outfile:
            outfile_ext = os.path.splitext(outfile)[1]
            outfile = '%s' % (os.path.join(os.path.splitext(outfile)[0]))
            if len(chans) > 1:
                outfile = '%sc%s' % (outfile, ar)  # avoid naming conflicts
        else:
            outfile = fx.naming(infile_basename=infile_basename,
                                chans=chans,
                                chan=ar,
                                normalize=normalize,
                                zero=r[0]['timezero'][ar],
                                stack=stack,
                                reverse=reverse,
                                bgr=bgr,
                                win=win,
                                dewow=dewow,
                                freqmin=freqmin,
                                freqmax=freqmax,
                                plotting=plotting,
                                gain=gain)

        if plotting:
            plot.radargram(ar=img_arr[ar],
                           header=r[0],
                           freq=r[0]['antfreq'][ar],
                           verbose=verbose,
                           figsize=figsize,
                           dpi=dpi,
                           stack=stack,
                           x=x,
                           z=z,
                           gain=gain,
                           colormap=colormap,
                           colorbar=colorbar,
                           noshow=noshow,
                           outfile=outfile,
                           win=win,
                           title=title,
                           zero=r[0]['timezero'][ar],
                           zoom=zoom)

        if histogram:
            plot.histogram(ar=img_arr[ar], verbose=verbose)

        if specgram:
            plot.spectrogram(ar=img_arr[ar],
                             header=header,
                             freq=r[0]['antfreq'][ar],
                             verbose=verbose)

    if frmt != None:
        if verbose:
            fx.printmsg('outputting to %s...' % frmt)
        for ar in img_arr:
            # is there an output filepath given?
            outfile_abspath = os.path.abspath(
                outfile)  # set output to given location

            # what is the output format
            if frmt in 'csv':
                translate.csv(ar=img_arr[ar],
                              outfile_abspath=outfile_abspath,
                              header=r[0],
                              verbose=verbose)
            elif frmt in 'h5':
                translate.h5(ar=img_arr[ar],
                             infile_basename=infile_basename,
                             outfile_abspath=outfile_abspath,
                             verbose=verbose)
            elif frmt in 'segy':
                translate.segy(ar=img_arr[ar],
                               outfile_abspath=outfile_abspath,
                               verbose=verbose)
            elif frmt in 'numpy':
                translate.numpy(ar=img_arr[ar],
                                outfile_abspath=outfile_abspath,
                                verbose=verbose)
            elif frmt in 'gprpy':
                translate.gprpy(ar=img_arr[ar],
                                outfile_abspath=outfile_abspath,
                                header=r[0],
                                verbose=verbose)
        if frmt in ('object', 'python'):
            return r[0], img_arr, r[2]
コード例 #2
0
ファイル: plot.py プロジェクト: limitswen/readgssi
def radargram(ar, ant, header, freq, figsize='auto', gain=1, stack=1, x='seconds', z='nanoseconds', title=True,
              colormap='gray', colorbar=False, absval=False, noshow=False, win=None, outfile='readgssi_plot', zero=2,
              zoom=[0,0,0,0], dpi=150, showmarks=False, verbose=False):
    """
    Function that creates, modifies, and saves matplotlib plots of radargram images. For usage information, see :doc:`plotting`.

    :param numpy.ndarray ar: The radar array
    :param int ant: Antenna channel number
    :param dict header: Radar file header dictionary
    :param int freq: Antenna frequency
    :param float plotsize: The height of the output plot in inches
    :param float gain: The gain applied to the image. Must be positive but can be between 0 and 1 to reduce gain.
    :param int stack: Number of times the file was stacked horizontally. Used to calculate traces on the X axis.
    :param str x: The units to display on the x-axis during plotting. Defaults to :py:data:`x='seconds'`. Acceptable values are :py:data:`x='distance'` (which sets to meters), :py:data:`'km'`, :py:data:`'m'`, :py:data:`'cm'`, :py:data:`'mm'`, :py:data:`'kilometers'`, :py:data:`'meters'`, etc., for distance; :py:data:`'seconds'`, :py:data:`'s'`, :py:data:`'temporal'` or :py:data:`'time'` for seconds, and :py:data:`'traces'`, :py:data:`'samples'`, :py:data:`'pulses'`, or :py:data:`'columns'` for traces.
    :param str z: The units to display on the z-axis during plotting. Defaults to :py:data:`z='nanoseconds'`. Acceptable values are :py:data:`z='depth'` (which sets to meters), :py:data:`'m'`, :py:data:`'cm'`, :py:data:`'mm'`, :py:data:`'meters'`, etc., for depth; :py:data:`'nanoseconds'`, :py:data:`'ns'`, :py:data:`'temporal'` or :py:data:`'time'` for seconds, and :py:data:`'samples'` or :py:data:`'rows'` for samples.
    :param bool title: Whether to add a title to the figure. Defaults to True.
    :param matplotlib.colors.Colormap colormap: The matplotlib colormap to use, defaults to 'gray' which is to say: the same as the default RADAN colormap
    :param bool colorbar: Whether to draw the colorbar. Defaults to False.
    :param bool absval: Whether to draw the array with an absolute value scale. Defaults to False.
    :param bool noshow: Whether to suppress the matplotlib figure GUI window. Defaults to False, meaning the dialog will be displayed.
    :param int win: Window size for background removal filter :py:func:`readgssi.filtering.bgr` to display in plot title.
    :param str outfile: The name of the output file. Defaults to 'readgssi_plot.png' in the current directory.
    :param int zero: The zero point. This represents the number of samples sliced off the top of the profile by the timezero option in :py:func:`readgssi.readgssi.readgssi`.
    :param list[int,int,int,int] zoom: Zoom extents for matplotlib plots. Must pass a list of four integers: :py:data:`[left, right, up, down]`. Since the z-axis begins at the top, the "up" value is actually the one that displays lower on the page. All four values are axis units, so if you are working in nanoseconds, 10 will set a limit 10 nanoseconds down. If your x-axis is in seconds, 6 will set a limit 6 seconds from the start of the survey. It may be helpful to display the matplotlib interactive window at full extents first, to determine appropriate extents to set for this parameter. If extents are set outside the boundaries of the image, they will be set back to the boundaries. If two extents on the same axis are the same, the program will default to plotting full extents for that axis.
    :param int dpi: The dots per inch value to use when creating images. Defaults to 150.
    :param bool showmarks: Whether to plot user marks as vertical lines. Defaults to False.
    :param bool verbose: Verbose, defaults to False
    """

    # having lots of trouble with this line not being friendly with figsize tuple (integer coercion-related errors)
    # so we will force everything to be integers explicitly
    if figsize != 'auto':
        figx, figy = int(int(figsize)*int(int(ar.shape[1])/int(ar.shape[0]))), int(figsize)-1 # force to integer instead of coerce
        if figy <= 1:
            figy += 1 # avoid zero height error in y dimension
        if figx <= 1:
            figx += 1 # avoid zero height error in x dimension
        if verbose:
            fx.printmsg('plotting %sx%sin image with gain=%s...' % (figx, figy, gain))
        fig, ax = plt.subplots(figsize=(figx, figy), dpi=dpi)
    else:
        if verbose:
            fx.printmsg('plotting with gain=%s...' % gain)
        fig, ax = plt.subplots()

    mean = np.mean(ar)
    if verbose:
        fx.printmsg('image stats')
        fx.printmsg('size:               %sx%s' % (ar.shape[0], ar.shape[1]))
        fx.printmsg('mean:               %.3f' % mean)

    if absval:
        fx.printmsg('plotting absolute value of array gradient')
        ar = np.abs(np.gradient(ar, axis=1))
        flip = 1
        ll = np.min(ar)
        ul = np.max(ar)
        std = np.std(ar)
    else:
        if mean > 1000:
            fx.printmsg('WARNING: mean pixel value is very high. consider filtering with -t')
        flip = 1
        std = np.std(ar)
        ll = mean - (std * 3) # lower color limit
        ul = mean + (std * 3) # upper color limit
        fx.printmsg('stdev:              %.3f' % std)
        fx.printmsg('lower color limit:  %.2f [mean - (3 * stdev)]' % (ll))
        fx.printmsg('upper color limit:  %.2f [mean + (3 * stdev)]' % (ul))

    # X scaling routine
    if (x == None) or (x in 'seconds'): # plot x as time by default
        xmax = header['sec']
        xlabel = 'Time (s)'
    else:
        if (x in ('cm', 'm', 'km')) and (header['rhf_spm'] > 0): # plot as distance based on unit
            xmax = ar.shape[1] / header['rhf_spm']
            if 'cm' in x:
                xmax = xmax * 100.
            if 'km' in x:
                xmax = xmax / 1000.
            xlabel = 'Distance (%s)' % (x)
        else: # else we plot in units of stacked traces
            if header['rhf_spm'] == 0:
                fx.printmsg('samples per meter value is zero. plotting trace numbers instead.')
            xmax = ar.shape[1] # * float(stack)
            xlabel = 'Trace number'
            if stack > 1:
                xlabel = 'Trace number (after %sx stacking)' % (stack)
    # finally, relate max scale value back to array shape in order to set matplotlib axis scaling
    try:
        xscale = ar.shape[1]/xmax
    except ZeroDivisionError:
        fx.printmsg('ERROR: cannot plot x-axis in "%s" mode; header value is zero. using time instead.' % (x))
        xmax = header['sec']
        xlabel = 'Time (s)'
        xscale = ar.shape[1]/xmax

    zmin = 0
    # Z scaling routine
    if (z == None) or (z in 'nanoseconds'): # plot z as time by default
        zmax = header['rhf_range'] #could also do: header['ns_per_zsample'] * ar.shape[0] * 10**10 / 2
        zlabel = 'Two-way time (ns)'
    else:
        if z in ('mm', 'cm', 'm'): # plot z as TWTT based on unit and cr/rhf_epsr value
            zmax = header['rhf_depth'] - header['rhf_top']
            if 'cm' in z:
                zmax = zmax * 100.
            if 'mm' in z:
                zmax = zmax * 1000.
            zlabel = r'Depth at $\epsilon_r$=%.2f (%s)' % (header['rhf_epsr'], z)
        else: # else we plot in units of samples
            zmin = zero
            zmax = ar.shape[0] + zero
            zlabel = 'Sample'
    # finally, relate max scale value back to array shape in order to set matplotlib axis scaling
    try:
        zscale = ar.shape[0]/zmax
    except ZeroDivisionError: # apparently this can happen even in genuine GSSI files
        fx.printmsg('ERROR: cannot plot z-axis in "%s" mode; header max value is zero. using samples instead.' % (z))
        zmax = ar.shape[0]
        zlabel = 'Sample'
        zscale = ar.shape[0]/zmax

    if verbose:
        fx.printmsg('xmax: %.4f %s, zmax: %.4f %s' % (xmax, xlabel, zmax, zlabel))

    extent = [0, xmax, zmax, zmin]

    try:
        if verbose:
            fx.printmsg('attempting to plot with colormap %s' % (colormap))
        img = ax.imshow(ar, cmap=colormap, clim=(ll, ul), interpolation='bicubic', aspect=float(zscale)/float(xscale),
                     norm=colors.SymLogNorm(linthresh=float(std)/float(gain), linscale=flip,
                                            vmin=ll, vmax=ul), extent=extent)
    except:
        fx.printmsg('ERROR: matplotlib did not accept colormap "%s", using gray instead' % colormap)
        fx.printmsg('see examples here: https://matplotlib.org/users/colormaps.html#grayscale-conversion')
        img = ax.imshow(ar, cmap='gray', clim=(ll, ul), interpolation='bicubic', aspect=float(zscale)/float(xscale),
                     norm=colors.SymLogNorm(linthresh=float(std)/float(gain), linscale=flip,
                                            vmin=ll, vmax=ul), extent=extent)

    # user marks
    if showmarks:
        if verbose:
            fx.printmsg('plotting marks at traces: %s' % header['marks'])
        for mark in header['marks']:
            plt.axvline(x=mark/xscale, color='r', linestyle=(0, (14,14)), linewidth=1, alpha=0.7)

    # zooming
    if zoom != [0,0,0,0]: # if zoom is set
        zoom = fx.zoom(zoom=zoom, extent=extent, x=x, z=z, verbose=verbose) # figure out if the user set extents properly
    else:
        zoom = extent # otherwise, zoom is full extents
    if zoom != extent: # if zoom is set correctly, then set new axis limits
        if verbose:
            fx.printmsg('zooming in to %s [xmin, xmax, ymax, ymin]' % zoom)
        ax.set_xlim(zoom[0], zoom[1])
        ax.set_ylim(zoom[2], zoom[3])
        # add zoom extents to file name via the Seth W. Campbell honorary naming scheme
        outfile = fx.naming(outfile=outfile, zoom=[int(i) for i in zoom])

    ax.set_xlabel(xlabel)
    ax.set_ylabel(zlabel)

    if colorbar:
        fig.colorbar(img)
    if title:
        try:
            antfreq = freq[ant]
        except TypeError:
            antfreq = freq
        title = '%s - %s MHz - stacking: %s - gain: %s' % (
                    os.path.basename(header['infile']), antfreq, stack, gain)
        if win:
            if win == 0:
                win = 'full'
            title = '%s - bgr: %s' % (title, win)
        plt.title(title)
    if figx / figy >=1: # if x is longer than y (avoids plotting error where data disappears for some reason)
        plt.tight_layout()#pad=fig.get_size_inches()[1]/4.) # then it's ok to call tight_layout()
    else:
        try:
            # the old way of handling
            #plt.tight_layout(w_pad=2, h_pad=1)

            # the new way of handling
            fx.printmsg('WARNING: axis lengths are funky. using alternative sizing method. please adjust manually in matplotlib gui.')
            figManager = plt.get_current_fig_manager()
            try:
                figManager.window.showMaximized()
            except:
                figManager.resize(*figManager.window.maxsize())
            for item in ([ax.xaxis.label, ax.yaxis.label] +
                        ax.get_xticklabels() + ax.get_yticklabels()):
                item.set_fontsize(5)
            ax.title.set_fontsize(7)
            plt.draw()
            fig.canvas.start_event_loop(0.1)
            plt.tight_layout()
        except:
            fx.printmsg('WARNING: tight_layout() raised an error because axis lengths are funky. please adjust manually in matplotlib gui.')
    if outfile != 'readgssi_plot':
        # if outfile doesn't match this then save fig with the outfile name
        if verbose:
            fx.printmsg('saving figure as %s.png' % (outfile))
        plt.savefig('%s.png' % (outfile), dpi=dpi, bbox_inches='tight')
    else:
        # else someone has called this function from outside and forgotten the outfile field
        if verbose:
            fx.printmsg('saving figure as %s_%sMHz.png with dpi=%s' % (os.path.splitext(header['infile'])[0], freq, dpi))
        plt.savefig('%s_%sMHz.png' % (os.path.splitext(header['infile'])[0], freq), bbox_inches='tight')
    if noshow:
        if verbose:
            fx.printmsg('not showing matplotlib')
        plt.close()
    else:
        if verbose:
            fx.printmsg('showing matplotlib figure...')
        plt.show()