Пример #1
0
    def QC(node, modRoot):
        ''' Add model data. QC for wind, Lt, SZA, spectral outliers, and met filters'''
        print(
            "Add model data. QC for wind, Lt, SZA, spectral outliers, and met filters"
        )

        referenceGroup = node.getGroup("IRRADIANCE")
        sasGroup = node.getGroup("RADIANCE")
        gpsGroup = node.getGroup('GPS')
        satnavGroup = None
        ancGroup = None
        pyrGroup = None
        for gp in node.groups:
            if gp.id.startswith("SOLARTRACKER"):
                if gp.id != "SOLARTRACKER_STATUS":
                    satnavGroup = gp
            if gp.id.startswith("ANCILLARY"):
                ancGroup = gp
                ancGroup.id = "ANCILLARY"  # shift back from ANCILLARY_METADATA
            if gp.id.startswith("PYROMETER"):
                pyrGroup = gp

        # Regardless of whether SolarTracker/pySAS is used, Ancillary data will have been already been
        # interpolated in L1B as long as the ancillary file was read in at L1AQC. Regardless, these need
        # to have model data and/or default values incorporated.

        # If GMAO modeled data is selected in ConfigWindow, and an ancillary field data file
        # is provided in Main Window, then use the model data to fill in gaps in the field
        # record. Otherwise, use the selected default values from ConfigWindow

        # This step is only necessary for the ancillary datasets that REQUIRE
        # either field or GMAO or GUI default values. The remaining ancillary data
        # are culled from datasets in groups in L1B
        ProcessL1bqc.includeModelDefaults(ancGroup, modRoot)

        # Shift metadata into the ANCILLARY group as needed (i.e. from GPS).
        #
        # GPS Group
        # These have TT2/Datetag incorporated in arrays
        # Change their column names from NONE to something appropriate to be consistent in
        # ancillary group going forward.
        # Replace metadata lat/long with GPS lat/long, in case the former is from the ancillary file
        ancGroup.datasets['LATITUDE'] = gpsGroup.getDataset('LATITUDE')
        ancGroup.datasets['LATITUDE'].changeColName('NONE', 'LATITUDE')
        ancGroup.datasets['LONGITUDE'] = gpsGroup.getDataset('LONGITUDE')
        ancGroup.datasets['LONGITUDE'].changeColName('NONE', 'LONGITUDE')

        # Take Heading and Speed preferentially from GPS
        if 'HEADING' in gpsGroup.datasets:
            # These have TT2/Datetag incorporated in arrays
            ancGroup.addDataset('HEADING')
            ancGroup.datasets['HEADING'] = gpsGroup.getDataset('COURSE')
            ancGroup.datasets['HEADING'].changeColName('TRUE', 'HEADING')
        if 'SOG' in gpsGroup.datasets:
            ancGroup.addDataset('SOG')
            ancGroup.datasets['SOG'] = gpsGroup.getDataset('SOG')
            ancGroup.datasets['SOG'].changeColName('NONE', 'SOG')
        if 'HEADING' not in gpsGroup.datasets and 'HEADING' in ancGroup.datasets:
            ancGroup.addDataset('HEADING')
            # ancGroup.datasets['HEADING'] = ancTemp.getDataset('HEADING')
            ancGroup.datasets['HEADING'] = ancGroup.getDataset('HEADING')
            ancGroup.datasets['HEADING'].changeColName('NONE', 'HEADING')
        if 'SOG' not in gpsGroup.datasets and 'SOG' in ancGroup.datasets:
            ancGroup.datasets['SOG'] = ancGroup.getDataset('SOG')
            ancGroup.datasets['SOG'].changeColName('NONE', 'SOG')
        if 'SPEED_F_W' in ancGroup.datasets:
            ancGroup.addDataset('SPEED_F_W')
            ancGroup.datasets['SPEED_F_W'] = ancGroup.getDataset('SPEED_F_W')
            ancGroup.datasets['SPEED_F_W'].changeColName('NONE', 'SPEED_F_W')
        # Take SZA and SOLAR_AZ preferentially from ancGroup (calculated with pysolar in L1C)
        ancGroup.datasets['SZA'].changeColName('NONE', 'SZA')
        ancGroup.datasets['SOLAR_AZ'].changeColName('NONE', 'SOLAR_AZ')
        if 'CLOUD' in ancGroup.datasets:
            ancGroup.datasets['CLOUD'].changeColName('NONE', 'CLOUD')
        if 'PITCH' in ancGroup.datasets:
            ancGroup.datasets['PITCH'].changeColName('NONE', 'PITCH')
        if 'ROLL' in ancGroup.datasets:
            ancGroup.datasets['ROLL'].changeColName('NONE', 'ROLL')
        if 'STATION' in ancGroup.datasets:
            ancGroup.datasets['STATION'].changeColName('NONE', 'STATION')
        if 'WAVE_HT' in ancGroup.datasets:
            ancGroup.datasets['WAVE_HT'].changeColName('NONE', 'WAVE_HT')
        if 'SALINITY' in ancGroup.datasets:
            ancGroup.datasets['SALINITY'].changeColName('NONE', 'SALINITY')
        if 'WINDSPEED' in ancGroup.datasets:
            ancGroup.datasets['WINDSPEED'].changeColName('NONE', 'WINDSPEED')
        if 'SST' in ancGroup.datasets:
            ancGroup.datasets['SST'].changeColName('NONE', 'SST')

        if satnavGroup:
            ancGroup.datasets['REL_AZ'] = satnavGroup.getDataset('REL_AZ')
            if 'HUMIDITY' in ancGroup.datasets:
                ancGroup.datasets['HUMIDITY'] = satnavGroup.getDataset(
                    'HUMIDITY')
                ancGroup.datasets['HUMIDITY'].changeColName('NONE', 'HUMIDITY')
            # ancGroup.datasets['HEADING'] = satnavGroup.getDataset('HEADING') # Use GPS heading instead
            ancGroup.addDataset('POINTING')
            ancGroup.datasets['POINTING'] = satnavGroup.getDataset('POINTING')
            ancGroup.datasets['POINTING'].changeColName('ROTATOR', 'POINTING')
            ancGroup.datasets['REL_AZ'] = satnavGroup.getDataset('REL_AZ')
            ancGroup.datasets['REL_AZ'].datasetToColumns()
            # Use PITCH and ROLL preferentially from SolarTracker
            if 'PITCH' in satnavGroup.datasets:
                ancGroup.addDataset('PITCH')
                ancGroup.datasets['PITCH'] = satnavGroup.getDataset('PITCH')
                ancGroup.datasets['PITCH'].changeColName('SAS', 'PITCH')
            if 'ROLL' in satnavGroup.datasets:
                ancGroup.addDataset('ROLL')
                ancGroup.datasets['ROLL'] = satnavGroup.getDataset('ROLL')
                ancGroup.datasets['ROLL'].changeColName('SAS', 'ROLL')

        if 'NONE' in ancGroup.datasets['REL_AZ'].columns:
            ancGroup.datasets['REL_AZ'].changeColName('NONE', 'REL_AZ')

        if pyrGroup is not None:
            #PYROMETER
            ancGroup.datasets['SST_IR'] = pyrGroup.getDataset("T")
            ancGroup.datasets['SST_IR'].datasetToColumns()
            ancGroup.datasets['SST_IR'].changeColName('IR', 'SST_IR')

        # At this stage, all datasets in all groups of node have Timetag2
        #     and Datetag incorporated into data arrays. Calculate and add
        #     Datetime to each data array.
        Utilities.rootAddDateTimeCol(node)

        #################################################################################

        #   Filter the spectra from the entire collection before slicing the intervals at L2

        ##################################################################################

        # Lt Quality Filtering; anomalous elevation in the NIR
        if ConfigFile.settings["bL1bqcLtUVNIR"]:
            msg = "Applying Lt(NIR)>Lt(UV) quality filtering to eliminate spectra."
            print(msg)
            Utilities.writeLogFile(msg)
            # This is not well optimized for large files...
            badTimes = ProcessL1bqc.ltQuality(sasGroup)

            if badTimes is not None:
                print('Removing records... Can be slow for large files')
                check = Utilities.filterData(referenceGroup, badTimes)
                # check is now fraction removed
                #   I.e., if >99% of the Es spectra from this entire file were remove, abort this file
                if check > 0.99:
                    msg = "Too few spectra remaining. Abort."
                    print(msg)
                    Utilities.writeLogFile(msg)
                    return False
                Utilities.filterData(sasGroup, badTimes)
                Utilities.filterData(ancGroup, badTimes)

        # Filter low SZAs and high winds after interpolating model/ancillary data
        maxWind = float(ConfigFile.settings["fL1bqcMaxWind"])

        wind = ancGroup.getDataset("WINDSPEED").data["WINDSPEED"]
        timeStamp = ancGroup.datasets["WINDSPEED"].columns["Datetime"]

        badTimes = None
        i = 0
        start = -1
        stop = []
        for index, _ in enumerate(wind):
            if wind[index] > maxWind:
                i += 1
                if start == -1:
                    msg = f'High Wind: {round(wind[index])}'
                    Utilities.writeLogFile(msg)
                    start = index
                stop = index
                if badTimes is None:
                    badTimes = []
            else:
                if start != -1:
                    msg = f'Passed. Wind: {round(wind[index])}'
                    print(msg)
                    Utilities.writeLogFile(msg)
                    startstop = [timeStamp[start], timeStamp[stop]]
                    msg = f'   Flag data from TT2: {startstop[0]} to {startstop[1]}'
                    # print(msg)
                    Utilities.writeLogFile(msg)
                    badTimes.append(startstop)
                    start = -1
            end_index = index
        msg = f'Percentage of data out of Wind limits: {round(100*i/len(timeStamp))} %'
        print(msg)
        Utilities.writeLogFile(msg)

        if start != -1 and stop == end_index:  # Records from a mid-point to the end are bad
            startstop = [timeStamp[start], timeStamp[stop]]
            msg = f'   Flag data from TT2: {startstop[0]} to {startstop[1]}'
            # print(msg)
            Utilities.writeLogFile(msg)
            if badTimes is None:  # only one set of records
                badTimes = [startstop]
            else:
                badTimes.append(startstop)

        if start == 0 and stop == end_index:  # All records are bad
            return False

        if badTimes is not None and len(badTimes) != 0:
            print('Removing records...')
            check = Utilities.filterData(referenceGroup, badTimes)
            if check > 0.99:
                msg = "Too few spectra remaining. Abort."
                print(msg)
                Utilities.writeLogFile(msg)
                return False
            Utilities.filterData(sasGroup, badTimes)
            Utilities.filterData(ancGroup, badTimes)

        # Filter SZAs
        SZAMin = float(ConfigFile.settings["fL1bqcSZAMin"])
        SZAMax = float(ConfigFile.settings["fL1bqcSZAMax"])

        SZA = ancGroup.datasets["SZA"].columns["SZA"]
        timeStamp = ancGroup.datasets["SZA"].columns["Datetime"]

        badTimes = None
        i = 0
        start = -1
        stop = []
        for index, _ in enumerate(SZA):
            if SZA[index] < SZAMin or SZA[index] > SZAMax or wind[
                    index] > maxWind:
                i += 1
                if start == -1:
                    msg = f'Low SZA. SZA: {round(SZA[index])}'
                    print(msg)
                    Utilities.writeLogFile(msg)
                    start = index
                stop = index
                if badTimes is None:
                    badTimes = []
            else:
                if start != -1:
                    msg = f'Passed. SZA: {round(SZA[index])}'
                    print(msg)
                    Utilities.writeLogFile(msg)
                    startstop = [timeStamp[start], timeStamp[stop]]
                    msg = f'   Flag data from TT2: {startstop[0]} to {startstop[1]}'
                    # print(msg)
                    Utilities.writeLogFile(msg)
                    badTimes.append(startstop)
                    start = -1
            end_index = index
        msg = f'Percentage of data out of SZA limits: {round(100*i/len(timeStamp))} %'
        print(msg)
        Utilities.writeLogFile(msg)

        if start != -1 and stop == end_index:  # Records from a mid-point to the end are bad
            startstop = [timeStamp[start], timeStamp[stop]]
            msg = f'   Flag data from TT2: {startstop[0]} to {startstop[1]}'
            # print(msg)
            Utilities.writeLogFile(msg)
            if badTimes is None:  # only one set of records
                badTimes = [startstop]
            else:
                badTimes.append(startstop)

        if start == 0 and stop == end_index:  # All records are bad
            return False

        if badTimes is not None and len(badTimes) != 0:
            print('Removing records...')
            check = Utilities.filterData(referenceGroup, badTimes)
            if check > 0.99:
                msg = "Too few spectra remaining. Abort."
                print(msg)
                Utilities.writeLogFile(msg)
                return False
            Utilities.filterData(sasGroup, badTimes)
            Utilities.filterData(ancGroup, badTimes)

    # Spectral Outlier Filter
        enableSpecQualityCheck = ConfigFile.settings[
            'bL1bqcEnableSpecQualityCheck']
        if enableSpecQualityCheck:
            badTimes = None
            msg = "Applying spectral filtering to eliminate noisy spectra."
            print(msg)
            Utilities.writeLogFile(msg)
            inFilePath = node.attributes['In_Filepath']
            badTimes1 = ProcessL1bqc.specQualityCheck(referenceGroup,
                                                      inFilePath)
            badTimes2 = ProcessL1bqc.specQualityCheck(sasGroup, inFilePath)
            if badTimes1 is not None and badTimes2 is not None:
                badTimes = np.append(badTimes1, badTimes2, axis=0)
            elif badTimes1 is not None:
                badTimes = badTimes1
            elif badTimes2 is not None:
                badTimes = badTimes2

            if badTimes is not None:
                print('Removing records...')
                check = Utilities.filterData(referenceGroup, badTimes)
                if check > 0.99:
                    msg = "Too few spectra remaining. Abort."
                    print(msg)
                    Utilities.writeLogFile(msg)
                    return False
                check = Utilities.filterData(sasGroup, badTimes)
                if check > 0.99:
                    msg = "Too few spectra remaining. Abort."
                    print(msg)
                    Utilities.writeLogFile(msg)
                    return False
                check = Utilities.filterData(ancGroup, badTimes)
                if check > 0.99:
                    msg = "Too few spectra remaining. Abort."
                    print(msg)
                    Utilities.writeLogFile(msg)
                    return False

        # Next apply the Meteorological Filter prior to slicing
        esData = referenceGroup.getDataset("ES")
        enableMetQualityCheck = int(
            ConfigFile.settings["bL1bqcEnableQualityFlags"])
        if enableMetQualityCheck:
            msg = "Applying meteorological filtering to eliminate spectra."
            print(msg)
            Utilities.writeLogFile(msg)
            badTimes = ProcessL1bqc.metQualityCheck(referenceGroup, sasGroup)

            if badTimes is not None:
                if len(badTimes) == esData.data.size:
                    msg = "All data flagged for deletion. Abort."
                    print(msg)
                    Utilities.writeLogFile(msg)
                    return False
                print('Removing records...')
                check = Utilities.filterData(referenceGroup, badTimes)
                if check > 0.99:
                    msg = "Too few spectra remaining. Abort."
                    print(msg)
                    Utilities.writeLogFile(msg)
                    return False
                Utilities.filterData(sasGroup, badTimes)
                Utilities.filterData(ancGroup, badTimes)

        return True