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)))
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)))
def derive(self, ra=M('TCAS RA'), off=KTI('Liftoff'), td=KTI('Touchdown') ): ras_local = ra.array ras_slices = library.runs_of_ones(ras_local) # put together runs separated by short drop-outs ras_slicesb = library.slices_remove_small_gaps(ras_slices, time_limit=2, hz=1) for ra_slice in ras_slicesb: is_post_liftoff = (ra_slice.start - off.get_first().index) > 10 is_pre_touchdown = (td.get_first().index - ra_slice.start ) > 10 duration = ra_slice.stop-ra_slice.start if is_post_liftoff and is_pre_touchdown and 3.0 <= duration < 300.0: #ignore if too short to do anything #print ' ra section', ra_slice self.create_phase( ra_slice ) return
def derive(self, ra=M('TCAS RA'), off=KTI('Liftoff'), td=KTI('Touchdown') ): ras_local = ra.array ras_slices = library.runs_of_ones(ras_local) # put together runs separated by short drop-outs ras_slicesb = library.slices_remove_small_gaps(ras_slices, time_limit=2, hz=1) for ra_slice in ras_slicesb: is_post_liftoff = (ra_slice.start - off.get_first().index) > 10 is_pre_touchdown = (td.get_first().index - ra_slice.start ) > 10 duration = ra_slice.stop-ra_slice.start if is_post_liftoff and is_pre_touchdown and 3.0 <= duration < 300.0: #ignore if too short to do anything #print ' ra section', ra_slice self.create_phase( ra_slice ) return
def derive(self, gl=M('Gear (L) Up'), gn=M('Gear (N) Up'), gr=M('Gear (R) Up'), gc=M('Gear (C) Up')): # Join all available gear parameters and use whichever are available. self.array = vstack_params_where_state( (gl, 'Up'), (gn, 'Up'), (gr, 'Up'), (gc, 'Up'), ).all(axis=0) # remove any spikes _slices = runs_of_ones(self.array == 'Down') _slices = slices_remove_small_gaps(_slices, 2, self.hz) for _slice in _slices: self.array[_slice] = 'Down'
def _split_on_eng_params(slice_start_secs, slice_stop_secs, split_params_min, split_params_frequency): ''' Find split using engine parameters. :param slice_start_secs: Start of slow slice in seconds. :type slice_start_secs: int or float :param slice_stop_secs: Stop of slow slice in seconds. :type slice_stop_secs: int or float :param split_params_min: Minimum of engine parameters. :type split_params_min: np.ma.MaskedArray :param split_params_frequency: Frequency of split_params_min. :type split_params_frequency: int or float :returns: Split index in seconds and value of split_params_min at this index. :rtype: (int or float, int or float) ''' slice_start = slice_start_secs * split_params_frequency slice_stop = slice_stop_secs * split_params_frequency split_params_slice = slice(np.round(slice_start, 0), np.round(slice_stop, 0)) split_index, split_value = min_value(split_params_min, _slice=split_params_slice) if split_index is None: return split_index, split_value eng_min_slices = slices_remove_small_slices(slices_remove_small_gaps( runs_of_ones(split_params_min[split_params_slice] == split_value), time_limit=60, hz=split_params_frequency), hz=split_params_frequency) if not eng_min_slices: return split_index, split_value split_index = eng_min_slices[0].start + \ ((eng_min_slices[0].stop - eng_min_slices[0].start) / 2) + slice_start split_index = round(split_index / split_params_frequency) return split_index, split_value
def _segment_type_and_slice(speed_array, speed_frequency, heading_array, heading_frequency, start, stop, eng_arrays, aircraft_info, thresholds, hdf): """ Uses the Heading to determine whether the aircraft moved about at all and the airspeed to determine if it was a full or partial flight. NO_MOVEMENT: When the aircraft is in the hanger, the altitude and airspeed can be tested and record values which look like the aircraft is in flight; however the aircraft is rarely moved and the heading sensor is a good indication that this is a hanger test. GROUND_ONLY: If the heading changed, the airspeed needs to have been above the threshold speed for flight for a minimum amount of time, currently 3 minutes to determine. If not, this segment is identified as GROUND_ONLY, probably taxiing, repositioning on the ground or a rejected takeoff. START_ONLY: If the airspeed started slow but ended fast, we had a partial segment for the start of a flight. STOP_ONLY: If the airspeed started fast but ended slow, we had a partial segment for the end of a flight. MID_FLIGHT: The airspeed started and ended fast - no takeoff or landing! START_AND_STOP: The airspeed started and ended slow, implying a complete flight. segment_type is one of: * 'NO_MOVEMENT' (didn't change heading) * 'GROUND_ONLY' (didn't go fast) * 'START_AND_STOP' * 'START_ONLY' * 'STOP_ONLY' * 'MID_FLIGHT' """ speed_start = start * speed_frequency speed_stop = stop * speed_frequency speed_array = speed_array[speed_start:speed_stop] heading_start = start * heading_frequency heading_stop = stop * heading_frequency heading_array = heading_array[heading_start:heading_stop] # remove small gaps between valid data, e.g. brief data spikes unmasked_slices = slices_remove_small_gaps( np.ma.clump_unmasked(speed_array), 2, speed_frequency) # remove small slices to find 'consistent' valid data unmasked_slices = slices_remove_small_slices(unmasked_slices, 40, speed_frequency) if unmasked_slices: # Check speed slow_start = speed_array[ unmasked_slices[0].start] < thresholds['speed_threshold'] slow_stop = speed_array[unmasked_slices[-1].stop - 1] < thresholds['speed_threshold'] threshold_exceedance = np.ma.sum( speed_array > thresholds['speed_threshold']) / speed_frequency fast_for_long = threshold_exceedance > thresholds['min_duration'] else: slow_start = slow_stop = fast_for_long = None # Find out if the aircraft moved if aircraft_info and aircraft_info['Aircraft Type'] == 'helicopter': # if any gear params use them gog = next( iter([ hdf.get(name) for name in ('Gear On Ground', 'Gear (R) On Ground', 'Gear (L) On Ground') ])) if gog: gog_start_idx = start * gog.frequency gog_stop_idx = stop * gog.frequency gog_samples = 120 * gog.frequency gog_start = closest_unmasked_value(gog.array, gog_start_idx, gog_start_idx - gog_samples, gog_start_idx + gog_samples) gog_stop = closest_unmasked_value(gog.array, gog_stop_idx, gog_stop_idx - gog_samples, gog_stop_idx + gog_samples) if gog_start is not None and gog_stop is not None: # Use Gear on Ground rather than rotor speed as rotors may be # 90+% at beginning or end of segment. slow_start = (gog_start.value == 'Ground') slow_stop = (gog_stop.value == 'Ground') temp = np.ma.array(gog.array[gog_start_idx:gog_stop_idx].data, mask=gog.array[gog_start_idx:gog_stop_idx].mask) gog_test = np.ma.masked_less(temp, 1.0) # We have seeen 12-second spurious gog='Air' signals during rotor rundown. Hence increased limit. did_move = slices_remove_small_slices(np.ma.clump_masked(gog_test), time_limit=30, hz=gog.frequency) else: hdiff = np.ma.abs(np.ma.diff(heading_array)).sum() did_move = hdiff > settings.HEADING_CHANGE_TAXI_THRESHOLD else: # Check Heading change for fixed wing. if eng_arrays is not None: heading_array = np.ma.masked_where( eng_arrays[heading_start:heading_stop] < settings.MIN_FAN_RUNNING, heading_array) hdiff = np.ma.abs(np.ma.diff(heading_array)).sum() did_move = hdiff > settings.HEADING_CHANGE_TAXI_THRESHOLD if not did_move or (not fast_for_long and eng_arrays is None): # added check for not fast for long and no engine params to avoid # lots of Herc ground runs logger.debug("Aircraft did not move.") segment_type = 'NO_MOVEMENT' # e.g. hanger tests, esp. if speed changes! elif slow_start and slow_stop and fast_for_long: logger.debug( "speed started below threshold, rose above and stopped below.") segment_type = 'START_AND_STOP' elif slow_start and threshold_exceedance: logger.debug("speed started below threshold and stopped above.") segment_type = 'START_ONLY' elif slow_stop and threshold_exceedance: logger.debug("speed started above threshold and stopped below.") segment_type = 'STOP_ONLY' elif not fast_for_long: logger.debug("speed was below threshold.") segment_type = 'GROUND_ONLY' # e.g. RTO, re-positioning A/C #Q: report a go_fast? else: logger.debug("speed started and stopped above threshold.") segment_type = 'MID_FLIGHT' logger.info("Segment type is '%s' between '%s' and '%s'.", segment_type, start, stop) # ARINC 717 data has frames or superframes. ARINC 767 will be split # on a minimum boundary of 4 seconds for the analyser. boundary = 64 if hdf.superframe_present else 4 segment = slice(start, stop) supf_start_secs, supf_stop_secs, array_start_secs, array_stop_secs = segment_boundaries( segment, boundary) start_padding = segment.start - supf_start_secs return segment_type, segment, array_start_secs
def _segment_type_and_slice(speed_array, speed_frequency, heading_array, heading_frequency, start, stop, eng_arrays, aircraft_info, thresholds, hdf): """ Uses the Heading to determine whether the aircraft moved about at all and the airspeed to determine if it was a full or partial flight. NO_MOVEMENT: When the aircraft is in the hanger, the altitude and airspeed can be tested and record values which look like the aircraft is in flight; however the aircraft is rarely moved and the heading sensor is a good indication that this is a hanger test. GROUND_ONLY: If the heading changed, the airspeed needs to have been above the threshold speed for flight for a minimum amount of time, currently 3 minutes to determine. If not, this segment is identified as GROUND_ONLY, probably taxiing, repositioning on the ground or a rejected takeoff. START_ONLY: If the airspeed started slow but ended fast, we had a partial segment for the start of a flight. STOP_ONLY: If the airspeed started fast but ended slow, we had a partial segment for the end of a flight. MID_FLIGHT: The airspeed started and ended fast - no takeoff or landing! START_AND_STOP: The airspeed started and ended slow, implying a complete flight. segment_type is one of: * 'NO_MOVEMENT' (didn't change heading) * 'GROUND_ONLY' (didn't go fast) * 'START_AND_STOP' * 'START_ONLY' * 'STOP_ONLY' * 'MID_FLIGHT' """ speed_start = start * speed_frequency speed_stop = stop * speed_frequency speed_array = speed_array[speed_start:speed_stop] heading_start = start * heading_frequency heading_stop = stop * heading_frequency heading_array = heading_array[heading_start:heading_stop] # remove small gaps between valid data, e.g. brief data spikes unmasked_slices = slices_remove_small_gaps( np.ma.clump_unmasked(speed_array), 2, speed_frequency) # remove small slices to find 'consistent' valid data unmasked_slices = slices_remove_small_slices( unmasked_slices, 25, speed_frequency) if unmasked_slices: # Check speed slow_start = speed_array[unmasked_slices[0].start] < thresholds['speed_threshold'] slow_stop = speed_array[unmasked_slices[-1].stop - 1] < thresholds['speed_threshold'] threshold_exceedance = np.ma.sum( speed_array > thresholds['speed_threshold']) / speed_frequency fast_for_long = threshold_exceedance > thresholds['min_duration'] else: slow_start = slow_stop = fast_for_long = None # Find out if the aircraft moved if aircraft_info and aircraft_info['Aircraft Type'] == 'helicopter': # if any gear params use them gog = next(iter([hdf.get(name) for name in ('Gear On Ground', 'Gear (R) On Ground', 'Gear (L) On Ground')])) if gog: gog_start = start * gog.frequency gog_stop = stop * gog.frequency temp = np.ma.array(gog.array[gog_start:gog_stop].data, mask=gog.array[gog_start:gog_stop].mask) gog_test = np.ma.masked_less(temp, 1.0) # We have seeen 12-second spurious gog='Air' signals during rotor rundown. Hence increased limit. did_move = slices_remove_small_slices(np.ma.clump_masked(gog_test), time_limit=30, hz=gog.frequency) else: hdiff = np.ma.abs(np.ma.diff(heading_array)).sum() did_move = hdiff > settings.HEADING_CHANGE_TAXI_THRESHOLD else: # Check Heading change for fixed wing. if eng_arrays is not None: heading_array = np.ma.masked_where(eng_arrays[heading_start:heading_stop] < settings.MIN_FAN_RUNNING, heading_array) hdiff = np.ma.abs(np.ma.diff(heading_array)).sum() did_move = hdiff > settings.HEADING_CHANGE_TAXI_THRESHOLD if not did_move or (not fast_for_long and eng_arrays is None): # added check for not fast for long and no engine params to avoid # lots of Herc ground runs logger.debug("Aircraft did not move.") segment_type = 'NO_MOVEMENT' # e.g. hanger tests, esp. if speed changes! elif not fast_for_long: logger.debug("speed was below threshold.") segment_type = 'GROUND_ONLY' # e.g. RTO, re-positioning A/C #Q: report a go_fast? elif slow_start and slow_stop: logger.debug( "speed started below threshold, rose above and stopped below.") segment_type = 'START_AND_STOP' elif slow_start: logger.debug("speed started below threshold and stopped above.") segment_type = 'START_ONLY' elif slow_stop: logger.debug("speed started above threshold and stopped below.") segment_type = 'STOP_ONLY' else: logger.debug("speed started and stopped above threshold.") segment_type = 'MID_FLIGHT' logger.info("Segment type is '%s' between '%s' and '%s'.", segment_type, start, stop) # ARINC 717 data has frames or superframes. ARINC 767 will be split # on a minimum boundary of 4 seconds for the analyser. boundary = 64 if hdf.superframe_present else 4 segment = slice(start, stop) supf_start_secs, supf_stop_secs, array_start_secs, array_stop_secs = segment_boundaries(segment, boundary) start_padding = segment.start - supf_start_secs return segment_type, segment, array_start_secs