Пример #1
0
    def derive(self,
               alt_rad=P('Altitude Radio'),
               alt_agl=P('Altitude AGL'),
               gog=M('Gear On Ground'),
               rtr=S('Rotors Turning')):
        # When was the gear in the air?
        gear_off_grounds = runs_of_ones(gog.array == 'Air')

        if alt_rad and alt_agl and rtr:
            # We can do a full analysis.
            # First, confirm that the rotors were turning at this time:
            gear_off_grounds = slices_and(gear_off_grounds, rtr.get_slices())

            # When did the radio altimeters indicate airborne?
            airs = slices_remove_small_gaps(
                np.ma.clump_unmasked(
                    np.ma.masked_less_equal(alt_agl.array, 1.0)),
                time_limit=AIRBORNE_THRESHOLD_TIME_RW,
                hz=alt_agl.frequency)
            # Both is a reliable indication of being in the air.
            for air in airs:
                for goff in gear_off_grounds:
                    # Providing they relate to each other :o)
                    if slices_overlap(air, goff):
                        start_index = max(air.start, goff.start)
                        end_index = min(air.stop, goff.stop)

                        better_begin = index_at_value(
                            alt_rad.array,
                            1.0,
                            _slice=slice(
                                max(start_index - 5 * alt_rad.frequency, 0),
                                start_index + 5 * alt_rad.frequency))
                        if better_begin:
                            begin = better_begin
                        else:
                            begin = start_index

                        better_end = index_at_value(
                            alt_rad.array,
                            1.0,
                            _slice=slice(
                                max(end_index + 5 * alt_rad.frequency, 0),
                                end_index - 5 * alt_rad.frequency, -1))
                        if better_end:
                            end = better_end
                        else:
                            end = end_index

                        duration = end - begin
                        if (duration /
                                alt_rad.hz) > AIRBORNE_THRESHOLD_TIME_RW:
                            self.create_phase(slice(begin, end))
        else:
            # During data validation we can select just sensible flights;
            # short hops make parameter validation tricky!
            self.create_phases(
                slices_remove_small_gaps(
                    slices_remove_small_slices(gear_off_grounds,
                                               time_limit=30)))
Пример #2
0
    def derive(self, alt_rad=P('Altitude Radio'),
               alt_aal=P('Altitude AAL'),
               alt_baro=P('Altitude STD Smoothed'),
               gog=M('Gear On Ground')):

        # If we have no Altitude Radio we will have to fall back to Altitude AAL
        if not alt_rad:
            self.array = alt_aal.array
            return

        # When was the helicopter on the ground?
        gear_on_grounds = np.ma.clump_masked(np.ma.masked_equal(gog.array, 1))
        # Find and eliminate short spikes (15 seconds) as these are most likely errors.
        short_spikes = slices_find_small_slices(gear_on_grounds, time_limit=20, hz=gog.hz)
        for _slice in short_spikes:
            gog.array[_slice.start:_slice.stop] = 0

        # Remove slices shorter than 15 seconds as these are most likely created in error.
        gear_on_grounds = slices_remove_small_slices(gear_on_grounds, time_limit=20, hz=gog.hz)
        # Compute the half period which we will need.
        hp = int(alt_rad.frequency*ALTITUDE_AGL_SMOOTHING) // 2

        # We force altitude AGL to be zero when the gear shows 'Ground' state
        alt_agl = moving_average(np.maximum(alt_rad.array, 0.0) * (1 - gog.array.data), window=hp*2+1, weightings=None)

        # Refine the baro estimates
        length = len(alt_agl)-1
        baro_sections = np.ma.clump_masked(np.ma.masked_greater(alt_agl, ALTITUDE_AGL_TRANS_ALT))
        for baro_section in baro_sections:
            begin = max(baro_section.start - 1, 0)
            end = min(baro_section.stop + 1, length)
            start_diff = alt_baro.array[begin] - alt_agl[begin]
            stop_diff = alt_baro.array[end] - alt_agl[end]
            if start_diff is not np.ma.masked and stop_diff is not np.ma.masked:
                diff = np.linspace(start_diff, stop_diff, end-begin-2)
                alt_agl[begin+1:end-1] = alt_baro.array[begin+1:end-1]-diff
            elif start_diff is not np.ma.masked:
                alt_agl[begin+1:end-1] = alt_baro.array[begin+1:end-1] - start_diff
            elif stop_diff is not np.ma.masked:
                alt_agl[begin+1:end-1] = alt_baro.array[begin+1:end-1] - stop_diff
            else:
                pass
        low_sections = np.ma.clump_unmasked(np.ma.masked_greater(alt_agl, 5))
        for both in slices_and(low_sections, gear_on_grounds):
            alt_agl[both] = 0.0

        '''
        # Quick visual check of the altitude agl.
        import matplotlib.pyplot as plt
        plt.plot(alt_baro.array, 'y-')
        plt.plot(alt_rad.array, 'r-')
        plt.plot(alt_agl, 'b-')
        plt.show()
        '''

        self.array = alt_agl
    def derive(self, alt_rad=P('Altitude Radio'),
               alt_aal=P('Altitude AAL'),
               alt_baro=P('Altitude STD Smoothed'),
               gog=M('Gear On Ground')):

        # If we have no Altitude Radio we will have to fall back to Altitude AAL
        if not alt_rad:
            self.array = alt_aal.array
            return

        # When was the helicopter on the ground?
        gear_on_grounds = np.ma.clump_masked(np.ma.masked_equal(gog.array, 1))
        # Find and eliminate short spikes (15 seconds) as these are most likely errors.
        short_spikes = slices_find_small_slices(gear_on_grounds, time_limit=15, hz=gog.hz)
        for slice in short_spikes:
            gog.array[slice.start:slice.stop] = 0

        # Remove slices shorter than 15 seconds as these are most likely created in error.
        gear_on_grounds = slices_remove_small_slices(gear_on_grounds, time_limit=15, hz=gog.hz)
        # Compute the half period which we will need.
        hp = int(alt_rad.frequency*ALTITUDE_AGL_SMOOTHING)//2
        # We force altitude AGL to be zero when the gear shows 'Ground' state
        alt_agl = moving_average(np.maximum(alt_rad.array, 0.0) * (1 - gog.array.data), window=hp*2+1, weightings=None)

        # Refine the baro estimates
        length = len(alt_agl)-1
        baro_sections = np.ma.clump_masked(np.ma.masked_greater(alt_agl, ALTITUDE_AGL_TRANS_ALT))
        for baro_section in baro_sections:
            begin = max(baro_section.start - 1, 0)
            end = min(baro_section.stop + 1, length)
            start_diff = alt_baro.array[begin] - alt_agl[begin]
            stop_diff = alt_baro.array[end] - alt_agl[end]
            if start_diff is not np.ma.masked and stop_diff is not np.ma.masked:
                diff = np.linspace(start_diff, stop_diff, end-begin-2)
                alt_agl[begin+1:end-1] = alt_baro.array[begin+1:end-1]-diff
            elif start_diff is not np.ma.masked:
                alt_agl[begin+1:end-1] = alt_baro.array[begin+1:end-1] - start_diff
            elif stop_diff is not np.ma.masked:
                alt_agl[begin+1:end-1] = alt_baro.array[begin+1:end-1] - stop_diff
            else:
                pass
        low_sections = np.ma.clump_unmasked(np.ma.masked_greater(alt_agl, 5))
        for both in slices_and(low_sections, gear_on_grounds):
            alt_agl[both] = 0.0

        '''
        # Quick visual check of the altitude agl.
        import matplotlib.pyplot as plt
        plt.plot(alt_baro.array, 'y-')
        plt.plot(alt_rad.array, 'r-')
        plt.plot(alt_agl, 'b-')
        plt.show()
        '''

        self.array = alt_agl
    def derive(self,
               alt_rad=P('Altitude Radio'),
               alt_agl=P('Altitude AGL'),
               gog=M('Gear On Ground'),
               rtr=S('Rotors Turning')):
        # When was the gear in the air?
        gear_off_grounds = runs_of_ones(gog.array == 'Air')

        if alt_rad and alt_agl and rtr:
            # We can do a full analysis.
            # First, confirm that the rotors were turning at this time:
            gear_off_grounds = slices_and(gear_off_grounds, rtr.get_slices())

            # When did the radio altimeters indicate airborne?
            airs = slices_remove_small_gaps(
                np.ma.clump_unmasked(np.ma.masked_less_equal(alt_agl.array, 1.0)),
                time_limit=AIRBORNE_THRESHOLD_TIME_RW, hz=alt_agl.frequency)
            # Both is a reliable indication of being in the air.
            for air in airs:
                for goff in gear_off_grounds:
                    # Providing they relate to each other :o)
                    if slices_overlap(air, goff):
                        start_index = max(air.start, goff.start)
                        end_index = min(air.stop, goff.stop)

                        better_begin = index_at_value(
                            alt_rad.array, 1.0,
                            _slice=slice(max(start_index-5*alt_rad.frequency, 0),
                                         start_index+5*alt_rad.frequency)
                        )
                        if better_begin:
                            begin = better_begin
                        else:
                            begin = start_index

                        better_end = index_at_value(
                            alt_rad.array, 1.0,
                            _slice=slice(max(end_index+5*alt_rad.frequency, 0),
                                         end_index-5*alt_rad.frequency, -1))
                        if better_end:
                            end = better_end
                        else:
                            end = end_index

                        duration = end - begin
                        if (duration / alt_rad.hz) > AIRBORNE_THRESHOLD_TIME_RW:
                            self.create_phase(slice(begin, end))
        else:
            # During data validation we can select just sensible flights;
            # short hops make parameter validation tricky!
            self.create_phases(
                slices_remove_small_gaps(
                    slices_remove_small_slices(gear_off_grounds, time_limit=30)))
Пример #5
0
    def derive(self,
               alt_agl=P('Altitude AGL'),
               airs=S('Airborne'),
               gspd=P('Groundspeed'),
               trans_hfs=S('Transition Hover To Flight'),
               trans_fhs=S('Transition Flight To Hover')):

        low_flights = []
        hovers = []

        for air in airs:
            lows = slices_below(alt_agl.array[air.slice],
                                HOVER_HEIGHT_LIMIT)[1]
            for low in lows:
                if np.ma.min(alt_agl.array[shift_slice(
                        low, air.slice.start)]) <= HOVER_MIN_HEIGHT:
                    low_flights.extend([shift_slice(low, air.slice.start)])

        repaired_gspd = repair_mask(gspd.array,
                                    frequency=gspd.hz,
                                    repair_duration=8,
                                    method='fill_start')

        slows = slices_below(repaired_gspd, HOVER_GROUNDSPEED_LIMIT)[1]
        low_flights = slices_and(low_flights, slows)
        # Remove periods identified already as transitions.
        for low_flight in low_flights:
            if trans_fhs:
                for trans_fh in trans_fhs:
                    if slices_overlap(low_flight, trans_fh.slice):
                        low_flight = slice(trans_fh.slice.stop,
                                           low_flight.stop)

            if trans_hfs:
                for trans_hf in trans_hfs:
                    if slices_overlap(low_flight, trans_hf.slice):
                        low_flight = slice(low_flight.start,
                                           trans_hf.slice.start)

            hovers.extend([low_flight])

        # Exclude transition periods and trivial periods of operation.
        self.create_phases(
            filter_slices_duration(hovers,
                                   HOVER_MIN_DURATION,
                                   frequency=alt_agl.frequency))
Пример #6
0
    def derive(self,
               gnds=S('Grounded'),
               pitch=P('Pitch'),
               roll=P('Roll'),
               offshores=S('Offshore')):

        if offshores is not None:
            self.create_sections(
                slices_and(gnds.get_slices(edges=False),
                           offshores.get_slices(edges=False)))
            return

        decks = []
        for gnd in gnds:
            # The fourier transform for pitching motion...
            p = pitch.array[gnd.slice]
            if np.all(p.mask):
                continue
            n = float(
                len(p))  # Scaling the result to be independet of data length.
            fft_p = np.abs(np.fft.rfft(p - moving_average(p))) / n

            # similarly for roll
            r = roll.array[gnd.slice]
            if np.all(r.mask):
                continue
            fft_r = np.abs(np.fft.rfft(r - moving_average(r))) / n

            # What was the maximum harmonic seen?
            fft_max = np.ma.max(fft_p + fft_r)

            # Values of less than 0.1 were on the ground, and 0.34 on deck for the one case seen to date.
            if fft_max > 0.2:
                decks.append(gnd.slice)
        if decks:
            self.create_sections(decks)
    def derive(self, alt_agl=P('Altitude AGL'),
               airs=S('Airborne'),
               gspd=P('Groundspeed'),
               trans_hfs=S('Transition Hover To Flight'),
               trans_fhs=S('Transition Flight To Hover')):

        low_flights = []
        hovers = []

        for air in airs:
            lows = slices_below(alt_agl.array[air.slice], HOVER_HEIGHT_LIMIT)[1]
            for low in lows:
                if np.ma.min(alt_agl.array[shift_slice(low, air.slice.start)]) <= HOVER_MIN_HEIGHT:
                    low_flights.extend([shift_slice(low, air.slice.start)])

        repaired_gspd = repair_mask(gspd.array, frequency=gspd.hz,
                                    repair_duration=8, method='fill_start')

        slows = slices_below(repaired_gspd, HOVER_GROUNDSPEED_LIMIT)[1]
        low_flights = slices_and(low_flights, slows)
        # Remove periods identified already as transitions.
        for low_flight in low_flights:
            if trans_fhs:
                for trans_fh in trans_fhs:
                    if slices_overlap(low_flight, trans_fh.slice):
                        low_flight = slice(trans_fh.slice.stop, low_flight.stop)

            if trans_hfs:
                for trans_hf in trans_hfs:
                    if slices_overlap(low_flight, trans_hf.slice):
                        low_flight = slice(low_flight.start, trans_hf.slice.start)

            hovers.extend([low_flight])

        # Exclude transition periods and trivial periods of operation.
        self.create_phases(filter_slices_duration(hovers, HOVER_MIN_DURATION, frequency=alt_agl.frequency))
Пример #8
0
    def derive(self,
               pitch=P('Pitch'),
               climbs=S('Initial Climb'),
               offshore=S('Offshore')):

        for climb_offshore in slices_and(climbs.get_slices(),
                                         offshore.get_slices()):
            climb_offshore = slices_int(climb_offshore)
            masked_pitch = mask_outside_slices(pitch.array, [climb_offshore])

            pitch_index = np.ma.argmax(
                masked_pitch <= -10) or np.ma.argmin(masked_pitch)

            scaling_factor = abs(masked_pitch[pitch_index]) / 10

            window_threshold = -10.00 * scaling_factor
            min_window_threshold = -8.00 * scaling_factor
            window_size = 32
            window_threshold_step = 0.050 * scaling_factor

            diffs = np.ma.ediff1d(
                masked_pitch[climb_offshore.start:pitch_index])
            diffs_exist = diffs.data.size >= 2

            big_diff_index = -1

            while diffs_exist:
                sig_pitch_threshold = window_threshold / window_size

                for i, d in enumerate(diffs):
                    # Look for the first big negative pitch spike
                    if diffs[slices_int(
                            i, i + window_size)].sum() < window_threshold:

                        # Find the first significant negative value within the
                        # spike and make that the starting point of the phase
                        big_diff_index = np.ma.argmax(
                            diffs[i:i + window_size] < sig_pitch_threshold) + i
                        break

                # Bail on match or total failure
                if big_diff_index != -1 or window_size < 2:
                    break

                # Shrink window size instead of looking for insignificant
                # spikes and scale window/pitch thresholds accordingly
                if window_threshold >= min_window_threshold:
                    window_size //= 2
                    min_window_threshold /= 2
                    window_threshold /= 2
                    window_threshold_step /= 2
                    sig_pitch_threshold *= 2
                else:
                    window_threshold += window_threshold_step

            if big_diff_index != -1:
                self.create_section(
                    slice(climb_offshore.start + big_diff_index, pitch_index))

            # Worst case fallback, this should happen extremely rarely
            # and would trigger all events related to this phase
            else:
                self.create_section(
                    slice(climb_offshore.start, climb_offshore.stop))
    def derive(self,
               gl=M('Gear (L) On Ground'),
               gr=M('Gear (R) On Ground'),
               vert_spd=P('Vertical Speed'),
               torque=P('Eng (*) Torque Avg'),
               ac_series=A('Series'),
               collective=P('Collective')):

        if gl and gr:
            delta = abs((gl.offset - gr.offset) * gl.frequency)
            if 0.75 < delta or delta < 0.25:
                # If the samples of the left and right gear are close together,
                # the best representation is to map them onto a single
                # parameter in which we accept that either wheel on the ground
                # equates to gear on ground.
                self.array = np.ma.logical_or(gl.array, gr.array)
                self.frequency = gl.frequency
                self.offset = gl.offset
                return
            else:
                # If the paramters are not co-located, then
                # merge_two_parameters creates the best combination possible.
                self.array, self.frequency, self.offset = merge_two_parameters(
                    gl, gr)
                return
        elif gl or gr:
            gear = gl or gr
            self.array = gear.array
            self.frequency = gear.frequency
            self.offset = gear.offset
        elif vert_spd and torque:
            vert_spd_limit = 100.0
            torque_limit = 30.0
            if ac_series and ac_series.value == 'Columbia 234':
                vert_spd_limit = 125.0
                torque_limit = 22.0
                collective_limit = 15.0

                vert_spd_array = align(
                    vert_spd,
                    torque) if vert_spd.hz != torque.hz else vert_spd.array
                collective_array = align(
                    collective,
                    torque) if collective.hz != torque.hz else collective.array

                vert_spd_array = moving_average(vert_spd_array)
                torque_array = moving_average(torque.array)
                collective_array = moving_average(collective_array)

                roo_vs_array = runs_of_ones(
                    abs(vert_spd_array) < vert_spd_limit, min_samples=1)
                roo_torque_array = runs_of_ones(torque_array < torque_limit,
                                                min_samples=1)
                roo_collective_array = runs_of_ones(
                    collective_array < collective_limit, min_samples=1)

                vs_and_torque = slices_and(roo_vs_array, roo_torque_array)
                grounded = slices_and(vs_and_torque, roo_collective_array)

                array = np_ma_zeros_like(vert_spd_array)
                for _slice in slices_remove_small_slices(grounded, count=2):
                    array[_slice] = 1
                array.mask = vert_spd_array.mask | torque_array.mask
                array.mask = array.mask | collective_array.mask
                self.array = nearest_neighbour_mask_repair(array)
                self.frequency = torque.frequency
                self.offset = torque.offset

            else:
                vert_spd_array = align(
                    vert_spd,
                    torque) if vert_spd.hz != torque.hz else vert_spd.array
                # Introducted for S76 and Bell 212 which do not have Gear On Ground available

                vert_spd_array = moving_average(vert_spd_array)
                torque_array = moving_average(torque.array)

                grounded = slices_and(
                    runs_of_ones(abs(vert_spd_array) < vert_spd_limit,
                                 min_samples=1),
                    runs_of_ones(torque_array < torque_limit, min_samples=1))

                array = np_ma_zeros_like(vert_spd_array)
                for _slice in slices_remove_small_slices(grounded, count=2):
                    array[_slice] = 1
                array.mask = vert_spd_array.mask | torque_array.mask
                self.array = nearest_neighbour_mask_repair(array)
                self.frequency = torque.frequency
                self.offset = torque.offset

        else:
            # should not get here if can_operate is correct
            raise NotImplementedError()
    def derive(self,
               gl=M('Gear (L) On Ground'),
               gr=M('Gear (R) On Ground'),
               vert_spd=P('Vertical Speed'),
               torque=P('Eng (*) Torque Avg'),
               ac_series=A('Series'),
               collective=P('Collective')):

        if gl and gr:
            delta = abs((gl.offset - gr.offset) * gl.frequency)
            if 0.75 < delta or delta < 0.25:
                # If the samples of the left and right gear are close together,
                # the best representation is to map them onto a single
                # parameter in which we accept that either wheel on the ground
                # equates to gear on ground.
                self.array = np.ma.logical_or(gl.array, gr.array)
                self.frequency = gl.frequency
                self.offset = gl.offset
                return
            else:
                # If the paramters are not co-located, then
                # merge_two_parameters creates the best combination possible.
                self.array, self.frequency, self.offset = merge_two_parameters(gl, gr)
                return
        elif gl or gr:
            gear = gl or gr
            self.array = gear.array
            self.frequency = gear.frequency
            self.offset = gear.offset
        elif vert_spd and torque:
            vert_spd_limit = 100.0
            torque_limit = 30.0
            if ac_series and ac_series.value == 'Columbia 234':
                vert_spd_limit = 125.0
                torque_limit = 22.0
                collective_limit = 15.0

                vert_spd_array = align(vert_spd, torque) if vert_spd.hz != torque.hz else vert_spd.array
                collective_array = align(collective, torque) if collective.hz != torque.hz else collective.array

                vert_spd_array = moving_average(vert_spd_array)
                torque_array = moving_average(torque.array)
                collective_array = moving_average(collective_array)

                roo_vs_array = runs_of_ones(abs(vert_spd_array) < vert_spd_limit, min_samples=1)
                roo_torque_array = runs_of_ones(torque_array < torque_limit, min_samples=1)
                roo_collective_array = runs_of_ones(collective_array < collective_limit, min_samples=1)

                vs_and_torque = slices_and(roo_vs_array, roo_torque_array)
                grounded = slices_and(vs_and_torque, roo_collective_array)

                array = np_ma_zeros_like(vert_spd_array)
                for _slice in slices_remove_small_slices(grounded, count=2):
                    array[_slice] = 1
                array.mask = vert_spd_array.mask | torque_array.mask
                array.mask = array.mask | collective_array.mask
                self.array = nearest_neighbour_mask_repair(array)
                self.frequency = torque.frequency
                self.offset = torque.offset

            else:
                vert_spd_array = align(vert_spd, torque) if vert_spd.hz != torque.hz else vert_spd.array
                # Introducted for S76 and Bell 212 which do not have Gear On Ground available

                vert_spd_array = moving_average(vert_spd_array)
                torque_array = moving_average(torque.array)

                grounded = slices_and(runs_of_ones(abs(vert_spd_array) < vert_spd_limit, min_samples=1),
                                      runs_of_ones(torque_array < torque_limit, min_samples=1))

                array = np_ma_zeros_like(vert_spd_array)
                for _slice in slices_remove_small_slices(grounded, count=2):
                    array[_slice] = 1
                array.mask = vert_spd_array.mask | torque_array.mask
                self.array = nearest_neighbour_mask_repair(array)
                self.frequency = torque.frequency
                self.offset = torque.offset

        else:
            # should not get here if can_operate is correct
            raise NotImplementedError()
    def derive(self,
               vert_spd=P('Vertical Speed'),
               torque=P('Eng (*) Torque Avg'),
               ac_series=A('Series'),
               collective=P('Collective')):

        vert_spd_limit = 100.0
        torque_limit = 30.0
        if ac_series and ac_series.value == 'Columbia 234':
            vert_spd_limit = 125.0
            torque_limit = 22.0
            collective_limit = 15.0

            vert_spd_array = align(
                vert_spd,
                torque) if vert_spd.hz != torque.hz else vert_spd.array
            collective_array = align(
                collective,
                torque) if collective.hz != torque.hz else collective.array

            vert_spd_array = moving_average(vert_spd_array)
            torque_array = moving_average(torque.array)
            collective_array = moving_average(collective_array)

            roo_vs_array = runs_of_ones(abs(vert_spd_array) < vert_spd_limit,
                                        min_samples=1)
            roo_torque_array = runs_of_ones(torque_array < torque_limit,
                                            min_samples=1)
            roo_collective_array = runs_of_ones(
                collective_array < collective_limit, min_samples=1)

            vs_and_torque = slices_and(roo_vs_array, roo_torque_array)
            grounded = slices_and(vs_and_torque, roo_collective_array)

            array = np_ma_zeros_like(vert_spd_array)
            for _slice in slices_remove_small_slices(grounded, count=2):
                array[_slice] = 1
            array.mask = vert_spd_array.mask | torque_array.mask
            array.mask = array.mask | collective_array.mask
            self.array = nearest_neighbour_mask_repair(array)
            self.frequency = torque.frequency
            self.offset = torque.offset

        else:
            vert_spd_array = align(
                vert_spd,
                torque) if vert_spd.hz != torque.hz else vert_spd.array
            # Introducted for S76 and Bell 212 which do not have Gear On Ground available

            vert_spd_array = moving_average(vert_spd_array)
            torque_array = moving_average(torque.array)

            grounded = slices_and(
                runs_of_ones(abs(vert_spd_array) < vert_spd_limit,
                             min_samples=1),
                runs_of_ones(torque_array < torque_limit, min_samples=1))

            array = np_ma_zeros_like(vert_spd_array)
            for _slice in slices_remove_small_slices(grounded, count=2):
                array[_slice] = 1
            array.mask = vert_spd_array.mask | torque_array.mask
            self.array = nearest_neighbour_mask_repair(array)
            self.frequency = torque.frequency
            self.offset = torque.offset