def estimate(iq_array: IQArray, noise: float = None, modulation: str = None) -> dict: if isinstance(iq_array, np.ndarray): iq_array = IQArray(iq_array) magnitudes = iq_array.magnitudes # find noise threshold noise = detect_noise_level(magnitudes) if noise is None else noise # segment messages message_indices = segment_messages_from_magnitudes(magnitudes, noise_threshold=noise) # detect modulation modulation = detect_modulation_for_messages( iq_array, message_indices) if modulation is None else modulation if modulation is None: return None if modulation == "OOK": message_indices = merge_message_segments_for_ook(message_indices) if modulation == "OOK" or modulation == "ASK": data = signal_functions.afp_demod(iq_array.data, noise, 0) elif modulation == "FSK": data = signal_functions.afp_demod(iq_array.data, noise, 1) elif modulation == "PSK": data = signal_functions.afp_demod(iq_array.data, noise, 2) else: raise ValueError("Unsupported Modulation") centers = [] bit_lengths = [] tolerances = [] for start, end in message_indices: msg_rect_data = data[start:end] center = detect_center(msg_rect_data) if center is None: continue plateau_lengths = c_auto_interpretation.get_plateau_lengths( msg_rect_data, center, percentage=25) tolerance = estimate_tolerance_from_plateau_lengths(plateau_lengths) if tolerance is None: tolerance = 0 else: tolerances.append(tolerance) merged_lengths = merge_plateau_lengths(plateau_lengths, tolerance=tolerance) if len(merged_lengths) < 2: continue bit_length = get_bit_length_from_plateau_lengths(merged_lengths) min_bit_length = tolerance + 1 if bit_length > min_bit_length: # only add to score if found bit length surpasses minimum bit length centers.append(center) bit_lengths.append(bit_length) # Since we cannot have different centers per message (yet) we need to combine them to return a common center if modulation == "OOK" or modulation == "ASK": # for ask modulations the center tends to be the minimum of all found centers center = min_without_outliers(np.array(centers), z=2) if center is None: # did not find any centers at all so we cannot return a valid estimation return None elif len(centers) > 0: # for other modulations it is a better strategy to take the mean of found centers center = np.mean(centers) else: # did not find any centers at all so we cannot return a valid estimation return None bit_length = get_most_frequent_value(bit_lengths) if bit_length is None: return None try: tolerance = np.percentile(tolerances, 50) except IndexError: # no tolerances found, default to 5% of bit length tolerance = max(1, int(0.05 * bit_length)) result = { "modulation_type": "ASK" if modulation == "OOK" else modulation, "bit_length": bit_length, "center": center, "tolerance": int(tolerance), "noise": noise } return result
def estimate(signal: np.ndarray, noise: float = None, modulation: str = None) -> dict: magnitudes = np.abs(signal) # find noise threshold noise = detect_noise_level(magnitudes) if noise is None else noise # segment messages message_indices = segment_messages_from_magnitudes(magnitudes, noise_threshold=noise) # detect modulation modulation = detect_modulation_for_messages(signal, message_indices) if modulation is None else modulation if modulation is None: return None if modulation == "OOK": message_indices = merge_message_segments_for_ook(message_indices) if modulation == "OOK" or modulation == "ASK": data = signal_functions.afp_demod(signal, noise, 0) elif modulation == "FSK": data = signal_functions.afp_demod(signal, noise, 1) elif modulation == "PSK": data = signal_functions.afp_demod(signal, noise, 2) else: raise ValueError("Unsupported Modulation") centers = [] bit_lengths = [] tolerances = [] for start, end in message_indices: msg_rect_data = data[start:end] center = detect_center(msg_rect_data) if center is None: continue plateau_lengths = c_auto_interpretation.get_plateau_lengths(msg_rect_data, center, percentage=25) tolerance = estimate_tolerance_from_plateau_lengths(plateau_lengths) if tolerance is None: tolerance = 0 else: tolerances.append(tolerance) merged_lengths = merge_plateau_lengths(plateau_lengths, tolerance=tolerance) if len(merged_lengths) < 2: continue bit_length = get_bit_length_from_plateau_lengths(merged_lengths) min_bit_length = tolerance + 1 if bit_length > min_bit_length: # only add to score if found bit length surpasses minimum bit length centers.append(center) bit_lengths.append(bit_length) # Since we cannot have different centers per message (yet) we need to combine them to return a common center if modulation == "OOK" or modulation == "ASK": # for ask modulations the center tends to be the minimum of all found centers center = min_without_outliers(np.array(centers), z=2) if center is None: # did not find any centers at all so we cannot return a valid estimation return None elif len(centers) > 0: # for other modulations it is a better strategy to take the mean of found centers center = np.mean(centers) else: # did not find any centers at all so we cannot return a valid estimation return None bit_length = get_most_frequent_value(bit_lengths) if bit_length is None: return None try: tolerance = np.percentile(tolerances, 50) except IndexError: # no tolerances found, default to 5% of bit length tolerance = max(1, int(0.05 * bit_length)) result = { "modulation_type": "ASK" if modulation == "OOK" else modulation, "bit_length": bit_length, "center": center, "tolerance": int(tolerance), "noise": noise } return result