예제 #1
0
    def printList(self) -> List[str]:
        """Class information as a list of strings

        Returns
        -------
        out : List[str]
            List of strings with information
        """

        textLst: List[str] = []
        textLst.append("Time data path = {}".format(self.timePath))
        textLst.append("Spectra data path = {}".format(self.specPath))
        textLst.append("Statistics data path = {}".format(self.statPath))
        textLst.append("Mask data path = {}".format(self.maskPath))
        textLst.append("TransFunc data path = {}".format(self.transFuncPath))
        textLst.append("Calibration data path = {}".format(self.calPath))
        textLst.append("Images data path = {}".format(self.imagePath))
        textLst.append("Reference time = {}".format(
            self.refTime.strftime("%Y-%m-%d %H:%M:%S")))
        textLst.append("Project start time = {}".format(
            self.projStart.strftime("%Y-%m-%d %H:%M:%S.%f")))
        textLst.append("Project stop time = {}".format(
            self.projEnd.strftime("%Y-%m-%d %H:%M:%S.%f")))
        textLst.append("Project found {} sites:".format(self.getNumSites()))
        for site in self.sites:
            textLst.append("{}\t\tstart: {}\tend: {}".format(
                site,
                self.getSiteData(site).siteStart,
                self.getSiteData(site).siteEnd,
            ))
        textLst.append("Sampling frequencies found in project (Hz): {}".format(
            listToString(self.getSampleFreqs())))
        return textLst
예제 #2
0
    def write(self):
        """Write transfer function data in internal format file"""

        outF = open(self.filepath, "w")
        self.printText("Writing out transfer function data to file {}".format(
            self.filepath))
        # write out the headers
        for h, v in self.headers.items():
            if isinstance(v, list):
                v = listToString(v)
                if len(v) == 0:
                    v = "False"
            outF.write("{} = {}\n".format(h, v))

        # write evaluation frequencies
        outF.write("Evaluation frequencies\n")
        outF.write("{}\n".format(arrayToString(self.tfData.freq)))

        # now need to write out the other data
        for pol in self.polarisations:
            outF.write("Z-{}\n".format(pol))
            outF.write("{}\n".format(arrayToString(self.tfData.data[pol])))

        # variances
        for pol in self.polarisations:
            outF.write("Var-{}\n".format(pol))
            outF.write("{}\n".format(arrayToString(
                self.tfData.variances[pol])))

        outF.close()
예제 #3
0
    def printList(self) -> List[str]:
        """Class information as a list of strings

        Returns
        -------
        out : list
            List of strings with information
        """

        textLst = []
        textLst.append("Default options")
        textLst.append("\tInput Chans = {}".format(listToString(self.getInChans())))
        textLst.append("\tOutput Chans = {}".format(listToString(self.getOutChans())))
        textLst.append(
            "\tRemote Chans = {}".format(listToString(self.getRemoteChans()))
        )
        textLst.append("\tPowers = {}".format(listToString(self.getPSDChans())))
        textLst.append(
            "\tCoherence pairs = {}".format(listToString(self.getCohPairs()))
        )
        textLst.append(
            "\tPartial coherence = {}".format(listToString(self.getPolDirs()))
        )
        if len(self.getEvalFreq()) == 0:
            textLst.append("Evaluation frequencies = {}")
        else:
            textLst.append(
                "Evaluation frequencies = {}".format(arrayToString(self.getEvalFreq()))
            )
        return textLst
예제 #4
0
    def write(self, maskData: MaskData) -> None:
        """Write the maskData out to datapath

        Mask data is saved as a numpy binary object

        Parameters
        ----------
        maskData : MaskData
            MaskData object
        """

        infoName, winName = self.getFileNames(maskData.maskName,
                                              maskData.sampleFreq)
        infoFile = open(infoName, "w")
        # first write out constraints
        infoFile.write("{:.9f}\n".format(maskData.sampleFreq))
        infoFile.write("{}\n".format(maskData.numLevels))
        for iL in range(0, maskData.numLevels):
            infoFile.write("{}\n".format(listToString(maskData.evalFreq[iL])))
        infoFile.write("{}\n".format(", ".join(maskData.stats)))
        # now write out the data file
        # first get a sorted list of all the evaluations frequencies to loop through
        evalFreq = sorted(list(maskData.constraints.keys()))
        for eFreq in evalFreq:
            infoFile.write("Frequency = {:.9f}\n".format(eFreq))
            for stat in maskData.stats:
                infoFile.write("Statistic = {}\n".format(stat))
                for component in maskData.constraints[eFreq][stat]:
                    minVal = maskData.constraints[eFreq][stat][component][0]
                    maxVal = maskData.constraints[eFreq][stat][component][1]
                    infoFile.write("{}\t{}\t{}\t{}\n".format(
                        component,
                        minVal,
                        maxVal,
                        maskData.insideOut[eFreq][stat][component],
                    ))
        # then loop through each
        infoFile.close()
        maskSize = 0
        for eFreq in evalFreq:
            if len(maskData.maskWindows[eFreq]) > maskSize:
                maskSize = len(maskData.maskWindows[eFreq])
        # create window mask array and initalise to -1
        winMaskArray = np.ones(shape=(len(evalFreq), maskSize), dtype=int) * -1
        # now fill the array
        for eIdx, eFreq in enumerate(evalFreq):
            lst = list(maskData.maskWindows[eFreq])
            winMaskArray[eIdx, 0:len(lst)] = lst
        np.save(winName, winMaskArray)
예제 #5
0
    def printListSection(self, section: str) -> List[str]:
        """Configuration section information as a list of strings

        Returns
        -------
        out : List[str]
            List of strings with information
        """

        textLst: List[str] = []
        textLst.append("{:s}:".format(section))
        for key, value in self.configParams[section].items():
            textLst.append("\t{:s} = {}".format(key, value))
        defaultOptions = "No defaults used"
        if len(self.configParams[section].defaults) > 0:
            defaultOptions = listToString(self.configParams[section].defaults) 
        textLst.append("\tDefaulted options = {:s}".format(defaultOptions))
        
        return textLst
예제 #6
0
    def viewImpedance(self, **kwargs) -> plt.figure:
        """Plots the transfer function data

        For resistivity data, both axes are log scale (period and resistivity). For phase data, period is in log scale and phase is linear scale.
        Units, x axis is seconds, resistivity is Ohm m and phase is degrees.

        Parameters
        ----------
        polarisations : List[str], optional
            Polarisations to plot
        fig : matplotlib.pyplot.figure, optional
            A figure object
        oneplot : bool, optional   
            Boolean flag for plotting all polarisations on one plot rather than separate plots               
        colours : Dict[str, str], optional
            Colours dictionary for plotting impedance components 
        mk : str, optional
            Plot marker type
        ls : str, optional
            Line style  
        plotfonts : Dict, optional
            A dictionary of plot fonts
        label : str, optional
            Label for the plots
        xlim : List, optional
            Limits for the x axis
        res_ylim : List, optional
            Limits for the resistivity y axis
        phase_ylim : List, optional
            Limits for the phase y axis 

        Returns
        -------
        plt.figure
            Matplotlib figure object            
        """

        polarisations = (kwargs["polarisations"]
                         if "polarisations" in kwargs else self.polarisations)

        # limits
        xlim = kwargs["xlim"] if "xlim" in kwargs else [1e-3, 1e4]
        res_ylim = kwargs["res_ylim"] if "res_ylim" in kwargs else [1e-2, 1e3]
        phase_ylim = kwargs["phase_ylim"] if "phase_ylim" in kwargs else [
            -30, 120
        ]

        # markers
        colours = (kwargs["colours"]
                   if "colours" in kwargs else transferFunctionColours())
        mk = kwargs["mk"] if "mk" in kwargs else "o"
        ls = kwargs["ls"] if "ls" in kwargs else "none"
        plotfonts = kwargs[
            "plotfonts"] if "plotfonts" in kwargs else getViewFonts()

        # calculate number of rows and columns
        oneplot = False
        if "oneplot" in kwargs and kwargs["oneplot"]:
            oneplot = True
        nrows = 2
        ncols = 1 if oneplot else len(polarisations)
        # a multiplier to make sure all the components end up on the right plot
        plotNumMult = 0 if ncols > 1 else 1

        # plot
        if "fig" in kwargs:
            fig = plt.figure(kwargs["fig"].number)
        else:
            figsize = getTransferFunctionFigSize(oneplot, len(polarisations))
            fig = plt.figure(figsize=figsize)

        st = fig.suptitle(
            "Impedance tensor apparent resistivity and phase",
            fontsize=plotfonts["suptitle"],
        )
        st.set_y(0.98)

        for idx, pol in enumerate(polarisations):
            res, phase = self.getResAndPhase(pol)
            resError, phaseError = self.getResAndPhaseErrors(pol)
            label = kwargs["label"] + " - {}".format(
                pol) if "label" in kwargs else pol
            # plot resistivity
            ax1 = plt.subplot(nrows, ncols, idx + 1 - plotNumMult * idx)
            # the title
            if not oneplot:
                plt.title("Polarisation {}".format(pol),
                          fontsize=plotfonts["title"])
            else:
                plt.title(
                    "Polarisations {}".format(listToString(polarisations)),
                    fontsize=plotfonts["title"],
                )
            # plot the data
            ax1.errorbar(
                self.period,
                res,
                yerr=resError,
                ls=ls,
                marker=mk,
                markersize=9,
                markerfacecolor="white",
                markeredgecolor=colours[pol],
                mew=1.1,
                color=colours[pol],
                ecolor=colours[pol],
                elinewidth=1.0,
                capsize=4,
                barsabove=False,
                label=label,
            )
            ax1.set_xscale("log")
            ax1.set_yscale("log")
            ax1.set_aspect("equal", adjustable="box")
            # axis options
            plt.ylabel("Apparent Res. [Ohm m]",
                       fontsize=plotfonts["axisLabel"])
            plt.xlim(xlim)
            plt.ylim(res_ylim)
            # set tick sizes
            for lab in ax1.get_xticklabels() + ax1.get_yticklabels():
                lab.set_fontsize(plotfonts["axisTicks"])

            # plot phase
            ax2 = plt.subplot(nrows, ncols,
                              ncols + idx + 1 - plotNumMult * idx)
            # plot the data
            ax2.errorbar(
                self.period,
                phase,
                yerr=phaseError,
                ls="none",
                marker=mk,
                markersize=9,
                markerfacecolor="white",
                markeredgecolor=colours[pol],
                mew=1.1,
                color=colours[pol],
                ecolor=colours[pol],
                elinewidth=1.0,
                capsize=4,
                barsabove=False,
                label=label,
            )
            ax2.set_xscale("log")
            # axis options
            plt.xlabel("Period [s]", fontsize=plotfonts["axisLabel"])
            plt.ylabel("Phase [degrees]", fontsize=plotfonts["axisLabel"])
            plt.xlim(xlim)
            plt.ylim(phase_ylim)
            # set tick sizes
            for lab in ax2.get_xticklabels() + ax2.get_yticklabels():
                lab.set_fontsize(plotfonts["axisTicks"])

        # add the legend
        for idx, pol in enumerate(polarisations):
            ax1 = plt.subplot(nrows, ncols, idx + 1 - plotNumMult * idx)
            leg = plt.legend(loc="lower left", fontsize=plotfonts["legend"])
            leg.get_frame().set_linewidth(0.0)
            leg.get_frame().set_facecolor("w")
            plt.grid(True, ls="--")
            ax2 = plt.subplot(nrows, ncols,
                              ncols + idx + 1 - plotNumMult * idx)
            leg = plt.legend(loc="lower left", fontsize=plotfonts["legend"])
            leg.get_frame().set_linewidth(0.0)
            leg.get_frame().set_facecolor("w")
            plt.grid(True, ls="--")

        # show if the figure is not in keywords
        if "fig" not in kwargs:
            # layout options
            plt.tight_layout()
            fig.subplots_adjust(top=0.92)
            plt.show()

        return fig
예제 #7
0
def calculateRemoteStatistics(projData: ProjectData, remoteSite: str,
                              **kwargs):
    """Calculate statistics involving a remote reference site

    
    Parameters
    ----------
    projData : ProjectData
        A project data instance
    remoteSite : str
        The name of the site to use as the remote site
    sites : List[str], optional
        A list of sites to calculate statistics for
    sampleFreqs : List[float], optional
        List of sampling frequencies for which to calculate statistics
    chans : List[str], optional
        List of data channels to use
    specdir : str, optional
        The spectra directory for which to calculate statistics
    remotestats : List[str], optional
        The statistics to calculate out. Acceptable statistics are: "RR_coherence", "RR_coherenceEqn", "RR_absvalEqn", "RR_transferFunction", "RR_resPhase". Configuration file values are used by default.
    """

    options = {}
    options["sites"] = projData.getSites()
    options["sampleFreqs"] = projData.getSampleFreqs()
    options["chans"] = []
    options["specdir"] = projData.config.configParams["Spectra"]["specdir"]
    options["remotestats"] = projData.config.configParams["Statistics"][
        "remotestats"]
    options = parseKeywords(options, kwargs)

    projectText(
        "Calculating stats: {} for sites: {} with remote site {}".format(
            listToString(options["remotestats"]),
            listToString(options["sites"]),
            remoteSite,
        ))

    # create the statistic calculator and IO object
    statCalculator = StatisticCalculator()
    statIO = StatisticIO()

    # loop over sites
    for site in options["sites"]:
        siteData = projData.getSiteData(site)
        measurements = siteData.getMeasurements()

        for meas in measurements:
            sampleFreq = siteData.getMeasurementSampleFreq(meas)
            if sampleFreq not in options["sampleFreqs"]:
                # don't need to calculate statistics for this sampling frequency
                continue

            projectText(
                "Calculating stats for site {}, measurement {} with reference {}"
                .format(site, meas, remoteSite))

            # decimation and window parameters
            decParams = getDecimationParameters(sampleFreq, projData.config)
            decParams.printInfo()
            numLevels = decParams.numLevels
            winParams = getWindowParameters(decParams, projData.config)

            # create the window selector and find the shared windows
            winSelector = getWindowSelector(projData, decParams, winParams)
            winSelector.setSites([site, remoteSite])
            # calc shared windows between site and remote
            winSelector.calcSharedWindows()
            # create the spectrum reader
            specReader = SpectrumReader(
                os.path.join(siteData.getMeasurementSpecPath(meas),
                             options["specdir"]))

            # loop through decimation levels
            for iDec in range(0, numLevels):
                # open the spectra file for the current decimation level
                check = specReader.openBinaryForReading("spectra", iDec)
                if not check:
                    # probably because this decimation level not calculated
                    continue
                specReader.printInfo()

                # get a set of the shared windows at this decimation level
                # these are the global indices
                sharedWindows = winSelector.getSharedWindowsLevel(iDec)

                # get other information regarding only this spectra file
                refTime = specReader.getReferenceTime()
                winSize = specReader.getWindowSize()
                winOlap = specReader.getWindowOverlap()
                numWindows = specReader.getNumWindows()
                evalFreq = decParams.getEvalFrequenciesForLevel(iDec)
                sampleFreqDec = specReader.getSampleFreq()
                globalOffset = specReader.getGlobalOffset()
                fArray = specReader.getFrequencyArray()

                # now want to find the size of the intersection between the windows in this file and the shared windows
                sharedWindowsMeas = sharedWindows.intersection(
                    set(np.arange(globalOffset, globalOffset + numWindows)))
                sharedWindowsMeas = sorted(list(sharedWindowsMeas))
                numSharedWindows = len(sharedWindowsMeas)

                statHandlers = {}
                # create the statistic handlers
                for stat in options["remotestats"]:
                    statElements = getStatElements(stat)
                    statHandlers[stat] = StatisticData(stat, refTime,
                                                       sampleFreqDec, winSize,
                                                       winOlap)
                    # remember, this is with the remote reference, so the number of windows is number of shared windows
                    statHandlers[stat].setStatParams(numSharedWindows,
                                                     statElements, evalFreq)
                    statHandlers[stat].comments = specReader.getComments()
                    statHandlers[stat].addComment(
                        projData.config.getConfigComment())
                    statHandlers[stat].addComment(
                        "Calculating remote statistic: {}".format(stat))
                    statHandlers[stat].addComment(
                        "Statistic components: {}".format(
                            listToString(statElements)))

                # loop over the shared windows between the remote station and local station
                for iW, globalWindow in enumerate(sharedWindowsMeas):
                    # get data and set in the statCalculator
                    winData = specReader.readBinaryWindowGlobal(globalWindow)
                    statCalculator.setSpectra(fArray, winData, evalFreq)
                    # for the remote site, use the reader in win selector
                    remoteSF, remoteReader = winSelector.getSpecReaderForWindow(
                        remoteSite, iDec, globalWindow)
                    winDataRR = remoteReader.readBinaryWindowGlobal(
                        globalWindow)
                    statCalculator.addRemoteSpec(winDataRR)

                    for sH in statHandlers:
                        data = statCalculator.getDataForStatName(sH)
                        statHandlers[sH].addStat(iW, globalWindow, data)

                # save statistic
                for sH in statHandlers:
                    statIO.setDatapath(
                        os.path.join(siteData.getMeasurementStatPath(meas),
                                     options["specdir"]))
                    statIO.write(statHandlers[sH], iDec)
예제 #8
0
def calculateStatistics(projData: ProjectData, **kwargs):
    """Calculate statistics for sites
    
    Parameters
    ----------
    projData : ProjectData
        A project data instance
    sites : List[str], optional
        A list of sites to calculate statistics for
    sampleFreqs : List[float], optional
        List of sampling frequencies for which to calculate statistics
    chans : List[str], optional
        List of data channels to use
    specdir : str, optional
        The spectra directory for which to calculate statistics
    stats : List[str], optional
        The statistics to calculate out. Acceptable values are: "absvalEqn" "coherence", "psd", "poldir", "transFunc", "resPhase", "partialcoh". Configuration file values are used by default.
    """

    options = {}
    options["sites"] = projData.getSites()
    options["sampleFreqs"] = projData.getSampleFreqs()
    options["chans"] = []
    options["specdir"] = projData.config.configParams["Spectra"]["specdir"]
    options["stats"] = projData.config.configParams["Statistics"]["stats"]
    options = parseKeywords(options, kwargs)

    projectText("Calculating stats: {} for sites: {}".format(
        listToString(options["stats"]), listToString(options["sites"])))

    # create the statistic calculator and IO object
    statCalculator = StatisticCalculator()
    statIO = StatisticIO()

    # loop through sites and calculate statistics
    for site in options["sites"]:
        siteData = projData.getSiteData(site)
        measurements = siteData.getMeasurements()

        for meas in measurements:
            sampleFreq = siteData.getMeasurementSampleFreq(meas)
            if sampleFreq not in options["sampleFreqs"]:
                # don't need to calculate statistics for this sampling frequency
                continue

            projectText("Calculating stats for site {}, measurement {}".format(
                site, meas))

            # decimation parameters
            decParams = getDecimationParameters(sampleFreq, projData.config)
            decParams.printInfo()
            numLevels = decParams.numLevels

            # create the spectrum reader
            specReader = SpectrumReader(
                os.path.join(siteData.getMeasurementSpecPath(meas),
                             options["specdir"]))

            # loop through decimation levels
            for iDec in range(0, numLevels):
                # open the spectra file for the current decimation level
                check = specReader.openBinaryForReading("spectra", iDec)
                if not check:
                    # probably because this decimation level not calculated
                    continue
                specReader.printInfo()

                # get windows
                refTime = specReader.getReferenceTime()
                winSize = specReader.getWindowSize()
                winOlap = specReader.getWindowOverlap()
                numWindows = specReader.getNumWindows()
                evalFreq = decParams.getEvalFrequenciesForLevel(iDec)
                sampleFreqDec = specReader.getSampleFreq()
                globalOffset = specReader.getGlobalOffset()
                fArray = specReader.getFrequencyArray()

                statHandlers = {}
                # create the statistic handlers
                for stat in options["stats"]:
                    statElements = getStatElements(stat)
                    statHandlers[stat] = StatisticData(stat, refTime,
                                                       sampleFreqDec, winSize,
                                                       winOlap)
                    statHandlers[stat].setStatParams(numWindows, statElements,
                                                     evalFreq)
                    statHandlers[stat].comments = specReader.getComments()
                    statHandlers[stat].addComment(
                        projData.config.getConfigComment())
                    statHandlers[stat].addComment(
                        "Calculating statistic: {}".format(stat))
                    statHandlers[stat].addComment(
                        "Statistic components: {}".format(
                            listToString(statElements)))

                # loop over windows and calculate the relevant statistics
                for iW in range(0, numWindows):
                    # get data
                    winData = specReader.readBinaryWindowLocal(iW)
                    globalIndex = iW + globalOffset
                    # give the statistic calculator the spectra
                    statCalculator.setSpectra(fArray, winData, evalFreq)
                    # get the desired statistics
                    for sH in statHandlers:
                        data = statCalculator.getDataForStatName(sH)
                        statHandlers[sH].addStat(iW, globalIndex, data)

                # save statistic
                for sH in statHandlers:
                    statIO.setDatapath(
                        os.path.join(siteData.getMeasurementStatPath(meas),
                                     options["specdir"]))
                    statIO.write(statHandlers[sH], iDec)
예제 #9
0
def calculateSpectra(projData: ProjectData, **kwargs) -> None:
    """Calculate spectra for the project time data

    The philosophy is that spectra are calculated out for all data and later limited using statistics and time constraints

    Parameters
    ----------
    projData : ProjectData
        A project data object
    sites : str, List[str], optional
        Either a single site or a list of sites
    sampleFreqs : int, float, List[float], optional
        The frequencies in Hz for which to calculate the spectra. Either a single frequency or a list of them.
    chans : List[str], optional
        The channels for which to calculate out the spectra
    polreverse :  Dict[str, bool]
        Keys are channels and values are boolean flags for reversing
    scale : Dict[str, float]
        Keys are channels and values are floats to multiply the channel data by
    calibrate : bool, optional
        Flag whether to calibrate the data or not
    notch : List[float], optional
        List of frequencies to notch
    filter : Dict, optional
        Filter parameters
    specdir : str, optional
        The spectra directory to save the spectra data in
    """

    # default options
    options = {}
    options["sites"] = projData.getSites()
    options["sampleFreqs"]: List[float] = projData.getSampleFreqs()
    options["chans"]: List[str] = []
    options["polreverse"]: Union[bool, Dict[str, bool]] = False
    options["scale"]: Union[bool, Dict[str, float]] = False       
    options["calibrate"]: bool = True
    options["notch"]: List[float] = []
    options["filter"]: Dict = {}
    options["specdir"]: str = projData.config.configParams["Spectra"]["specdir"]
    options = parseKeywords(options, kwargs)

    # prepare calibrator
    cal = getCalibrator(projData.calPath, projData.config)
    if options["calibrate"]:
        cal.printInfo()

    datetimeRef = projData.refTime
    for site in options["sites"]:
        siteData = projData.getSiteData(site)
        siteData.printInfo()

        # calculate spectra for each frequency
        for sampleFreq in options["sampleFreqs"]:
            measurements = siteData.getMeasurements(sampleFreq)
            projectText(
                "Site {} has {:d} measurement(s) at sampling frequency {:.2f}".format(
                    site, len(measurements), sampleFreq
                )
            )
            if len(measurements) == 0:
                continue  # no data files at this sample rate

            for meas in measurements:
                projectText(
                    "Calculating spectra for site {} and measurement {}".format(
                        site, meas
                    )
                )
                # get measurement start and end times - this is the time of the first and last sample
                reader = siteData.getMeasurement(meas)
                startTime = siteData.getMeasurementStart(meas)
                stopTime = siteData.getMeasurementEnd(meas)
                dataChans = (
                    options["chans"]
                    if len(options["chans"]) > 0
                    else reader.getChannels()
                )
                timeData = reader.getPhysicalData(startTime, stopTime, chans=dataChans)
                timeData.addComment(breakComment())
                timeData.addComment("Calculating project spectra")
                timeData.addComment(projData.config.getConfigComment())

                # apply various options
                applyPolarisationReversalOptions(options, timeData)
                applyScaleOptions(options, timeData)
                applyCalibrationOptions(options, cal, timeData, reader)
                applyFilterOptions(options, timeData)
                applyNotchOptions(options, timeData)

                # define decimation and window parameters
                decParams = getDecimationParameters(sampleFreq, projData.config)
                decParams.printInfo()
                numLevels = decParams.numLevels
                winParams = getWindowParameters(decParams, projData.config)
                dec = Decimator(timeData, decParams)
                timeData.addComment(
                    "Decimating with {} levels and {} frequencies per level".format(
                        numLevels, decParams.freqPerLevel
                    )
                )

                # loop through decimation levels
                for iDec in range(0, numLevels):
                    # get the data for the current level
                    check = dec.incrementLevel()
                    if not check:
                        break  # not enough data
                    timeData = dec.timeData

                    # create the windower and give it window parameters for current level
                    sampleFreqDec = dec.sampleFreq
                    win = Windower(
                        datetimeRef,
                        timeData,
                        winParams.getWindowSize(iDec),
                        winParams.getOverlap(iDec),
                    )
                    if win.numWindows < 2:
                        break  # do no more decimation

                    # add some comments
                    timeData.addComment(
                        "Evaluation frequencies for this level {}".format(
                            listToString(decParams.getEvalFrequenciesForLevel(iDec))
                        )
                    )
                    timeData.addComment(
                        "Windowing with window size {} samples and overlap {} samples".format(
                            winParams.getWindowSize(iDec), winParams.getOverlap(iDec)
                        )
                    )

                    # create the spectrum calculator and statistics calculators
                    specCalc = SpectrumCalculator(
                        sampleFreqDec, winParams.getWindowSize(iDec)
                    )
                    # get ready a file to save the spectra
                    specPath = os.path.join(
                        siteData.getMeasurementSpecPath(meas), options["specdir"]
                    )
                    specWrite = SpectrumWriter(specPath, datetimeRef)
                    specWrite.openBinaryForWriting(
                        "spectra",
                        iDec,
                        sampleFreqDec,
                        winParams.getWindowSize(iDec),
                        winParams.getOverlap(iDec),
                        win.winOffset,
                        win.numWindows,
                        dataChans,
                    )

                    # loop though windows, calculate spectra and save
                    for iW in range(0, win.numWindows):
                        # get the window data
                        winData = win.getData(iW)
                        # calculate spectra
                        specData = specCalc.calcFourierCoeff(winData)
                        # write out spectra
                        specWrite.writeBinary(specData)

                    # close spectra file
                    specWrite.writeCommentsFile(timeData.getComments())
                    specWrite.closeFile()