コード例 #1
0
def baselineTracking(spectrogram: Spectrogram,
                     baselineVel,
                     changeThreshold,
                     skipUntilTime: float = 12e-6):
    """
        Return the first time in microseconds that baseline's intensity value changes outside the changeThreshold.
        Use the average baseline value as the estimate
    """

    baselineInd = spectrogram._velocity_to_index(baselineVel)
    runningAverage = spectrogram.intensity[baselineInd][
        spectrogram._time_to_index(spectrogram.t_start + skipUntilTime)]

    startInd = spectrogram._time_to_index(spectrogram.t_start + skipUntilTime)
    for ind in range(startInd, spectrogram.intensity.shape[-1]):
        currentIntensity = spectrogram.intensity[baselineInd][ind]
        if (np.abs(currentIntensity) >= np.abs(
            (1 + changeThreshold) * runningAverage)) or (
                np.abs(currentIntensity) <= np.abs(
                    (1 - changeThreshold) * runningAverage)):
            # print(f"{changeThreshold}: {spectrogram.time[ind]*1e6}")
            return spectrogram.time[ind] * 1e6
        runningAverage += 1 / (ind + 1 - startInd) * \
            (currentIntensity - runningAverage)

    return spectrogram.time[ind] * 1e6
コード例 #2
0
def saveBaselineIntensityImages(files,
                                saveLoc: str = None,
                                imageExt: str = "png"):
    """
    Save a image file of the baseline as a function of time to the folder specified by saveLoc.
    """
    if saveLoc == None:
        saveLoc = r"../baselineIntensityMaps"

    for i in tqdm.trange(len(files)):
        filename = f"../dig/{files[i]}"
        MySpect = Spectrogram(filename)
        peaks, _, heights = baselines_by_squash(MySpect)

        plt.plot(
            np.array(MySpect.time) * 1e6,
            MySpect.intensity[MySpect._velocity_to_index(peaks[0])])
        plt.xlabel("Time ($\mu$s)")
        plt.ylabel("Intensity (db)")
        plt.title(f"{MySpect.data.filename}")
        path = os.path.join(
            saveLoc,
            files[i].replace(".dig", "") + f"_baselineIntensity.{imageExt}")
        if not os.path.exists(os.path.split(path)[0]):
            os.makedirs(os.path.split(path)[0])

        plt.savefig(path)

        # Reset the state for the next data file.
        plt.clf()
        del MySpect
コード例 #3
0
def setupForDocumentation():
    # Set up
    filename = "../dig/CH_4_009.dig"
    t1 = 14.2389/1e6
    t2 = 31.796/1e6
    MySpect = Spectrogram(filename, mode = "complex")
    sTime = MySpect._time_to_index(t1)
    eTime = MySpect._time_to_index(t2)

    bottomVel = 1906.38
    botInd = MySpect._velocity_to_index(bottomVel)

    topVel = 5000
    topVelInd = MySpect._velocity_to_index(topVel)

    visualSignalStart = 2652.21
    visSigIndex = MySpect._velocity_to_index(visualSignalStart)

    return MySpect, sTime, eTime, botInd, topVelInd, visSigIndex
コード例 #4
0
def signal_finder(spectrogram: Spectrogram,
                  baseline: np.ndarray,
                  t_start: float,
                  dt: int = 2,
                  min_separation=200):  # m/s
    """
    Look above the baseline for a signal corresponding to a surface velocity.
    """
    from scipy.signal import find_peaks

    t_index = spectrogram._time_to_index(t_start)
    spectra = np.sum(spectrogram.intensity[:, t_index - dt:t_index + dt],
                     axis=1)

    try:
        # limit the region we look at to points above the baseline
        # get the index of the baseline
        blo = spectrogram._velocity_to_index(baseline[0]) + 5
        if len(baseline) > 1:
            bhi = spectrogram._velocity_to_index(baseline[1]) - 5
        else:
            bhi = len(spectrogram.velocity) - 1
        velocity = spectrogram.velocity[blo:bhi]
        spectrum = spectra[blo:bhi]
        smax = spectrum.max()
        min_sep = int(min_separation / spectrogram.dv)
        peaks, properties = find_peaks(spectrum,
                                       height=0.05 * smax,
                                       distance=min_sep)
        heights = properties['peak_heights']
        # produce an ordering of the peaks from high to low
        ordering = np.flip(np.argsort(heights))
        peak_index = peaks[ordering]
        peaks = velocity[peak_index]
        hts = heights[ordering]

        # normalize to largest peak of 1 (e.g., the baseline)
        hts = hts / hts[0]

        return peaks[0]
    except Exception as eeps:
        print(eeps)
        return None
コード例 #5
0
def setupForDocumentation():
    # Set up
    filename = "../See_if_this_is_enough_to_get_started/CH_4_009.dig"
    t1 = 14.2389/1e6
    t2 = 31.796/1e6
    MySpect = Spectrogram(filename)
    sTime = MySpect._time_to_index(t1)
    eTime = MySpect._time_to_index(t2)

    bottomVel = 1906.38
    botInd = MySpect._velocity_to_index(bottomVel)

    topVel = 5000
    topVelInd = MySpect._velocity_to_index(topVel)

    signalJump = 25

    visualSignalStart = 2652.21
    visSigIndex = MySpect._velocity_to_index(visualSignalStart)

    return MySpect, sTime, eTime, signalJump, botInd, topVelInd, visSigIndex
コード例 #6
0
class SpectrogramWidget:
    """
    A Jupyter notebook widget to represent a spectrogram, along with
    numerous controls to adjust the appearance of the spectrogram.
    For the widget to behave in a Jupyter notebook, place::

        %matplotlib widget

    at the top of the notebook. This requires that the package
    ipympl is installed, which can be done either with pip3
    or conda install ipympl.

    I also recommend editing ~/.jupyter/custom/custom.css to
    modify the definition of .container::

        .container {
            width: 100% !important;
            margin-right: 40px;
            margin-left: 40px;
            }

    **Inputs**

    - digfile: either a string or DigFile
    - kwargs: optional keyword arguments. These are passed to
      the Spectrogram constructor and to the routine that
      creates the control widgets.

    **Data members**

    - digfile: the source data DigFile
    - title: the title displayed above the spectrogram
    - baselines: a list of baseline velocities
    - spectrogram: a Spectrogram object deriving from digfile
    - fig:
    - axSpectrum:
    - axSpectrogram:
    - image:
    - colorbar:
    - individual_controls: dictionary of widgets
    - controls:
    """
    _gspec = {
        'width_ratios': [6, 1.25],
        'height_ratios': [1],
        'wspace': 0.05,
        'left': 0.075,
        'right': 0.975,
    }

    def __init__(self, *args, **kwargs):
        """
        If one passes in a single unnamed arg, it can either be a digfile,
        a string pointing to a digfile, or a two-dimensional ndarray.
        If we are founded on a dig file, it is possible to recompute
        things. Only a subset of operations are possible when we're
        based on a two-dimensional array, but perhaps that is sometimes
        desirable.
        """
        self.digfile = None
        if len(args) == 1:
            arg = args[0]
            if isinstance(arg, str):
                self.digfile = DigFile(arg)
            elif isinstance(arg, DigFile):
                self.digfile = arg
        if self.digfile == None and len(args):
            # Let's see if we have enough information to display a spectrogram
            # That means we have a two-dimensional ndarray, and possibly corresponding
            # time and velocity arrays. The signature would be (intensity, [times, velocities]).
            arg = args[0]
            if isinstance(arg, np.ndarray) and len(arg.shape) == 2:
                self._static = {
                    'intensity': arg,
                }

                if len(args) == 3:
                    self._static['time'] = np.asarray(args[1])
                    self._static['velocity'] = np.asarray(args[2])
                else:
                    self._static['time'] = np.arange(0, -1 + arg.shape[1])
                    self._static['velocity'] = np.arange(0, -1 + arg.shape[0])

            assert hasattr(
                self, '_static'
            ), "Inappropriate arguments passed to the SpectrogramWidget constructor"

        # If LaTeX is enabled in matplotlib, underscores in the title
        # cause problems in displaying the histogram. However, we can
        # solve this by not using latex in displaying the title, so there
        # is no need to alter characters here.
        self.title = "" if self.static else self.digfile.filename.split(
            '/')[-1]
        self.baselines = []

        # Compute the base spectrogram (do we really need this?)
        self.spectrogram = None
        if self.dig:
            self.spectrogram = Spectrogram(self.digfile, None, None, **kwargs)
            self.spectrogram_fresh = True  # flag for the first pass

            self.spectrogram.overlap = 0.875

        self.fig, axes = plt.subplots(nrows=1,
                                      ncols=2,
                                      sharey=True,
                                      squeeze=True,
                                      gridspec_kw=self._gspec)
        self.axSpectrogram, self.axSpectrum = axes

        self.subfig = None
        self.axTrack = None
        self.axSpare = None

        # At the moment, clicking on the image updates the spectrum
        # shown on the left axes. It would be nice to be more sophisticated and
        # allow for more sophisticated interactions, including the ability
        # to display more than one spectrum.
        self.fig.canvas.mpl_connect('button_press_event',
                                    lambda x: self.handle_click(x))
        self.fig.canvas.mpl_connect('key_press_event',
                                    lambda x: self.handle_key(x))

        self.spectrum(None, "")

        self.image = None  # we will set in update_spectrogram
        self.colorbar = None  # we will set this on updating, based on the

        self.peak_followers = []  # will hold any PeakFollowers
        self.spectra = []  # will hold spectra displayed at right
        self.spectra_in_db = True  # should spectra be displayed in db?

        self.controls = dict()  # widgets stored by name
        self.layout = None  # how the controls get laid out
        self.selecting = False  # we are not currently selecting a ROI
        self.roi = []  # and we have no regions of interest
        self.threshold = None
        self.make_controls(**kwargs)

        display(self.layout)
        self.update_spectrogram()

    @property
    def static(self):
        "If we are not associated with a dig file, return True"
        return hasattr(self, '_static')

    @property
    def dig(self):
        "True if we are associated with a dig file and can recompute the spectrogram"
        return isinstance(self.digfile, DigFile)

    @property
    def intensity(self):
        "Return the two-dimensional array of intensities"
        if self.dig:
            return self.spectrogram.intensity
        else:
            return self._static['intensity']

    def make_controls(self, **kwargs):
        """
        Create the controls for this widget and store them in self.controls.
        """
        cd = self.controls  # the dictionary of controls
        t_range = kwargs.get('t_range', (0, 100))

        # If we are associated with a dig file, include controls that
        # allow recomputation of the spectrogram
        if self.dig:
            df = self.digfile

            # FFT size  ###########################################
            # Set the size of each spectrum
            pps = self.spectrogram.points_per_spectrum
            # pps = kwargs.get('points_per_spectrum', 8192)
            val = int(np.log2(pps))
            cd['spectrum_size'] = slide = widgets.IntSlider(
                value=val, min=8, max=18, step=1, description="FFT 2^n")
            slide.continuous_update = False
            slide.observe(
                lambda x: self.overhaul(points_per_spectrum=2**x['new']),
                names="value")

            # Set the overlap percentage of successive time intervals
            cd['overlap'] = slide = widgets.FloatSlider(
                description='Overlap %',
                value=100.0 * self.spectrogram.overlap,
                min=0,
                max=100)
            slide.continuous_update = False
            slide.observe(lambda x: self.overhaul(overlap=x['new'] * 0.01),
                          names="value")

            # Thumbnail  ###########################################
            # Display a thumbnail of the raw signal
            cd['raw_signal'] = widgets.ToggleButton(value=False,
                                                    description="Show V(t)",
                                                    button_style='',
                                                    icon='check')
            cd['raw_signal'].observe(lambda b: self.show_raw_signal(b),
                                     names="value")

            # Time range ###########################################

            cd['t_range'] = slide = ValueSlider("Time (µs)",
                                                t_range, (df.t0, df.t_final),
                                                1e6,
                                                readout_format=".1f")
            slide.continuous_update = False

            # Velocity range ###########################################
            cd['velocity_range'] = slide = ValueSlider(
                "Velocity (km/s)", (0, 50), (0.0, self.spectrogram.v_max),
                1e-3,
                readout_format=".1f",
                continuous_update=False)
        else:
            d = self._static
            cd['t_range'] = ValueSlider("Time",
                                        t_range, (d['time'][0], d['time'][-1]),
                                        1e-3,
                                        readout_format=".1f",
                                        continuous_update=False)
            cd['velocity_range'] = ValueSlider("Velocity (km/s)", (0, 100),
                                               (0.0, d['velocity'][-1]),
                                               1e-3,
                                               readout_format=".1f",
                                               continuous_update=False)

        cd['t_range'].observe(lambda x: self.do_update(x), names="value")
        cd['velocity_range'].observe(lambda x: self.update_velocity_range(x),
                                     names="value")

        # Color range ###########################################

        imax = self.intensity.max()
        if self.dig:
            hl = self.spectrogram.histogram_levels
            imin = hl['tens'][3]

            # Let's figure out the range likely to produce a clear
            # image. Put the 50% point at the bottom end and the 95%
            # at the top?

            def scale(x):
                return int(100.0 * (x - imin) / (imax - imin))

            start_range = (scale(hl['tens'][8]), scale(hl['tenths'][9]))
        else:
            imin = imax - 150  # ?? this is bad and assumes db
            start_range = (40, 70)

        cd['intensity_range'] = slide = ValueSlider("Color",
                                                    start_range, (imin, imax),
                                                    multiplier=1,
                                                    readout_format=".0f",
                                                    continuous_update=False)
        slide.observe(lambda x: self.update_color_range(), names="value")

        # Threshold percentage #####################################
        cd['threshold'] = slide = widgets.FloatSlider(
            description='Noise floor %',
            value=0,
            min=0,
            max=100.0,
            continuous_update=False)
        slide.observe(lambda x: self.update_threshold(x['new']), names="value")

        # Color map selector ###########################################
        the_maps = sorted(COLORMAPS.keys())
        the_maps.append('Computed')
        cd['color_map'] = widgets.Dropdown(
            options=the_maps,
            value=DEFMAP,
            description='Color Map',
            disabled=False,
        )
        cd['color_map'].observe(lambda x: self.update_cmap(), names="value")

        # Click selector  ###########################################
        # What to do when registering a click in the spectrogram
        cd['clicker'] = widgets.Select(options=("Spectrum (dB)", "Spectrum",
                                                "Peak", "Gauss",
                                                "Template_Matching"),
                                       value='Spectrum (dB)',
                                       description="Click:",
                                       disabled=False)

        cd['marquee'] = mwidgets.RectangleSelector(
            self.axSpectrogram,
            lambda eclick, erelease: self.RSelect(eclick, erelease),
            interactive=True,
            useblit=True,
            rectprops=dict(facecolor='yellow',
                           edgecolor='red',
                           alpha=0.2,
                           fill=True),
            drawtype='box')
        cd['marquee'].set_active(False)

        # Clear spectra ###########################################
        cd['clear_spectra'] = widgets.Button(description="Clear Spectra")
        cd['clear_spectra'].on_click(lambda b: self.clear_spectra())

        # Clear peak_followers ###########################################
        cd['clear_followers'] = widgets.Button(
            description="Clear Peak Followers")
        cd['clear_followers'].on_click(lambda b: self.clear_followers())

        # Computing baselines ###########################################
        cd['baselines'] = widgets.Dropdown(options=('_None_', 'Squash', 'FFT'),
                                           value='_None_',
                                           description='Baselines',
                                           disabled=False)
        cd['baselines'].observe(lambda x: self.update_baselines(x["new"]),
                                names="value")

        cd['squash'] = widgets.Button(description="Squash in Vertical")
        cd['squash'].on_click(lambda b: self.squash_vertical())

        columns = [
            'color_map;t_range;velocity_range;intensity_range;threshold',
            'clicker;clear_spectra;clear_followers',
        ]
        if self.dig:
            columns.append('spectrum_size;overlap;raw_signal;baselines;squash')

        vboxes = []
        for col in columns:
            vboxes.append(widgets.VBox([cd[x] for x in col.split(';')]))
        self.layout = widgets.HBox(vboxes)

    def range(self, var):
        "Return the range of the named control, or None if not found."
        if var in self.controls:
            return self.controls[var].range
        return None

    def RSelect(self, eclick, erelease):
        "Called when self.selecting is True and the marquee is active"
        if self.selecting:
            t0, t1 = eclick.xdata, erelease.xdata
            v0, v1 = eclick.ydata, erelease.ydata
            # make sure they are in the right order
            if t1 < t0:
                t0, t1 = t1, t0
            if v1 < v0:
                v0, v1 = v1, v0
            self.roi.append(dict(time=(t0, t1), velocity=(v0, v1)))

    def do_update(self, what):
        self.update_spectrogram()

    def show_raw_signal(self, box):
        """
        Display or remove the thumbnail of the time series data
        at the top of the spectrogram window.
        """
        if box.new:
            # display the thumbnail
            t_range = self.range('t_range')
            thumb = self.digfile.thumbnail(*t_range)
            # we have to superpose the thumbnail on the
            # existing velocity axis, so we need to rescale
            # the vertical.
            tvals = thumb['times'] * 1e6  # convert to µs
            yvals = thumb['peak_to_peak']
            ylims = self.axSpectrum.get_ylim()
            # Map the thumbnail to the top 20%
            ymax = yvals.max()
            yrange = ymax - yvals.min()
            yscale = 0.2 * (ylims[1] - ylims[0]) / yrange
            vvals = ylims[1] - yscale * (ymax - yvals)
            self.raw = self.axSpectrogram.plot(tvals, vvals, 'r-',
                                               alpha=0.5)[0]
        else:
            try:
                self.axSpectrogram.lines.remove(self.raw)
                self.raw = None
                self.fig.canvas.draw()
                self.fig.canvas.flush_events()
            except:
                pass

    def overhaul(self, **kwargs):
        """
        A parameter affecting the base spectrogram has been changed, so
        we need to recompute everything.
        """
        if self.dig:
            if self.spectrogram_fresh:
                self.spectrogram_fresh = False
            else:
                self.spectrogram.set(**kwargs)
        self.update_spectrogram()

    def update_spectrogram(self):
        """
        Recompute and display everything
        """

        intense = self.spectrogram.intensity if self.dig else self._static[
            'intensity']

        # Having recomputed the spectrum, we need to set the yrange

        # of the color map slider
        cmin = intense.min()
        cmax = intense.max()
        self.controls['intensity_range'].range = (cmin, cmax)
        self.display_spectrogram()

    def display_spectrogram(self):
        """

        """
        trange = self.range('t_range')
        vrange = self.range('velocity_range')

        if self.dig:
            # extract the requisite portions
            times, velocities, intensities = self.spectrogram.slice(
                trange, vrange)
        else:
            d = self._static
            times, velocities, intensities = d['time'], d['velocity'], d[
                'intensity']

        # if we have already displayed an image, remove it
        if self.colorbar:
            self.colorbar.remove()
            self.colorbar = None
        if self.image:
            self.image.remove()
        self.image = None

        if self.threshold:
            intensities[intensities < self.threshold] = self.threshold

        self.image = self.axSpectrogram.pcolormesh(times * 1e6, velocities,
                                                   intensities)

        self.colorbar = self.fig.colorbar(self.image,
                                          ax=self.axSpectrogram,
                                          fraction=0.08)

        self.axSpectrogram.set_title(self.title, usetex=False)
        self.axSpectrogram.set_xlabel('Time ($\mu$s)')
        self.axSpectrogram.set_xlim(*(np.array(trange) * 1e6))
        self.axSpectrogram.set_ylabel('Velocity (m/s)')
        self.update_velocity_range()
        self.update_color_range()
        self.update_cmap()

    def update_threshold(self, x):
        sg = self.spectrogram
        if int(x) == 0:
            self.threshold = None
        else:
            if x < 90:
                threshold = sg.histogram_levels['tens'][x // 10]
            elif x < 99:
                threshold = sg.histogram_levels['ones'][int(x - 90)]
            else:
                threshold = sg.histogram_levels['tenths'][int(10 * (x - 99))]
            self.threshold = self.spectrogram.transform(threshold)
        self.display_spectrogram()

    def update_cmap(self):
        """
        Update the color map used to display the spectrogram
        """
        mapname = self.controls['color_map'].value
        if mapname == 'Computed':
            from UI_Elements.generate_color_map import make_spectrogram_color_map
            mapinfo = make_spectrogram_color_map(self.spectrogram, 4, mapname)
            maprange = (mapinfo['centroids'][1], mapinfo['centroids'][-2])
            self.controls['intensity_range'].value = maprange
        self.image.set_cmap(COLORMAPS[mapname])

    def update_velocity_range(self, info=None):
        """
        Update the displayed velocity range using values obtained
        from the 'velocity_range' slider.
        """
        if info:
            old_vmin, old_vmax = info['old']
            vmin, vmax = info['new']
            if vmax > old_vmax or vmin < old_vmin:
                return self.update_spectrogram()
        vmin, vmax = self.range('velocity_range')
        self.axSpectrogram.set_ylim(vmin, vmax)
        self.axSpectrum.set_ylim(vmin, vmax)

    def update_color_range(self):
        self.image.set_clim(self.range('intensity_range'))

    def handle_click(self, event):
        try:
            # convert time to seconds
            t, v = event.xdata * 1e-6, event.ydata
        except:
            return 0
        if self.selecting:
            return 0
        # Look up what we should do with the click
        action = self.controls['clicker'].value
        # print(f"The action I am attempting to do is {action}")
        try:
            if 'Spectrum' in action:
                self.spectrum(t, action)
            elif 'Template_Matching' in action:
                ti = self.spectrogram._time_to_index(t)
                vi = self.spectrogram._velocity_to_index(v)
                self.match_templates(ti, vi)
            else:
                # print("I am handling a click that should be a peak follower")
                self.follow(t, v, action)

        except Exception as eeps:
            pass

    def handle_key(self, event):
        try:
            # convert time to seconds
            _, v = event.xdata * 1e-6, event.ydata
        except:
            pass
        char = event.key
        if char == 'x':
            # remove the all spectra
            self.clear_spectra()
        if char in ('f', 'b', 'F', 'B'):
            # We'd like to go exploring
            if not hasattr(self, 'explorer_mark'):
                self.explorer_mark = 2
            else:
                shifts = dict(f=4, F=20, b=-4, B=-40)
                self.explorer_mark += shifts[char]

            self.gaussian_explorer(self.explorer_mark)
        if char in ('m', 'M'):
            self.selecting = not self.selecting
            self.controls['marquee'].set_active(self.selecting)
        if char in "0123456789":
            n = int(char)
            # self.fan_out(int(char))
            self.gauss_out(n)
        if char in ('a', 'A') and self.roi:
            self.analyze_roi()

    def clear_spectra(self):
        """Remove all spectra from axSpectrum and the corresponding
        markers from axSpectrogram
        """
        for x in self.spectra:
            self.axSpectrogram.lines.remove(x['marker'])
            self.axSpectrum.lines.remove(x['line'])
        self.spectra = []
        self.fig.canvas.draw()
        self.fig.canvas.flush_events()

    def clear_followers(self):
        """Remove all followers"""
        for x in self.peak_followers:
            self.axSpectrogram.lines.remove(x.line)
        self.peak_followers = []
        self.fig.canvas.draw()
        self.fig.canvas.flush_events()

    def follow(self, t, v, action):
        """Attempt to follow the path starting with the clicked
        point."""
        print(
            f"Let's follow something starting at {t, v} using action {action}."
        )
        if action == "Gauss":
            fitter = GaussianFitter(self.spectrogram, (t, v))
            self.gauss = fitter
        elif action == "Peak":
            follower = PeakFollower(self.spectrogram, (t, v))
            # self.peak = follower
            self.peak_followers.append(follower)
            follower.run()
            tsec, v = follower.v_of_t
            follower.line = self.axSpectrogram.plot(tsec * 1e6,
                                                    v,
                                                    'r.',
                                                    alpha=0.4,
                                                    markersize=2)[0]
        # print("Create a figure and axes, then call self.gauss.show_fit(axes)")

    def gauss_out(self, n: int):
        """
        Show center velocity, width, and amplitude for gaussian
        fits to the data in follower n.
        """
        if n >= len(self.peak_followers):
            return 0
        WRITEOUT, fnum = False, 0
        pf = self.peak_followers[n]
        times, centers, widths, amps = [], [], [], []
        vind = pf.frame['vi_span'].to_numpy()
        tind = pf.frame['t_index'].to_numpy()
        sp = self.spectrogram
        for j in range(len(tind)):
            t = sp.time[tind[j]] * 1e6
            vfrom, vto = vind[j]
            powers = sp.power(sp.intensity[vfrom:vto, tind[j]])
            speeds = sp.velocity[vfrom:vto]

            gus = Gaussian(speeds, powers)
            if gus.valid:
                times.append(t)
                centers.append(gus.center)
                widths.append(gus.width)
                amps.append(gus.amplitude)
                if WRITEOUT:
                    fname = f"{os.path.splitext(self.digfile.filename)[0]}_{fnum:04d}.csv"
                    gus.write_csv(fname)
                    fnum += 1
        if WRITEOUT:
            # write out the times, too
            fname = f"{os.path.splitext(self.digfile.filename)[0]}_t.csv"
            v = np.asarray(times)
            np.savetxt(v, fname)

        fig, axes = plt.subplots(nrows=1, ncols=3, squeeze=True)
        ax1, ax2, ax3 = axes
        ax1.errorbar(times, centers, fmt='b-', yerr=widths)
        ax1.set_xlabel(r'$t$ ($\mu$s)')
        ax1.set_ylabel(r'$v$ (m/s)')

        ax2.plot(times, widths, 'r-')
        ax2.set_xlabel(r'$t$ ($\mu$s)')
        ax2.set_ylabel(r'$\delta v$ (m/s)')

        ax3.plot(times, amps, 'g-')
        ax3.set_xlabel(r'$t$ ($\mu$s)')
        ax3.set_ylabel('Amplitude')

        # Store the values for later access
        if not hasattr(self, "gauss_outs"):
            self.gauss_outs = [None for x in range(len(self.peak_followers))]
        else:
            while len(self.gauss_outs) < len(self.peak_followers):
                self.gauss_outs.append(None)
        self.gauss_outs[n] = dict(time=np.array(times),
                                  center=np.array(centers),
                                  width=np.array(widths),
                                  amplitude=np.array(amps))

    def gaussian_explorer(self, follower_pt: int):
        """
        Show center velocity, width, and amplitude for gaussian
        fits to the data in follower n.
        """
        if len(self.peak_followers) == 0:
            return 0
        pf = self.peak_followers[0]
        res = pf.results
        points = len(res['t_index'])

        def bound(x):
            return x % points

        follower_pt = bound(follower_pt)

        # We'd like to show data for this index, the previous one,
        #  and the next one, along with the gaussian fit
        hoods = [pf.hood(n=bound(x + follower_pt)) for x in (-2, -1, 0, 1, 2)]

        # Check that we have the requisite figure, and make it if we don't
        if not hasattr(self, 'explorer_fig'):
            self.explorer_fig, self.explorer_axes = plt.subplots(1,
                                                                 5,
                                                                 sharey=True)
            # also add a marker to the follower's representation on the
            # spectrogram to make it easier to see where we are
            self.explorer_marker = self.axSpectrogram.plot([], [], 'k*')[0]

        # show where we are
        tsec, v = pf.v_of_t
        self.explorer_marker.set_data([
            tsec[follower_pt] * 1e6,
        ], [
            v[follower_pt],
        ])

        min_v, max_v, max_peak = 1e10, 0, 0
        for ax, hood in zip(self.explorer_axes, hoods):
            ax.clear()
            # plot the data
            ax.plot(hood.velocity, hood.intensity, 'ko', alpha=0.5)

            # plot the background level used for the moment calculation
            bg = hood.moment['background']
            ax.plot([hood.velocity[0], hood.velocity[-1]], [bg, bg], 'r-')
            # show the center and widths from the moment calculation
            tallest = np.max(hood.intensity)
            max_peak = max(tallest, max_peak)
            ax.plot([
                hood.moment['center'] + x * hood.moment['std_err']
                for x in (-1, 0, 1)
            ], 0.5 * tallest * np.ones(3), 'r.')
            # show the gaussian
            hood.plot_gaussian(ax)
            vcenter, width = hood.peak_v, hood.moment['std_dev']
            min_v = min(min_v, vcenter - 12 * width)
            max_v = max(max_v, vcenter + 12 * width)
            # ax.set_xlim(vcenter - 12 * width, vcenter + 12 * width)
            ax.set_xlabel(f"$v$ (m/s)")
            ax.set_title(f"{hood.time*1e6:.2f}" + " $\\mu$s")
            txt = f"m = {hood.moment['center']:.1f} ± {hood.moment['std_err']:.1f}"
            txt = f"{txt}\ng = {hood.gaussian.center:.1f} ± {hood.gaussian.width:.1f}"
            ax.annotate(txt,
                        xy=(0.05, 0.95),
                        xycoords='axes fraction',
                        horizontalalignment='left',
                        verticalalignment='top')

        for ax in self.explorer_axes:
            ax.set_xlim(min_v, max_v)

        # label the common velocity axis
        ax = self.explorer_axes[0]
        ax.set_ylim(-0.05 * max_peak, 1.2 * max_peak)
        ax.set_ylabel("Intensity")

    def analyze_roi(self):
        """
        Extract the region(s) of interest and process them
        """
        for roi in self.roi:
            analyze_region(self.spectrogram, roi['time'])

    def fan_out(self, n: int):
        """Produce a zoomed in version of this trace, showing
        the neighborhood around the determined peak.
        """
        if n >= len(self.peak_followers):
            return 0
        pf = self.peak_followers[n]
        vind = pf.frame['vi_span'].to_numpy()
        tind = pf.frame['t_index'].to_numpy()
        self.subfig, axes = plt.subplots(nrows=1,
                                         ncols=2,
                                         sharey=True,
                                         squeeze=True,
                                         gridspec_kw=self._gspec)
        self.axSpare, self.axTrack = axes

        # We will create a "waterfall" of curves surrounding
        # the peaks, each offset by a bit. The x axis will
        # represent intensity, with subsequent time traces offset
        # by an amount I need to determine. The y axis
        # is velocity.

        spans = []
        vvec = self.spectrogram.velocity  # shortcut to velocity vector
        tvec = self.spectrogram.time
        ivec = self.spectrogram.intensity

        # pre-extract a bunch of one-dimensional curves
        # and be sure to convert to power
        for n in range(len(tind)):
            vfrom, vto = vind[n]
            spans.append({
                'v':
                vvec[vfrom:vto],
                'power':
                self.spectrogram.power(ivec[vfrom:vto, tind[n]]),
                't':
                tvec[tind[n]] * 1e6,
            })

        maxima = np.array([np.max(x['power']) for x in spans])
        maxpower = maxima.max()
        # Let's set the offset between times to be one tenth of
        # the maxpower
        offset = 0.025 * maxpower

        for n in reversed(list(range(len(spans)))):
            span = spans[n]
            self.axTrack.plot(span['power'] + n * offset,
                              span['v'],
                              'b-',
                              alpha=0.33)
        self.axTrack.set_ylabel('$v$')

    def squash_vertical(self):
        normy = self.spectrogram.squash(dB=False) * 2000
        self.axSpectrogram.plot(self.spectrogram.time * 1e6,
                                normy,
                                'b-',
                                alpha=0.75)

    def update_baselines(self, method):
        """
        Handle the baselines popup menu
        """
        from ProcessingAlgorithms.SignalExtraction.baselines import baselines_by_squash
        blines = []
        self.baselines = []  # remove any existing baselines
        if method == "Squash":
            peaks, sigs, heights = baselines_by_squash(self.spectrogram)
            blines.extend(peaks)

            # for n in range(len(heights)):
            # if heights[n] > 0.1:
            # blines.append(peaks[n])

        # Now show the baselines in blines or remove any
        # if blines is empty

        if not blines:
            for b in self.baselines:
                self.axSpectrum.lines.remove(b['line'])
            self.baselines = []  # remove them
        else:
            edges = (self.spectrogram.intensity.min(),
                     self.spectrogram.intensity.max())
            for v in blines:
                bline = self.axSpectrum.plot([edges[0], edges[1]], [v, v],
                                             'k-',
                                             alpha=0.4)
                self.baselines.append(dict(v=v, line=bline))

    def baseline_intensity(self):
        self.update_baselines("Squash")
        figgy = plt.figure()
        ax = figgy.add_subplot(1, 1, 1)
        sg = self.spectrogram
        for bline in self.baselines:
            index = sg._velocity_to_index(bline['v'])
            if index > 2:
                ax.semilogy(sg.time * 1e6, sg.power(sg.intensity[index, :]))
        ax.set_xlabel(r"$t$ ($\mu$ s)")
        ax.set_ylabel(r"Power")

    def Spectrum(self, the_time: float):
        """
        Return the column from the spectrogram in power form
        """
        sg = self.spectrogram
        t_index = sg._time_to_index(the_time)
        vals = sg.intensity[:, t_index]
        return sg.power(vals)

    def spectrum(self, the_time: float, form: str):
        """
        Display a spectrum in the left axes corresponding to the
        passed value of the_time (which is in seconds).
        """
        _colors = ["r", "g", "b", "y"]
        if the_time is None:
            # Initialize the axes
            # self.axSpectrum.plot([0, 1], [0, 1], 'r-')
            self.axSpectrum.grid(axis='x', which='both', color='b', alpha=0.4)
        else:
            if True:
                delta_t = self.spectrogram.points_per_spectrum / 2 * \
                    self.digfile.dt
                the_spectrum = Spectrum(self.digfile.values(
                    the_time - delta_t, the_time + delta_t),
                                        self.digfile.dt,
                                        remove_dc=True)
                # compute the level of the 90th percentile
                spec = dict(spectrum=the_spectrum)
                vals = the_spectrum.db
                ordering = np.argsort(vals)
                if self.baselines:
                    blines = [x['v'] for x in self.baselines]
                    n = -1
                    while the_spectrum.velocities[ordering[n]] in blines:
                        n -= 1
                else:
                    n = -1
                spec['max'] = vals[ordering[n]]
                noise_floor = int(n - 0.1 * len(vals))
                spec['90'] = vals[ordering[noise_floor]]
            else:
                t_index = self.spectrogram._time_to_index(the_time)
                vals = self.spectrogram.intensity[:, t_index]

            # We need to worry about the format of the spectrum
            db = ('dB' in form)
            field = 'db' if db else 'power'
            the_line = self.axSpectrum.plot(getattr(the_spectrum, field),
                                            the_spectrum.velocities,
                                            _colors[len(self.spectra)],
                                            alpha=0.33)
            spec['line'] = the_line[0]

            tval = the_time * 1e6  # convert to microseconds
            marker = self.axSpectrogram.plot([tval, tval],
                                             [0, self.spectrogram.v_max],
                                             _colors[len(self.spectra)],
                                             alpha=0.33)
            spec['marker'] = marker[0]

            self.spectra.append(spec)

            if db != self.spectra_in_db:
                self.spectra_in_db = db  # switch our mode
                # and replot all the spectra
                for spec in self.spectra:
                    li = spec['line']
                    sp = spec['spectrum']
                    li.set(xdata=getattr(sp, field), ydata=sp.velocities)

            self.axSpectrum.set_xlabel("Power (dB)" if db else "Power")
            if db:
                # we should order the values and set a limit at something
                # like the strongest decile
                ninety = max([x['90'] for x in self.spectra])
                peak = max([x['max'] for x in self.spectra])
                self.axSpectrum.set_xlim(ninety, peak)
            return 0
            line = self.axSpectrum.lines[0]
            intensities = the_spectrum.db
            line.set(xdata=intensities, ydata=the_spectrum.velocities)

            # We should also add a line to the spectrogram showing where
            # the spectrum came from.
            if not self.axSpectrogram.lines:
                self.axSpectrogram.plot([0, 0], [0, 1], 'r-', alpha=0.33)
            # this won't scale when we add baselines
            line = self.axSpectrogram.lines[0]

            line.set(xdata=[tval, tval], ydata=[0, self.spectrogram.v_max])

    def match_templates(self, time, velocity):

        methodsToUse = [
            'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED'
        ]

        matcher = TemplateMatcher(
            self.spectrogram,
            template=Templates.opencv_long_start_pattern5.value,
            span=200,
            k=20,
            methods=methodsToUse)

        times, velos, scores, methodsUsed = matcher.match()

        time_offset = 2.0 + (2 * abs(
            matcher.spectrogram.time[matcher.template_time_offset_index] * 1e6)
                             )
        times = np.array(times) + time_offset

        matcher.add_to_plot(self.axSpectrogram,
                            times,
                            velos,
                            scores,
                            methodsUsed,
                            show_points=True,
                            show_medoids=True,
                            verbose=False,
                            visualize_opacity=False,
                            show_bounds=True)
コード例 #7
0
    width = 50
    for peak_center in peaks:
        low, high = max(0, peak_center -
                        width), min(len(powers) - 1, peak_center + width)
        neighborhoods.append([velocities[low:high], powers[low:high]])
    return neighborhoods


if __name__ == '__main__':
    import os
    os.chdir('../dig')
    sgram = Spectrogram('./CH_4_009/seg00.dig', 0.0, 50.0e-6)


    peaks, uncertainties, peak_heights = baselines_by_squash(sgram)
    velos = []
    times = [x for x in range(0,30)]


    pcms, axes = sgram.plot(min_time=0, min_vel=100, max_vel=10000, cmap='3w_gby')


    # add the peaks to the plot and change the color map range
    pcm = pcms['intensity raw']
    pcm.set_clim(-40, -65)
    for peak in peaks:
        velo_index = sgram._velocity_to_index(peak)
        axes.plot(times, [peak for x in range(0,30)], color='red', linewidth=3, markersize=15)

    plt.show()
コード例 #8
0
def runExperiment(trainingFilePath,
                  thresholds: list,
                  skipUntilTimes: list = [],
                  cmap: str = DEFMAP,
                  errorPower: int = 1):

    data = pd.read_excel(trainingFilePath)

    # Ignore the samples that we do not have the full data for.
    data = data.dropna()
    data.reset_index()  # Reset so that it can be easily indexed.

    files = data["Filename"].to_list()
    bestGuessTimes = (data[data.columns[1]] * 1e6).to_list()
    # This will be the same times for the probe destruction dataset. It is generally ignored currently.
    bestGuessVels = data[data.columns[-1]].to_list()

    d = {"Filename": files}

    if skipUntilTimes == []:
        skipUntilTimes = [12e-6]

    cmapAttempt = cmap
    if cmapAttempt in COLORMAPS.keys():
        cmapAttempt = COLORMAPS[cmapAttempt]

    for i in range(len(thresholds)):
        d[f"threshold {thresholds[i]}"] = np.zeros(
            (len(skipUntilTimes), len(files)))
        d[f"threshold {thresholds[i]} error"] = np.zeros(
            (len(skipUntilTimes), len(files)))

    print("Running the experiment")

    for i in tqdm.trange(len(files)):
        filename = os.path.join(os.path.join("..", "dig"), f"{files[i]}")
        MySpect = Spectrogram(filename, overlap=0)
        peaks, _, heights = baselines_by_squash(MySpect, min_percent=0.75)
        baselineInd = MySpect._velocity_to_index(peaks[0])
        intensity = MySpect.intensity[baselineInd]

        for skipTimeInd in range(len(skipUntilTimes)):
            skipXInds = MySpect._time_to_index(skipUntilTimes[skipTimeInd])

            outputTimeInds = np.array(baselineExperiment(
                intensity, thresholds, skipXInds),
                                      dtype=int)

            for thresInd, thres in enumerate(thresholds):
                timeEstimate = MySpect.time[outputTimeInds[thresInd]] * 1e6

                d[f"threshold {thres}"][skipTimeInd][i] = timeEstimate
                d[f"threshold {thres} error"][skipTimeInd][
                    i] = bestGuessTimes[i] - timeEstimate

        del MySpect

    # Compute summary statistics
    summaryStatistics = np.zeros((len(thresholds), len(skipUntilTimes), 5))
    stats = ["Avg", "Std", "Median", "Max", "Min"]

    print(f"Computing the summary statistics: [{stats}]")

    for i in tqdm.trange(len(thresholds)):
        for j in range(len(skipUntilTimes)):
            q = np.power(d[f"threshold {thresholds[i]} error"][j], errorPower)
            summaryStatistics[i][j][0] = np.mean(q)
            summaryStatistics[i][j][1] = np.std(q)
            summaryStatistics[i][j][2] = np.median(q)
            summaryStatistics[i][j][3] = np.max(q)
            summaryStatistics[i][j][4] = np.min(q)

    for i in tqdm.trange(len(stats)):
        fig = plt.figure()
        ax = plt.gca()

        pcm = ax.pcolormesh(np.array(skipUntilTimes) * 1e6,
                            thresholds,
                            summaryStatistics[:, :, i],
                            cmap=cmapAttempt)
        plt.title(f"{stats[i]} Error over the Files Tested")
        plt.ylabel("Thresholds")
        plt.xlabel("Time that you skip at the beginning ($\mu$s)")
        plt.gcf().colorbar(pcm, ax=ax)

        plt.show()  # Need this for Macs.

    summaryFolder = r"../JumpOffTimeEstimates"
    if not os.path.exists(summaryFolder):
        os.makedirs(summaryFolder)

    for i in range(len(thresholds)):
        q = pd.DataFrame(summaryStatistics[i])
        internal_a = str(thresholds[i]).replace(".", "_")
        saveSummaryFilename = f"summaryThresh{internal_a}.csv"
        q.to_csv(os.path.join(summaryFolder, saveSummaryFilename))

    return summaryStatistics, d