Beispiel #1
0
class RadarDisplay(object):
    _increms = {'left': -1, 'right': 1}

    def do_save_results(self):
        return self._do_save

    def __init__(self, volume, tracks, falarms, polygons, radarFiles):
        """
        Create an interactive display for creating track data
        from radar data.
        """
        minLat, minLon, maxLat, maxLon, volTimes = ConsistentDomain(radarFiles)

        self.fig = plt.figure()
        self.ax = self.fig.gca()
        self.radarData = RadarCache(radarFiles, 4)

        self._increm_funcs = {
            'left': self.radarData.prev,
            'right': self.radarData.next
        }

        self._im = None
        self._curr_selection = None
        self._alphaScale = 1.0
        self.curr_lasso = None
        self.volTimes = volTimes

        self.state = StateManager(volTimes)
        self.state.load_features(tracks, falarms, volume, polygons)

        for feats in self.state._features.values():
            for feat in feats:
                for obj in feat.objects.values():
                    if obj is not None:
                        obj.set_visible(False)
                        self.ax.add_artist(obj)

        for track in self.state._tracks:
            track.obj.set_visible(True)
            self.ax.add_artist(track.obj)

        self.frameIndex = 0
        self._show_features = False
        data = self.radarData.next()

        radarName = data['station']
        if radarName == 'NWRT':
            radarName = 'PAR'
        radarSite = radarsites.ByName(radarName)[0]

        lons, lats = np.meshgrid(data['lons'], data['lats'])
        self.xs, self.ys = LonLat2Cart(radarSite['LON'], radarSite['LAT'],
                                       lons, lats)

        self.update_frame()

        self.ax.set_xlim(self.xs.min(), self.xs.max())
        self.ax.set_ylim(self.ys.min(), self.ys.max())
        self.ax.set_xlabel('X (km)')
        self.ax.set_ylabel('Y (km)')
        self.ax.set_aspect('equal')

        self.fig.canvas.mpl_connect('key_press_event', self.process_key)
        #self.fig.canvas.mpl_connect('button_release_event',
        #                             self.process_click)
        self.fig.canvas.mpl_connect('button_press_event', self.onpress)
        #self.fig.canvas.mpl_connect('pick_event', self.onpick)
        #self._savefeats_cid = self.fig.canvas.mpl_connect('close_event',
        #                                                  self.onclose)
        self._do_save = True

        # Start in outline mode
        self._mode = 'o'

        # Need to remove some keys...
        rcParams['keymap.fullscreen'] = []
        rcParams['keymap.home'].remove('r')
        rcParams['keymap.home'].remove('h')
        rcParams['keymap.back'].remove('left')
        rcParams['keymap.forward'].remove('right')
        rcParams['keymap.forward'].remove('v')
        rcParams['keymap.zoom'] = []
        rcParams['keymap.save'] = []

        print "Welcome to Track Maker! (press 'h' for menu of options)"

    def onpick(self, event):
        """
        Track picker handler
        """
        pass

    #def onclose(self, event) :
    #    """
    #    Trigger a saving of data (Note: this isn't a saving to files,
    #    but to the track lists and volume lists).
    #    """
    #    self.save_features()

    def onlasso(self, verts):
        """
        Creation of the contour polygon, which selects the initial
        region for watershed clustering.
        """
        newPoly = Polygon(verts,
                          lw=2,
                          fc='gray',
                          hatch='/',
                          zorder=1,
                          ec=Feature.orig_colors['contour'],
                          alpha=Feature.orig_alphas['contour'],
                          picker=None)
        self.state.add_feature(Feature(self.frameIndex, contour=newPoly))
        self.fig.canvas.draw_idle()
        self.fig.canvas.widgetlock.release(self.curr_lasso)
        del self.curr_lasso
        self.curr_lasso = None
        self.ax.add_artist(newPoly)

    def onpress(self, event):
        """
        Button-press handler
        """
        if self._mode == 'o':
            # Outline mode
            if self.fig.canvas.widgetlock.locked():
                return
            if event.inaxes is not self.ax:
                return

            self.curr_lasso = Lasso(event.inaxes, (event.xdata, event.ydata),
                                    self.onlasso)

            # Set a lock on drawing the lasso until finished
            self.fig.canvas.widgetlock(self.curr_lasso)

        elif self._mode == 's':
            # Selection mode
            if event.inaxes is not self.ax:
                return

            curr_select = None
            for feat in self.state._features[self.frameIndex]:
                if feat.contains(event):
                    curr_select = feat
            prev_select = self._curr_selection

            if curr_select is not None:
                if prev_select is not None:
                    # TODO: Make a tracking association
                    # TODO: May need some mapping of frameIndex to frameNum
                    # This is only valid if I am able to see the previous
                    # selection. This logic also makes it impossible to
                    # have feat1 be the same object as feat2
                    if (prev_select.get_visible()
                            and self.frameIndex != prev_select.frame):
                        # We are making associations across frames!
                        tmp = self.state.associate_features(
                            prev_select, curr_select)

                        if tmp is not None:
                            self.ax.add_artist(tmp.obj)

                    prev_select.deselect()

                if prev_select is curr_select:
                    self._curr_selection = None
                else:
                    self._curr_selection = curr_select
                    curr_select.select()

                self.fig.canvas.draw_idle()

    def process_key(self, event):
        """
        Key-press handler
        """
        if event.key in RadarDisplay._increms:
            if (0 <= (self.frameIndex + RadarDisplay._increms[event.key]) <=
                (len(self.radarData) - 1)):
                lastFrame = self.frameIndex
                self.frameIndex += RadarDisplay._increms[event.key]

                # Update the radar data
                self._increm_funcs[event.key]()

                #if self._curr_selection is not None :
                #    self._curr_selection.deselect()
                #    self._curr_selection = None

                # Update the frame
                self.update_frame(lastFrame, hold_recluster=True)

        elif event.key == '+':
            self._alphaScale = min(100.0, self._alphaScale * 2)
            self.update_frame(hold_recluster=True)

        elif event.key == '-':
            self._alphaScale = max(0.001, self._alphaScale / 2.0)
            self.update_frame(hold_recluster=True)

        elif event.key == 'r':
            # Recalculate ellipsoids
            self.update_frame(force_recluster=True, hold_recluster=False)

        elif event.key == 'c':
            # Completely remove the features for this frame
            if (self._curr_selection is not None
                    and self._curr_selection.frame == self.frameIndex):
                self._curr_selection.deselect()
                self._curr_selection = None

            self.state.clear_frame(self.frameIndex)

            self.update_frame()

        elif event.key == 'd':
            # Delete the currently selected artist (if in the current frame)
            if (self._curr_selection is not None
                    and self._curr_selection.frame == self.frameIndex):
                self._curr_selection.deselect()
                self._curr_selection.remove()
                self.state.remove_feature(self._curr_selection)
                self._curr_selection = None

            self.fig.canvas.draw_idle()

        elif event.key == 's':
            # set mode to "selection mode"
            self._mode = 's'

            # Just in case the canvas is still locked.
            if self.curr_lasso is not None:
                self.fig.canvas.widgetlock.release(self.curr_lasso)
                del self.curr_lasso
                self.curr_lasso = None
            print "Selection Mode"

        elif event.key == 'o':
            # set mode to "outline mode"
            self._mode = 'o'
            print "Outline Mode"

        elif event.key == 'f':
            # Show/Hide all identified features across time
            self._show_features = (not self._show_features)
            print "Show features:", self._show_features
            self.update_frame(hold_recluster=True)

        elif event.key == 'v':
            # Toogle save
            self._do_save = (not self._do_save)
            print "Do Save:", self._do_save
            #if not self._do_save :
            #    if self._savefeats_cid is not None :
            #        self.fig.canvas.mpl_disconnect(self._savefeats_cid)
            #        self._savefeats_cid = None
            #else :
            #    if self._savefeats_cid is None :
            #        self._savefeats_cid = self.fig.canvas.mpl_connect(
            #                                "close_event", self.onclose)

        #elif event.key == 'V' :
        #    # Save features to memory NOW!
        #    print "Converting to track and volume objects, NOW!"
        #    self.save_features()

        elif event.key == 'h':
            # Print helpful Menu
            print dedent("""
                Track Maker
                ===========

                Key         Action
                ------      -----------------------------
                h           Show this helpful menu
                right       Step forward by one frame
                left        Step back by one frame
                +, -        Rescale transparency as a function of time
                o           Outline mode
                s           Selection mode
                r           (re)cluster this frame
                c           clear this frame of existing features
                d           delete the currently selected feature
                s           show/hide all features across all time
                v           toggle saving features upon closing figure
                                (this is useful if you detect an error and
                                 do not want to save bad data).

                Current Values
                --------------
                    Current Frame: %d of %d
                    Current Mode: %s
                    Do save upon figure close: %s
                    Show all features: %s
                """ % (self.frameIndex + 1, len(self.radarData), self._mode,
                       self._do_save, self._show_features))

    def _clear_frame(self, frame=None):
        if frame is None:
            frame = self.frameIndex

        # Set the frame's features to invisible
        for feat in self.state._features[frame]:
            feat.set_visible(False)
            # Also reset their alpha values
            feat.set_alpha(1.0)
            #feat.set_picker(None)

    def get_clusters(self):
        dataset = self.radarData.curr()
        data = dataset['vals'][0]

        flat_data = data[data >= -40]

        clustLabels = np.empty(data.shape, dtype=int)
        clustLabels[:] = -1

        if np.nanmin(flat_data) == np.nanmax(flat_data):
            # can't cluster data with no change
            return clustLabels, 0

        bad_data = (np.isnan(data) | (data <= 0.0))

        bins = np.linspace(np.nanmin(flat_data), np.nanmax(flat_data), 2**8)
        data_digitized = np.digitize(data.flat, bins)
        data_digitized.shape = data.shape
        data_digitized = data_digitized.astype('uint8')

        markers = np.zeros(data.shape, dtype=int)

        for index, feat in enumerate(self.state._features[self.frameIndex]):
            if 'contour' in feat.objects:
                contr = feat.objects['contour']
                res = points_inside_poly(zip(self.xs.flat, self.ys.flat),
                                         contr.get_xy())
                res.shape = self.xs.shape
                markers[res] = index + 1

            # No contour available? Then fall back to just a point
            elif 'center' in feat.objects:
                cent = feat.objects['center']
                gridx, gridy = self._xy2grid(cent.center[0], cent.center[1])
                markers[gridy, gridx] = index + 1

            # TODO: work from an ellipse, if it exists?
            else:
                raise ValueError("Empty feature?")

        markers[bad_data] = -1
        ndimg.watershed_ift(data_digitized, markers, output=clustLabels)
        clustCnt = len(self.state._features[self.frameIndex])

        cents = ndimg.center_of_mass(data**2, clustLabels,
                                     range(1, clustCnt + 1))
        ellipses = FitEllipses(clustLabels, range(1, clustCnt + 1), self.xs,
                               self.ys)

        for center, ellip, feat in zip(cents, ellipses,
                                       self.state._features[self.frameIndex]):
            # Remove any other objects that may exist before adding
            # new objects to the feature.
            feat.cleanup(['contour'])

            if ellip is None:
                continue

            cent_indx = tuple(np.floor(center).astype(int).tolist())
            # TODO: clean this up!
            newPoint = self.state._new_point(self.xs[cent_indx],
                                             self.ys[cent_indx])
            self.ax.add_artist(ellip)
            self.ax.add_artist(newPoint)

            feat.objects['center'] = newPoint
            feat.objects['ellip'] = ellip

            if feat.track is not None:
                feat.track.update_frame(self.frameIndex)

        #print "clust count:", clustCnt
        return clustLabels, clustCnt

    def _xy2grid(self, x, y):
        return (self.xs[0].searchsorted(x), self.ys[:, 0].searchsorted(y))

    def update_frame(self,
                     lastFrame=None,
                     force_recluster=False,
                     hold_recluster=False):
        """
        Redraw the current frame.  Calculate clusters if needed.

        *lastFrame*         int (None)
            If specified, make this frame's features invisible.

        *force_recluster*   boolean (False)
            If True, do a recluster, even if it seems like it isn't needed.
            Can be over-ridden by *hold_recluster*.

        *hold_recluster*    boolean (False)
            If True, then don't do a recluster, even if needed or
            *force_recluster* is True.
        """
        if lastFrame is not None:
            self._clear_frame(lastFrame)

        data = self.radarData.curr()

        # Display current frame's radar image
        if self._im is None:
            self._im = MakeReflectPPI(data['vals'][0],
                                      self.ys,
                                      self.xs,
                                      meth='pcmesh',
                                      ax=self.ax,
                                      colorbar=False,
                                      axis_labels=False,
                                      zorder=0,
                                      mask=False)
        else:
            self._im.set_array(data['vals'][0, :-1, :-1].flatten())

        if force_recluster or any(
            [('center' not in feat.objects)
             for feat in self.state._features[self.frameIndex]]):
            if not hold_recluster:
                clustLabels, clustCnt = self.get_clusters()

        # Set features for this frame to visible
        for feat in self.state._features[self.frameIndex]:
            feat.set_visible(True)
            # Return alpha back to normal
            feat.set_alpha(1.0)
            # Put it on top
            feat.set_zorder(len(self.radarData))
            #feat.set_picker(True)

        # Show the other features
        if self._show_features:
            # How much alpha should change for each frame from frameIndex
            # The closer to self.frameIndex, the more opaque
            alphaIncrem = 1.0 / len(self.radarData)
            for frameIndex, features in self.state._features.iteritems():
                if frameIndex == self.frameIndex:
                    continue

                framesFrom = np.abs(self.frameIndex - frameIndex)
                timeAlpha = ((1.0 -
                              (framesFrom * alphaIncrem))**(1.0 /
                                                            self._alphaScale))
                zorder = len(self.radarData) - framesFrom
                for feat in features:
                    feat.set_visible(True)
                    feat.set_alpha(timeAlpha)
                    feat.set_zorder(zorder)
        else:
            # Make sure that these items are hidden
            for frameIndex, features in self.state._features.iteritems():
                if frameIndex != self.frameIndex:
                    for feat in features:
                        feat.set_visible(False)
                        # Return alpha to normal
                        feat.set_alpha(1.0)

        if self.volTimes[self.frameIndex] is None:
            theDateTime = datetime.utcfromtimestamp(data['scan_time'])
            self.volTimes[self.frameIndex] = theDateTime
        else:
            theDateTime = self.volTimes[self.frameIndex]

        self.ax.set_title(theDateTime.strftime("%Y/%m/%d %H:%M:%S"))
        self.fig.canvas.draw_idle()
Beispiel #2
0
def main(args):
    if args.bw_mode:
        BW_mode()

    if len(args.trackFiles) == 0: print "WARNING: No trackFiles given!"

    if args.trackTitles is None:
        args.trackTitles = args.trackFiles
    else:
        if len(args.trackTitles) != len(args.trackFiles):
            raise ValueError("The number of TITLEs do not match the"
                             " number of TRACKFILEs.")

    if args.statName is not None and args.statLonLat is None:
        statData = ByName(args.statName)[0]
        args.statLonLat = (statData['LON'], statData['LAT'])

    if args.layout is None:
        args.layout = (1, len(args.trackFiles))

    if args.figsize is None:
        args.figsize = plt.figaspect(float(args.layout[0]) / args.layout[1])

    trackerData = [
        FilterMHTTracks(*ReadTracks(trackFile))
        for trackFile in args.trackFiles
    ]
    polyData = [
        _load_verts(f, tracks + falarms)
        for f, (tracks, falarms) in zip(polyfiles, trackerData)
    ]

    if args.statLonLat is not None:
        for aTracker in trackerData:
            CoordinateTransform(aTracker[0] + aTracker[1], args.statLonLat[0],
                                args.statLonLat[1])

        for polys in polyData:
            CoordinateTrans_lists(polys, args.statLonLat[0],
                                  args.statLonLat[1])

    if args.simTagFiles is None:
        args.simTagFiles = [None]

    multiTags = [
        ReadSimTagFile(fname) if fname is not None else None
        for fname in args.simTagFiles
    ]

    if len(trackerData) > len(multiTags):
        # Very rudimentary broadcasting of multiTags to match trackerData
        tagMult = max(int(len(trackerData) // len(multiTags)), 1)
        multiTags = multiTags * tagMult

    theFig = plt.figure(figsize=args.figsize)
    grid = AxesGrid(theFig,
                    111,
                    nrows_ncols=args.layout,
                    aspect=False,
                    share_all=True,
                    axes_pad=0.45)

    showMap = (args.statLonLat is not None and args.displayMap)

    # Can only do this if all other data being displayed will be in
    # lon/lat coordinates
    if args.radarFile is not None and args.statLonLat is not None:
        if len(args.radarFile) > 1 and args.endFrame is not None:
            args.radarFile = args.radarFile[args.endFrame]
        else:
            args.radarFile = args.radarFile[-1]

        data = LoadRastRadar(args.radarFile)
        for ax in grid:
            MakeReflectPPI(data['vals'][0],
                           data['lats'],
                           data['lons'],
                           meth='pcmesh',
                           ax=ax,
                           colorbar=False,
                           axis_labels=False,
                           zorder=0,
                           alpha=0.6)

    MakeTrackPlots(grid,
                   trackerData,
                   args.trackTitles,
                   showMap,
                   endFrame=args.endFrame,
                   tail=args.tail,
                   fade=args.fade,
                   multiTags=multiTags,
                   tag_filters=args.filters)

    for ax, verts in zip(grid, polyData):
        _to_polygons(verts[args.endFrame:args.endFrame + 1], ax)

    if args.xlims is not None and np.prod(grid.get_geometry()) > 0:
        grid[0].set_xlim(args.xlims)

    if args.ylims is not None and np.prod(grid.get_geometry()) > 0:
        grid[0].set_ylim(args.ylims)

    if args.saveImgFile is not None:
        theFig.savefig(args.saveImgFile)

    if args.doShow:
        plt.show()
Beispiel #3
0
    def update_frame(self,
                     lastFrame=None,
                     force_recluster=False,
                     hold_recluster=False):
        """
        Redraw the current frame.  Calculate clusters if needed.

        *lastFrame*         int (None)
            If specified, make this frame's features invisible.

        *force_recluster*   boolean (False)
            If True, do a recluster, even if it seems like it isn't needed.
            Can be over-ridden by *hold_recluster*.

        *hold_recluster*    boolean (False)
            If True, then don't do a recluster, even if needed or
            *force_recluster* is True.
        """
        if lastFrame is not None:
            self._clear_frame(lastFrame)

        data = self.radarData.curr()

        # Display current frame's radar image
        if self._im is None:
            self._im = MakeReflectPPI(data['vals'][0],
                                      self.ys,
                                      self.xs,
                                      meth='pcmesh',
                                      ax=self.ax,
                                      colorbar=False,
                                      axis_labels=False,
                                      zorder=0,
                                      mask=False)
        else:
            self._im.set_array(data['vals'][0, :-1, :-1].flatten())

        if force_recluster or any(
            [('center' not in feat.objects)
             for feat in self.state._features[self.frameIndex]]):
            if not hold_recluster:
                clustLabels, clustCnt = self.get_clusters()

        # Set features for this frame to visible
        for feat in self.state._features[self.frameIndex]:
            feat.set_visible(True)
            # Return alpha back to normal
            feat.set_alpha(1.0)
            # Put it on top
            feat.set_zorder(len(self.radarData))
            #feat.set_picker(True)

        # Show the other features
        if self._show_features:
            # How much alpha should change for each frame from frameIndex
            # The closer to self.frameIndex, the more opaque
            alphaIncrem = 1.0 / len(self.radarData)
            for frameIndex, features in self.state._features.iteritems():
                if frameIndex == self.frameIndex:
                    continue

                framesFrom = np.abs(self.frameIndex - frameIndex)
                timeAlpha = ((1.0 -
                              (framesFrom * alphaIncrem))**(1.0 /
                                                            self._alphaScale))
                zorder = len(self.radarData) - framesFrom
                for feat in features:
                    feat.set_visible(True)
                    feat.set_alpha(timeAlpha)
                    feat.set_zorder(zorder)
        else:
            # Make sure that these items are hidden
            for frameIndex, features in self.state._features.iteritems():
                if frameIndex != self.frameIndex:
                    for feat in features:
                        feat.set_visible(False)
                        # Return alpha to normal
                        feat.set_alpha(1.0)

        if self.volTimes[self.frameIndex] is None:
            theDateTime = datetime.utcfromtimestamp(data['scan_time'])
            self.volTimes[self.frameIndex] = theDateTime
        else:
            theDateTime = self.volTimes[self.frameIndex]

        self.ax.set_title(theDateTime.strftime("%Y/%m/%d %H:%M:%S"))
        self.fig.canvas.draw_idle()
Beispiel #4
0
def main(args):
    import os.path
    import matplotlib.pyplot as plt
    from mpl_toolkits.axes_grid1 import AxesGrid

    if args.bw_mode:
        BW_mode()  # from TrackPlot module

    # FIXME: Currently, the code allows for trackFiles to be listed as well
    #        as providing a simulation (which trackfiles are automatically
    #        grabbed). Both situations can not be handled right now, though.
    trackFiles = []
    trackTitles = []

    if args.simName is not None:
        dirName = os.path.join(args.directory, args.simName)
        simParams = ParamUtils.ReadSimulationParams(
            os.path.join(dirName, "simParams.conf"))

        if args.trackRuns is not None:
            simParams['trackers'] = ExpandTrackRuns(simParams['trackers'],
                                                    args.trackRuns)

        trackFiles = [
            os.path.join(dirName, simParams['result_file'] + '_' + aTracker)
            for aTracker in simParams['trackers']
        ]
        if args.trackTitles is None:
            trackTitles = simParams['trackers']
        else:
            trackTitles = args.trackTitles

        if args.truthTrackFile is None:
            args.truthTrackFile = os.path.join(dirName,
                                               simParams['noisyTrackFile'])

        if args.simTagFile is None:
            args.simTagFile = os.path.join(dirName, simParams['simTagFile'])

    trackFiles += args.trackFiles

    if args.trackTitles is None:
        trackTitles += args.trackFiles
    else:
        trackTitles += args.trackTitles

    if len(trackFiles) == 0: print "WARNING: No trackFiles given or found!"

    if len(trackTitles) != len(trackFiles):
        raise ValueError("The number of TITLEs do not match the"
                         " number of TRACKFILEs.")

    if args.statName is not None and args.statLonLat is None:
        statData = ByName(args.statName)[0]
        args.statLonLat = (statData['LON'], statData['LAT'])

    if args.layout is None:
        args.layout = (1, len(trackFiles))

    if args.figsize is None:
        args.figsize = plt.figaspect(float(args.layout[0]) / args.layout[1])

    polyfiles = args.polys

    trackerData = [
        FilterMHTTracks(*ReadTracks(trackFile)) for trackFile in trackFiles
    ]
    polyData = [
        _load_verts(f, tracks + falarms)
        for f, (tracks, falarms) in zip(polyfiles, trackerData)
    ]

    keeperIDs = None

    if args.simTagFile is not None:
        simTags = ParamUtils.ReadSimTagFile(args.simTagFile)
        keeperIDs = ParamUtils.process_tag_filters(simTags, args.filters)

    if args.statLonLat is not None:
        for aTracker in trackerData:
            CoordinateTransform(aTracker[0] + aTracker[1], args.statLonLat[0],
                                args.statLonLat[1])

        for polys in polyData:
            CoordinateTrans_lists(polys, args.statLonLat[0],
                                  args.statLonLat[1])

    theFig = plt.figure(figsize=args.figsize)
    grid = AxesGrid(theFig,
                    111,
                    nrows_ncols=args.layout,
                    aspect=False,
                    share_all=True,
                    axes_pad=0.35)

    if args.truthTrackFile is not None:
        (true_tracks,
         true_falarms) = FilterMHTTracks(*ReadTracks(args.truthTrackFile))

        if args.statLonLat is not None:
            CoordinateTransform(true_tracks + true_falarms, args.statLonLat[0],
                                args.statLonLat[1])

        true_AssocSegs = CreateSegments(true_tracks)
        true_FAlarmSegs = CreateSegments(true_falarms)

        if keeperIDs is not None:
            true_AssocSegs = FilterSegments(keeperIDs, true_AssocSegs)
            true_FAlarmSegs = FilterSegments(keeperIDs, true_FAlarmSegs)

        (xLims, yLims,
         frameLims) = DomainFromTracks(true_tracks + true_falarms)
    else:
        true_AssocSegs = None
        true_FAlarmSegs = None

        stackedTracks = []
        for aTracker in trackerData:
            stackedTracks += aTracker[0] + aTracker[1]
        (xLims, yLims, frameLims) = DomainFromTracks(stackedTracks)

    endFrame = args.endFrame
    tail = args.tail

    if endFrame is None:
        endFrame = frameLims[1]

    if tail is None:
        tail = endFrame - frameLims[0]

    startFrame = endFrame - tail

    showMap = (args.statLonLat is not None and args.displayMap)

    if args.radarFile is not None and args.statLonLat is not None:
        if len(args.radarFile) > 1 and args.endFrame is not None:
            args.radarFile = args.radarFile[args.endFrame]
        else:
            args.radarFile = args.radarFile[-1]

        raddata = LoadRastRadar(args.radarFile)
    else:
        raddata = None

    if showMap:
        bmap = Basemap(projection='cyl',
                       resolution='i',
                       suppress_ticks=False,
                       llcrnrlat=yLims[0],
                       llcrnrlon=xLims[0],
                       urcrnrlat=yLims[1],
                       urcrnrlon=xLims[1])

    for index, (tracks, falarms) in enumerate(trackerData):
        curAxis = grid[index]

        if raddata is not None:
            MakeReflectPPI(raddata['vals'][0],
                           raddata['lats'],
                           raddata['lons'],
                           meth='pcmesh',
                           ax=curAxis,
                           colorbar=False,
                           axis_labels=False,
                           zorder=0,
                           alpha=0.6)

        if showMap:
            PlotMapLayers(bmap, mapLayers, curAxis)

        if true_AssocSegs is not None and true_FAlarmSegs is not None:
            trackAssocSegs = CreateSegments(tracks)
            trackFAlarmSegs = CreateSegments(falarms)

            if keeperIDs is not None:
                trackAssocSegs = FilterSegments(keeperIDs, trackAssocSegs)
                trackFAlarmSegs = FilterSegments(keeperIDs, trackFAlarmSegs)

            truthtable = CompareSegments(true_AssocSegs, true_FAlarmSegs,
                                         trackAssocSegs, trackFAlarmSegs)
            PlotSegments(truthtable, (startFrame, endFrame),
                         axis=curAxis,
                         fade=args.fade)
        else:
            if keeperIDs is not None:
                filtFunc = lambda trk: FilterTrack(trk, cornerIDs=keeperIDs)
                tracks = map(filtFunc, tracks)
                falarms = map(filtFunc, falarms)
                CleanupTracks(tracks, falarms)

            PlotPlainTracks(tracks,
                            falarms,
                            startFrame,
                            endFrame,
                            axis=curAxis,
                            fade=args.fade)

        #curAxis.set_xlim(xLims)
        #curAxis.set_ylim(yLims)
        #curAxis.set_aspect("equal", 'datalim')
        #curAxis.set_aspect("equal")
        curAxis.set_title(trackTitles[index])
        if not showMap:
            curAxis.set_xlabel("X")
            curAxis.set_ylabel("Y")
        else:
            curAxis.set_xlabel("Longitude")
            curAxis.set_ylabel("Latitude")

    for ax, verts in zip(grid, polyData):
        _to_polygons(verts[endFrame:endFrame + 1], ax)

    if args.xlims is not None and np.prod(grid.get_geometry()) > 0:
        grid[0].set_xlim(args.xlims)

    if args.ylims is not None and np.prod(grid.get_geometry()) > 0:
        grid[0].set_ylim(args.ylims)

    if args.saveImgFile is not None:
        theFig.savefig(args.saveImgFile, bbox_inches='tight')

    if args.doShow:
        plt.show()
Beispiel #5
0
class RadarDisplay(object) :
    _increms = {'left': -1, 'right': 1}

    def do_save_results(self) :
        return self._do_save

    def __init__(self, volume, tracks, falarms, polygons, radarFiles) :
        """
        Create an interactive display for creating track data
        from radar data.
        """
        minLat, minLon, maxLat, maxLon, volTimes = ConsistentDomain(radarFiles)


        self.fig = plt.figure()
        self.ax = self.fig.gca()
        self.radarData = RadarCache(radarFiles, 4)
        
        self._increm_funcs = {'left': self.radarData.prev,
                              'right': self.radarData.next}

        self._im = None
        self._curr_selection = None
        self._alphaScale = 1.0
        self.curr_lasso = None
        self.volTimes = volTimes

        self.state = StateManager(volTimes)
        self.state.load_features(tracks, falarms, volume, polygons)

        for feats in self.state._features.values() :
            for feat in feats :
                for obj in feat.objects.values() :
                    if obj is not None :
                        obj.set_visible(False)
                        self.ax.add_artist(obj)

        for track in self.state._tracks :
            track.obj.set_visible(True)
            self.ax.add_artist(track.obj)

        self.frameIndex = 0
        self._show_features = False
        data = self.radarData.next()

        radarName = data['station']
        if radarName == 'NWRT' :
            radarName = 'PAR'
        radarSite = radarsites.ByName(radarName)[0]

        lons, lats = np.meshgrid(data['lons'], data['lats'])
        self.xs, self.ys = LonLat2Cart(radarSite['LON'],
                                       radarSite['LAT'],
                                        lons, lats)

        self.update_frame()

        self.ax.set_xlim(self.xs.min(), self.xs.max())
        self.ax.set_ylim(self.ys.min(), self.ys.max())
        self.ax.set_xlabel('X (km)')
        self.ax.set_ylabel('Y (km)')
        self.ax.set_aspect('equal')


        self.fig.canvas.mpl_connect('key_press_event', self.process_key)
        #self.fig.canvas.mpl_connect('button_release_event',
        #                             self.process_click)
        self.fig.canvas.mpl_connect('button_press_event', self.onpress)
        #self.fig.canvas.mpl_connect('pick_event', self.onpick)
        #self._savefeats_cid = self.fig.canvas.mpl_connect('close_event',
        #                                                  self.onclose)
        self._do_save = True

        # Start in outline mode
        self._mode = 'o'

        # Need to remove some keys...
        rcParams['keymap.fullscreen'] = []
        rcParams['keymap.home'].remove('r')
        rcParams['keymap.home'].remove('h')
        rcParams['keymap.back'].remove('left')
        rcParams['keymap.forward'].remove('right')
        rcParams['keymap.forward'].remove('v')
        rcParams['keymap.zoom'] = []
        rcParams['keymap.save'] = []

        print "Welcome to Track Maker! (press 'h' for menu of options)"

    def onpick(self, event) :
        """
        Track picker handler
        """
        pass

    #def onclose(self, event) :
    #    """
    #    Trigger a saving of data (Note: this isn't a saving to files,
    #    but to the track lists and volume lists).
    #    """
    #    self.save_features()


    def onlasso(self, verts) :
        """
        Creation of the contour polygon, which selects the initial
        region for watershed clustering.
        """
        newPoly = Polygon(verts, lw=2, fc='gray', hatch='/', zorder=1,
                          ec=Feature.orig_colors['contour'],
                          alpha=Feature.orig_alphas['contour'], picker=None)
        self.state.add_feature(Feature(self.frameIndex, contour=newPoly))
        self.fig.canvas.draw_idle()
        self.fig.canvas.widgetlock.release(self.curr_lasso)
        del self.curr_lasso
        self.curr_lasso = None
        self.ax.add_artist(newPoly)

    def onpress(self, event) :
        """
        Button-press handler
        """
        if self._mode == 'o' :
            # Outline mode
            if self.fig.canvas.widgetlock.locked() :
                return
            if event.inaxes is not self.ax :
                return

            self.curr_lasso = Lasso(event.inaxes, (event.xdata, event.ydata),
                                    self.onlasso)

            # Set a lock on drawing the lasso until finished
            self.fig.canvas.widgetlock(self.curr_lasso)

        elif self._mode == 's':
            # Selection mode
            if event.inaxes is not self.ax :
                return

            curr_select = None
            for feat in self.state._features[self.frameIndex] :
                if feat.contains(event) :
                    curr_select = feat
            prev_select = self._curr_selection

            if curr_select is not None :
                if prev_select is not None :
                    # TODO: Make a tracking association
                    # TODO: May need some mapping of frameIndex to frameNum
                    # This is only valid if I am able to see the previous
                    # selection. This logic also makes it impossible to
                    # have feat1 be the same object as feat2
                    if (prev_select.get_visible() and
                        self.frameIndex != prev_select.frame) :
                        # We are making associations across frames!
                        tmp = self.state.associate_features(prev_select,
                                                            curr_select)

                        if tmp is not None :
                            self.ax.add_artist(tmp.obj)

                    prev_select.deselect()

                if prev_select is curr_select :
                    self._curr_selection = None
                else :
                    self._curr_selection = curr_select
                    curr_select.select()

                self.fig.canvas.draw_idle()


    def process_key(self, event) :
        """
        Key-press handler
        """
        if event.key in RadarDisplay._increms :
            if (0 <= (self.frameIndex + RadarDisplay._increms[event.key])
                  <= (len(self.radarData) - 1)) :
                lastFrame = self.frameIndex
                self.frameIndex += RadarDisplay._increms[event.key]

                # Update the radar data
                self._increm_funcs[event.key]()

                #if self._curr_selection is not None :
                #    self._curr_selection.deselect()
                #    self._curr_selection = None

                # Update the frame
                self.update_frame(lastFrame, hold_recluster=True)

        elif event.key == '+' :
            self._alphaScale = min(100.0, self._alphaScale * 2)
            self.update_frame(hold_recluster=True)

        elif event.key == '-' :
            self._alphaScale = max(0.001, self._alphaScale / 2.0)
            self.update_frame(hold_recluster=True)

        elif event.key == 'r' :
            # Recalculate ellipsoids
            self.update_frame(force_recluster=True, hold_recluster=False)

        elif event.key == 'c' :
            # Completely remove the features for this frame
            if (self._curr_selection is not None and
                self._curr_selection.frame == self.frameIndex) :
                self._curr_selection.deselect()
                self._curr_selection = None

            self.state.clear_frame(self.frameIndex)

            self.update_frame()

        elif event.key == 'd' :
            # Delete the currently selected artist (if in the current frame)
            if (self._curr_selection is not None and
                self._curr_selection.frame == self.frameIndex) :
                self._curr_selection.deselect()
                self._curr_selection.remove()
                self.state.remove_feature(self._curr_selection)
                self._curr_selection = None

            self.fig.canvas.draw_idle()

        elif event.key == 's' :
            # set mode to "selection mode"
            self._mode = 's'

            # Just in case the canvas is still locked.
            if self.curr_lasso is not None :
                self.fig.canvas.widgetlock.release(self.curr_lasso)
                del self.curr_lasso
                self.curr_lasso = None
            print "Selection Mode"

        elif event.key == 'o' :
            # set mode to "outline mode"
            self._mode = 'o'
            print "Outline Mode"

        elif event.key == 'f' :
            # Show/Hide all identified features across time
            self._show_features = (not self._show_features)
            print "Show features:", self._show_features
            self.update_frame(hold_recluster=True)

        elif event.key == 'v' :
            # Toogle save
            self._do_save = (not self._do_save)
            print "Do Save:", self._do_save
            #if not self._do_save :
            #    if self._savefeats_cid is not None :
            #        self.fig.canvas.mpl_disconnect(self._savefeats_cid)
            #        self._savefeats_cid = None
            #else :
            #    if self._savefeats_cid is None :
            #        self._savefeats_cid = self.fig.canvas.mpl_connect(
            #                                "close_event", self.onclose)

        #elif event.key == 'V' :
        #    # Save features to memory NOW!
        #    print "Converting to track and volume objects, NOW!"
        #    self.save_features()

        elif event.key == 'h' :
            # Print helpful Menu
            print dedent("""
                Track Maker
                ===========

                Key         Action
                ------      -----------------------------
                h           Show this helpful menu
                right       Step forward by one frame
                left        Step back by one frame
                +, -        Rescale transparency as a function of time
                o           Outline mode
                s           Selection mode
                r           (re)cluster this frame
                c           clear this frame of existing features
                d           delete the currently selected feature
                s           show/hide all features across all time
                v           toggle saving features upon closing figure
                                (this is useful if you detect an error and
                                 do not want to save bad data).

                Current Values
                --------------
                    Current Frame: %d of %d
                    Current Mode: %s
                    Do save upon figure close: %s
                    Show all features: %s
                """ % (self.frameIndex + 1, len(self.radarData), self._mode,
                       self._do_save, self._show_features))


    def _clear_frame(self, frame=None) :
        if frame is None :
            frame = self.frameIndex

        # Set the frame's features to invisible
        for feat in self.state._features[frame] :
            feat.set_visible(False)
            # Also reset their alpha values
            feat.set_alpha(1.0)
            #feat.set_picker(None)

    def get_clusters(self) :
        dataset = self.radarData.curr()
        data = dataset['vals'][0]

        flat_data = data[data >= -40]

        clustLabels = np.empty(data.shape, dtype=int)
        clustLabels[:] = -1

        if np.nanmin(flat_data) == np.nanmax(flat_data) :
            # can't cluster data with no change
            return clustLabels, 0

        bad_data = (np.isnan(data) | (data <= 0.0))

        bins = np.linspace(np.nanmin(flat_data),
                           np.nanmax(flat_data), 2**8)
        data_digitized = np.digitize(data.flat, bins)
        data_digitized.shape = data.shape
        data_digitized = data_digitized.astype('uint8')

        markers = np.zeros(data.shape, dtype=int)

        for index, feat in enumerate(self.state._features[self.frameIndex]) :
            if 'contour' in feat.objects :
                contr = feat.objects['contour']
                res = points_inside_poly(zip(self.xs.flat, self.ys.flat),
                                         contr.get_xy())
                res.shape = self.xs.shape
                markers[res] = index + 1

            # No contour available? Then fall back to just a point
            elif 'center' in feat.objects :
                cent = feat.objects['center']
                gridx, gridy = self._xy2grid(cent.center[0], cent.center[1])
                markers[gridy, gridx] = index + 1

            # TODO: work from an ellipse, if it exists?
            else :
                raise ValueError("Empty feature?")


        markers[bad_data] = -1
        ndimg.watershed_ift(data_digitized, markers, output=clustLabels)
        clustCnt = len(self.state._features[self.frameIndex])

        cents = ndimg.center_of_mass(data**2, clustLabels,
                                     range(1, clustCnt + 1))
        ellipses = FitEllipses(clustLabels, range(1, clustCnt + 1),
                               self.xs, self.ys)

        for center, ellip, feat in zip(cents, ellipses,
                                       self.state._features[self.frameIndex]) :
            # Remove any other objects that may exist before adding
            # new objects to the feature.
            feat.cleanup(['contour'])

            if ellip is None :
                continue

            cent_indx = tuple(np.floor(center).astype(int).tolist())
            # TODO: clean this up!
            newPoint = self.state._new_point(self.xs[cent_indx],
                                             self.ys[cent_indx])
            self.ax.add_artist(ellip)
            self.ax.add_artist(newPoint)

            feat.objects['center'] = newPoint
            feat.objects['ellip'] = ellip

            if feat.track is not None :
                feat.track.update_frame(self.frameIndex)

        #print "clust count:", clustCnt
        return clustLabels, clustCnt

    def _xy2grid(self, x, y) :
        return (self.xs[0].searchsorted(x),
                self.ys[:, 0].searchsorted(y))


    def update_frame(self, lastFrame=None,
                           force_recluster=False, hold_recluster=False) :
        """
        Redraw the current frame.  Calculate clusters if needed.

        *lastFrame*         int (None)
            If specified, make this frame's features invisible.

        *force_recluster*   boolean (False)
            If True, do a recluster, even if it seems like it isn't needed.
            Can be over-ridden by *hold_recluster*.

        *hold_recluster*    boolean (False)
            If True, then don't do a recluster, even if needed or
            *force_recluster* is True.
        """
        if lastFrame is not None :
            self._clear_frame(lastFrame)

        data = self.radarData.curr()

        # Display current frame's radar image
        if self._im is None :
            self._im = MakeReflectPPI(data['vals'][0], self.ys, self.xs,
                                      meth='pcmesh', ax=self.ax,
                                      colorbar=False,
                                      axis_labels=False, zorder=0, mask=False)
        else :
            self._im.set_array(data['vals'][0, :-1, :-1].flatten())

        if force_recluster or any([('center' not in feat.objects) for
                            feat in self.state._features[self.frameIndex]]) :
            if not hold_recluster :
                clustLabels, clustCnt = self.get_clusters()

        # Set features for this frame to visible
        for feat in self.state._features[self.frameIndex] :
            feat.set_visible(True)
            # Return alpha back to normal
            feat.set_alpha(1.0)
            # Put it on top
            feat.set_zorder(len(self.radarData))
            #feat.set_picker(True)

        # Show the other features
        if self._show_features :
            # How much alpha should change for each frame from frameIndex
            # The closer to self.frameIndex, the more opaque
            alphaIncrem = 1.0 / len(self.radarData)
            for frameIndex, features in self.state._features.iteritems() :
                if frameIndex == self.frameIndex :
                    continue

                framesFrom = np.abs(self.frameIndex - frameIndex)
                timeAlpha = ((1.0 - (framesFrom * alphaIncrem)) **
                             (1.0/self._alphaScale))
                zorder = len(self.radarData) - framesFrom
                for feat in features :
                    feat.set_visible(True)
                    feat.set_alpha(timeAlpha)
                    feat.set_zorder(zorder)
        else :
            # Make sure that these items are hidden
            for frameIndex, features in self.state._features.iteritems() :
                if frameIndex != self.frameIndex :
                    for feat in features :
                        feat.set_visible(False)
                        # Return alpha to normal
                        feat.set_alpha(1.0)

        if self.volTimes[self.frameIndex] is None :
            theDateTime = datetime.utcfromtimestamp(data['scan_time'])
            self.volTimes[self.frameIndex] = theDateTime
        else :
            theDateTime = self.volTimes[self.frameIndex]

        self.ax.set_title(theDateTime.strftime("%Y/%m/%d %H:%M:%S"))
        self.fig.canvas.draw_idle()
Beispiel #6
0
    def update_frame(self, lastFrame=None,
                           force_recluster=False, hold_recluster=False) :
        """
        Redraw the current frame.  Calculate clusters if needed.

        *lastFrame*         int (None)
            If specified, make this frame's features invisible.

        *force_recluster*   boolean (False)
            If True, do a recluster, even if it seems like it isn't needed.
            Can be over-ridden by *hold_recluster*.

        *hold_recluster*    boolean (False)
            If True, then don't do a recluster, even if needed or
            *force_recluster* is True.
        """
        if lastFrame is not None :
            self._clear_frame(lastFrame)

        data = self.radarData.curr()

        # Display current frame's radar image
        if self._im is None :
            self._im = MakeReflectPPI(data['vals'][0], self.ys, self.xs,
                                      meth='pcmesh', ax=self.ax,
                                      colorbar=False,
                                      axis_labels=False, zorder=0, mask=False)
        else :
            self._im.set_array(data['vals'][0, :-1, :-1].flatten())

        if force_recluster or any([('center' not in feat.objects) for
                            feat in self.state._features[self.frameIndex]]) :
            if not hold_recluster :
                clustLabels, clustCnt = self.get_clusters()

        # Set features for this frame to visible
        for feat in self.state._features[self.frameIndex] :
            feat.set_visible(True)
            # Return alpha back to normal
            feat.set_alpha(1.0)
            # Put it on top
            feat.set_zorder(len(self.radarData))
            #feat.set_picker(True)

        # Show the other features
        if self._show_features :
            # How much alpha should change for each frame from frameIndex
            # The closer to self.frameIndex, the more opaque
            alphaIncrem = 1.0 / len(self.radarData)
            for frameIndex, features in self.state._features.iteritems() :
                if frameIndex == self.frameIndex :
                    continue

                framesFrom = np.abs(self.frameIndex - frameIndex)
                timeAlpha = ((1.0 - (framesFrom * alphaIncrem)) **
                             (1.0/self._alphaScale))
                zorder = len(self.radarData) - framesFrom
                for feat in features :
                    feat.set_visible(True)
                    feat.set_alpha(timeAlpha)
                    feat.set_zorder(zorder)
        else :
            # Make sure that these items are hidden
            for frameIndex, features in self.state._features.iteritems() :
                if frameIndex != self.frameIndex :
                    for feat in features :
                        feat.set_visible(False)
                        # Return alpha to normal
                        feat.set_alpha(1.0)

        if self.volTimes[self.frameIndex] is None :
            theDateTime = datetime.utcfromtimestamp(data['scan_time'])
            self.volTimes[self.frameIndex] = theDateTime
        else :
            theDateTime = self.volTimes[self.frameIndex]

        self.ax.set_title(theDateTime.strftime("%Y/%m/%d %H:%M:%S"))
        self.fig.canvas.draw_idle()
Beispiel #7
0
def main(args):
    if args.bw_mode:
        BW_mode()

    if len(args.trackFiles) == 0:
        print "WARNING: No trackFiles given!"
    if len(args.truthTrackFile) == 0:
        print "WARNING: No truth trackFiles given!"

    if args.trackTitles is None:
        args.trackTitles = args.trackFiles
    else:
        if len(args.trackTitles) != len(args.trackFiles):
            raise ValueError("The number of TITLEs does not match the number"
                             " of TRACKFILEs")

    if args.statName is not None and args.statLonLat is None:
        statData = ByName(args.statName)[0]
        args.statLonLat = (statData['LON'], statData['LAT'])

    if args.layout is None:
        args.layout = (1, max(len(args.trackFiles), len(args.truthTrackFile)))

    if args.figsize is None:
        args.figsize = plt.figaspect(float(args.layout[0]) / args.layout[1])

    if args.simTagFiles is None:
        args.simTagFiles = []

    trackerData = [
        FilterMHTTracks(*ReadTracks(trackFile))
        for trackFile in args.trackFiles
    ]
    truthData = [
        FilterMHTTracks(*ReadTracks(trackFile))
        for trackFile in args.truthTrackFile
    ]
    multiTags = [ReadSimTagFile(fname) for fname in args.simTagFiles]

    if len(multiTags) == 0:
        multiTags = [None]

    if args.statLonLat is not None:
        for aTracker in trackerData + truthData:
            CoordinateTransform(aTracker[0] + aTracker[1], args.statLonLat[0],
                                args.statLonLat[1])

    if len(trackerData) != len(truthData):
        # Basic broadcasting needed!

        if len(truthData) > len(trackerData):
            # Need to extend track data to match with the number of truth sets
            if len(truthData) % len(trackerData) != 0:
                raise ValueError("Can't extend TRACKFILE list to match with"
                                 " the TRUTHFILE list!")
        else:
            # Need to extend truth sets to match with the number of track data
            if len(trackerData) % len(truthData) != 0:
                raise ValueError("Can't extend TRUTHFILE list to match with"
                                 " the TRACKFILE list!")

        trkMult = max(int(len(truthData) // len(trackerData)), 1)
        trthMult = max(int(len(trackerData) // len(truthData)), 1)

        trackerData = trackerData * trkMult
        truthData = truthData * trthMult

        tagMult = max(int(len(truthData) // len(multiTags)), 1)
        multiTags = multiTags * tagMult

        args.trackTitles = args.trackTitles * trkMult

    theFig = plt.figure(figsize=args.figsize)
    grid = AxesGrid(theFig,
                    111,
                    nrows_ncols=args.layout,
                    aspect=False,
                    share_all=True,
                    axes_pad=0.45)

    showMap = (args.statLonLat is not None and args.displayMap)

    if args.radarFile is not None and args.statLonLat is not None:
        if len(args.radarFile) > 1 and args.endFrame is not None:
            args.radarFile = args.radarFile[args.endFrame]
        else:
            args.radarFile = args.radarFile[-1]

        data = LoadRastRadar(args.radarFile)
        for ax in grid:
            MakeReflectPPI(data['vals'][0],
                           data['lats'],
                           data['lons'],
                           meth='pcmesh',
                           ax=ax,
                           colorbar=False,
                           axis_labels=False,
                           zorder=0,
                           alpha=0.6)

    MakeComparePlots(grid,
                     trackerData,
                     truthData,
                     args.trackTitles,
                     showMap,
                     endFrame=args.endFrame,
                     tail=args.tail,
                     fade=args.fade,
                     multiTags=multiTags,
                     tag_filters=args.filters)

    if args.xlims is not None and np.prod(grid.get_geometry()) > 0:
        grid[0].set_xlim(args.xlims)

    if args.ylims is not None and np.prod(grid.get_geometry()) > 0:
        grid[0].set_ylim(args.ylims)

    if args.saveImgFile is not None:
        theFig.savefig(args.saveImgFile)

    if args.doShow:
        plt.show()
Beispiel #8
0
def ClusterMap(clusters,
               vals,
               indicesToShow,
               radarBG_alpha=0.15,
               dimmerBox_alpha=0.25,
               zorder=0,
               axis=None,
               **kwargs):
    """
    Plot the clusters of the radar reflectivities in a
    really cool way.

    vals must be indexed in [lat, lon] orientation

    radarBG_alpha   - (float)
        How opaque should the radar reflectivities be?
	    Set to zero if you don't want any radar background.

    dimmerBox_alpha - (float)
        How opaque should the dimmer box be?
        Set to zero if you don't want any dimmer box

    zorder	    - (float)
        The zorder for the plot.
        Note that this plot will internally use zorders 
             from *zorder* to *zorder* + 1
    """
    if axis is None:
        axis = plt.gca()

    print 'producing clust map'

    boxBoundx = None
    boxBoundy = None

    holdStatus = axis.ishold()

    if holdStatus:
        boxBoundx = axis.get_xlim()
        boxBoundy = axis.get_ylim()
    else:
        boxBoundx = (min(clusters['lonAxis']), max(clusters['lonAxis']))
        boxBoundy = (min(clusters['latAxis']), max(clusters['latAxis']))

    bbBoxX = (boxBoundx[0], boxBoundx[0], boxBoundx[1], boxBoundx[1])
    bbBoxY = (boxBoundy[0], boxBoundy[1], boxBoundy[1], boxBoundy[0])
    bbBox = zip(bbBoxX, bbBoxY)

    (lons, lats) = np.meshgrid(clusters['lonAxis'], clusters['latAxis'])

    domainMask = (lats < boxBoundy[0]) | (lats > boxBoundy[1]) | \
                 (lons < boxBoundx[0]) | (lons > boxBoundx[1]) | \
                 np.isnan(vals)

    # Create a transform object for the drop shadow.
    dx, dy = 2 / 72., -2 / 72.
    #white_offset = axis.transData + transforms.ScaledTranslation(dx, dy, axis.figure.dpi_scale_trans)
    black_offset = axis.transData + \
                    transforms.ScaledTranslation(dx, dy,
                                    axis.figure.dpi_scale_trans)

    # elementCount determines how many things are going to be
    # displayed in this plotting function.  To start, there
    # will be len(indicesToShow) clusters to plot, each of
    # those are plotted with three elements (white contour,
    # black contour, and a patch).
    # Then there is the possible radar background.
    # Lastly, there is the possible dimmer box.
    # Note: this can be an over-estimate many clusters might be
    # outside the bounding box.
    elementCount = ((2 * len(indicesToShow)) + (radarBG_alpha > 0.0) +
                    (dimmerBox_alpha > 0.0))

    # Create an array of zorder values to use for the components
    # of the ClusterMap.
    zorders = np.linspace(zorder, zorder + 1, num=elementCount, endpoint=False)

    zorderIndex = 0

    #  ------ Radar Background --------------
    # Don't bother plotting the BG if alpha is zero.
    if radarBG_alpha > 0.0:
        MakeReflectPPI(vals,
                       lats,
                       lons,
                       mask=domainMask,
                       colorbar=False,
                       axis_labels=False,
                       ax=axis,
                       zorder=zorders[zorderIndex],
                       alpha=radarBG_alpha,
                       meth='im',
                       **kwargs)
        zorderIndex += 1
        axis.hold(True)

    # --------Dimmer box--------
    # Don't bother plotting the dimmer box if it is zero.
    if dimmerBox_alpha > 0.0:
        axis.fill(bbBoxX,
                  bbBoxY,
                  'black',
                  zorder=zorders[zorderIndex],
                  alpha=dimmerBox_alpha)
        zorderIndex += 1
        axis.hold(True)

    # -------- Clusters ------------
    clustMask = np.empty(vals.shape, dtype=bool)
    #clustVals = np.empty_like(vals)

    clustMembers = [
        np.nonzero(clusters['clusterIndicies'] == clustIndx)
        for clustIndx in indicesToShow
    ]

    # Need to draw each contour separately to guarrantee that the
    # cluster has a closed loop for itself (i.e. - not merged with
    # a neighboring cluster).
    print "ClustCnt:", len(indicesToShow)

    # Plot the outlines of the clusters by initializing an
    # array with NaNs, and then assigning ones to the pixels
    # for a particular cluster. Then plot the contour of this
    # array, forcing it to use only one contour level.
    # The clusters are done separately so that clusters that
    # touch or even share a few pixels are still distinguishable.
    for index, goodMembers in enumerate(clustMembers):
        # If the cluster does not exist within the bounding box,
        # don't bother rendering it.
        if not np.any(~domainMask[clusters['members_LatLoc'][goodMembers],
                                  clusters['members_LonLoc'][goodMembers]]):
            continue

        clustMask.fill(True)

        # Finding all members of the cluster
        clustMask[clusters['members_LatLoc'][goodMembers],
                  clusters['members_LonLoc'][goodMembers]] = False

        # Plotting the cluster (white line, black line, then
        # reflectivity patch). This could probably be improved

        # black outline
        outline = axis.contour(lons,
                               lats,
                               clustMask, [0, 1],
                               colors='k',
                               linewidths=2.5,
                               zorder=zorders[zorderIndex + 0])
        SetContourZorder(outline, zorders[zorderIndex + 1])
        SetContourTransform(outline, black_offset)

        # white highlight... save time by re-using contour data.
        #for aPath in outline.collections[0].get_paths() :
        #    axis.plot(*aPath.vertices.T, c='k', lw=2.5, zorder=zorders[zorderIndex],
        #              transform=white_offset)

        MakeReflectPPI(vals,
                       lats,
                       lons,
                       mask=clustMask,
                       ax=axis,
                       zorder=zorders[zorderIndex + 1],
                       axis_labels=False,
                       colorbar=False,
                       meth='im',
                       **kwargs)
        zorderIndex += 2

    # Return axis to whatever hold status it had before.
    axis.hold(holdStatus)

    print 'map produced'