Files
Artemis/src/filters.py
Alessandro 8e79bf6adf Fix a bug in the *.spec files and also apply the following major changes:
- Add support for adding the base folder to PATH.
- Only display one pop up window at a time in order to avoid confusion.
- Add automatic updates feature:
	- Windows and Linux versions will be shipped with an updater program used to
	  update both Artemis and the updater itself.
	- MacOs versions will not have the updater. Instead the user will be asked
	  to download the new software version (if present) via browser.
2019-10-15 19:14:29 +02:00

848 lines
34 KiB
Python

"""This module contains all the filter-related classes and functions.
The only class exposed is Filters which provides the following methods:
- ok(signal_name): to check if all the filters are passed;
- reset(): to reset all the applied filters;
- refresh(): used when the theme is changed."""
from functools import partial
import webbrowser
from PyQt5.QtWidgets import QListWidgetItem, QTreeWidgetItem
from PyQt5.QtCore import pyqtSlot, QObject
from constants import (
Constants,
Ftype,
Signal,
)
from utilities import (
uncheck_and_emit,
connect_events_to_func,
filters_limit,
is_undef_freq,
is_undef_band,
safe_cast,
show_matching_strings,
get_field_entries,
)
class _BaseFilter(QObject):
"""Base class for all filters."""
def __init__(self, owner):
"""Positional argument:
owner - the object containing the filter screen."""
super().__init__()
self._owner = owner
def refresh(self):
"""Refresh the screen."""
pass
class _FreqBandMixIn:
"""Mixin class for the frequency and band filters.
Provides some functions used in both classes."""
@pyqtSlot()
def _set_min_value_upper_limit(self, lower_combo_box,
lower_spin_box,
upper_combo_box,
upper_spin_box):
"""Forbid to a lower limit to be greater than the corresponding upper one.
Used for frequency and bandwidth screens."""
if lower_spin_box.isEnabled():
unit_conversion = {
'Hz': ['kHz', 'MHz', 'GHz'],
'kHz': ['MHz', 'GHz'],
'MHz': ['GHz']
}
lower_units = lower_combo_box.currentText()
upper_units = upper_combo_box.currentText()
lower_value = lower_spin_box.value()
inf_limit = (lower_value * Constants.CONVERSION_FACTORS[lower_units]) \
// Constants.CONVERSION_FACTORS[upper_units]
counter = 0
while inf_limit > upper_spin_box.maximum():
counter += 1
inf_limit //= 1000
if upper_spin_box.minimum() != inf_limit:
upper_spin_box.setMinimum(inf_limit)
if counter > 0:
new_unit = unit_conversion[upper_units][counter - 1]
upper_combo_box.disconnect()
upper_combo_box.setCurrentText(new_unit)
upper_combo_box.currentTextChanged.connect(
partial(
self._set_min_value_upper_limit,
lower_combo_box,
lower_spin_box,
upper_combo_box,
upper_spin_box
)
)
@pyqtSlot()
def _reset_fb_filters(self, ftype):
"""Reset the Frequency or Bandwidth depending on 'ftype'.
ftype can be either Ftype.FREQ or Ftype.BAND.
"""
if ftype != Ftype.FREQ and ftype != Ftype.BAND:
raise ValueError("Wrong ftype in function '_reset_fb_filters'")
apply_remove_btn = getattr(self._owner, 'apply_remove_' + ftype + '_filter_btn')
include_undef_btn = getattr(self._owner, 'include_undef_' + ftype + 's')
activate_low = getattr(self._owner, 'activate_low_' + ftype + '_filter_btn')
activate_up = getattr(self._owner, 'activate_up_' + ftype + '_filter_btn')
lower_unit = getattr(self._owner, 'lower_' + ftype + '_filter_unit')
upper_unit = getattr(self._owner, 'upper_' + ftype + '_filter_unit')
lower_spinbox = getattr(self._owner, 'lower_' + ftype + '_spinbox')
upper_spinbox = getattr(self._owner, 'upper_' + ftype + '_spinbox')
lower_confidence = getattr(self._owner, 'lower_' + ftype + '_confidence')
upper_confidence = getattr(self._owner, 'lower_' + ftype + '_confidence')
default_val = 1 if ftype == Ftype.FREQ else 5000
if ftype == Ftype.FREQ:
for f in self._frequency_filters_btns:
if f.isChecked():
f.setChecked(False)
uncheck_and_emit(apply_remove_btn)
if include_undef_btn.isChecked():
include_undef_btn.setChecked(False)
uncheck_and_emit(activate_low)
uncheck_and_emit(activate_up)
lower_unit.setCurrentText("MHz")
upper_unit.setCurrentText("MHz")
lower_spinbox.setValue(default_val)
upper_spinbox.setMinimum(1)
upper_spinbox.setValue(default_val)
lower_confidence.setValue(0)
upper_confidence.setValue(0)
@pyqtSlot()
def _set_band_filter_label(self,
activate_low_btn,
lower_spinbox,
lower_unit,
lower_confidence,
activate_up_btn,
upper_spinbox,
upper_unit,
upper_confidence,
range_lbl):
"""Display the actual range applied for the signal's property search.
Used for frequency and bandwidth screens."""
activate_low = False
activate_high = False
color = self._owner.inactive_color
title = ''
to_display = ''
if activate_low_btn.isChecked():
activate_low = True
color = self._owner.active_color
min_value = lower_spinbox.value()
if lower_confidence.value() != 0:
min_value -= lower_spinbox.value() * lower_confidence.value() / 100
to_display += str(round(min_value, Constants.MAX_DIGITS)) \
+ ' ' + lower_unit.currentText()
else:
to_display += 'DC'
to_display += Constants.RANGE_SEPARATOR
if activate_up_btn.isChecked():
max_value = upper_spinbox.value()
activate_high = True
color = self._owner.active_color
if upper_confidence.value() != 0:
max_value += upper_spinbox.value() * upper_confidence.value() / 100
to_display += str(round(max_value, Constants.MAX_DIGITS)) + ' ' \
+ upper_unit.currentText()
else:
to_display += 'INF'
if activate_low and activate_high:
title = 'Band-pass\n\n'
elif activate_low and not activate_high:
title = 'Low-pass\n\n'
elif not activate_low and activate_high:
title = 'High-pass\n\n'
else:
title = "Selected range:\n\n"
to_display = "Inactive"
to_display = title + to_display
range_lbl.setText(to_display)
range_lbl.setStyleSheet(f'color: {color};')
class FreqFilter(_BaseFilter, _FreqBandMixIn):
"""Frequency filter class."""
def __init__(self, owner):
super().__init__(owner)
self.apply_remove_btn = self._owner.apply_remove_freq_filter_btn
self.reset_btn = self._owner.reset_frequency_filters_btn
self._frequency_filters_btns = (
self._owner.elf_filter_btn,
self._owner.slf_filter_btn,
self._owner.ulf_filter_btn,
self._owner.vlf_filter_btn,
self._owner.lf_filter_btn,
self._owner.mf_filter_btn,
self._owner.hf_filter_btn,
self._owner.vhf_filter_btn,
self._owner.uhf_filter_btn,
self._owner.shf_filter_btn,
self._owner.ehf_filter_btn,
)
self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_btn.set_slave_filters(
simple_ones=[
*self._frequency_filters_btns,
self._owner.include_undef_freqs,
self._owner.activate_low_freq_filter_btn,
self._owner.activate_up_freq_filter_btn
],
radio_1=self._owner.activate_low_freq_filter_btn,
ruled_by_radio_1=[
self._owner.lower_freq_spinbox,
self._owner.lower_freq_filter_unit,
self._owner.lower_freq_confidence
],
radio_2=self._owner.activate_up_freq_filter_btn,
ruled_by_radio_2=[
self._owner.upper_freq_spinbox,
self._owner.upper_freq_filter_unit,
self._owner.upper_freq_confidence
]
)
connect_events_to_func(
events_to_connect=[self._owner.lower_freq_spinbox.valueChanged,
self._owner.upper_freq_spinbox.valueChanged,
self._owner.lower_freq_filter_unit.currentTextChanged,
self._owner.upper_freq_filter_unit.currentTextChanged,
self._owner.activate_low_freq_filter_btn.toggled],
fun_to_connect=self._set_min_value_upper_limit,
fun_args=[self._owner.lower_freq_filter_unit,
self._owner.lower_freq_spinbox,
self._owner.upper_freq_filter_unit,
self._owner.upper_freq_spinbox]
)
connect_events_to_func(
events_to_connect=[self._owner.lower_freq_spinbox.valueChanged,
self._owner.upper_freq_spinbox.valueChanged,
self._owner.lower_freq_filter_unit.currentTextChanged,
self._owner.upper_freq_filter_unit.currentTextChanged,
self._owner.activate_low_freq_filter_btn.clicked,
self._owner.activate_up_freq_filter_btn.clicked,
self._owner.lower_freq_confidence.valueChanged,
self._owner.upper_freq_confidence.valueChanged],
fun_to_connect=self._set_band_filter_label,
fun_args=[self._owner.activate_low_freq_filter_btn,
self._owner.lower_freq_spinbox,
self._owner.lower_freq_filter_unit,
self._owner.lower_freq_confidence,
self._owner.activate_up_freq_filter_btn,
self._owner.upper_freq_spinbox,
self._owner.upper_freq_filter_unit,
self._owner.upper_freq_confidence,
self._owner.freq_range_lbl]
)
self._owner.activate_low_freq_filter_btn.toggled.connect(
partial(self._owner.activate_if_toggled,
self._owner.activate_low_freq_filter_btn,
self._owner.lower_freq_spinbox,
self._owner.lower_freq_filter_unit,
self._owner.lower_freq_confidence)
)
self._owner.activate_up_freq_filter_btn.toggled.connect(
partial(self._owner.activate_if_toggled,
self._owner.activate_up_freq_filter_btn,
self._owner.upper_freq_spinbox,
self._owner.upper_freq_filter_unit,
self._owner.upper_freq_confidence)
)
@pyqtSlot()
def reset(self):
"""Reset the filter screen."""
self._reset_fb_filters(Ftype.FREQ)
def _ok(self, signal_name):
"""Evalaute if the signal matches the frequency filters."""
if not self.apply_remove_btn.isChecked():
return True
undef_freq = is_undef_freq(self._owner.db.loc[signal_name])
if undef_freq:
if self._owner.include_undef_freqs.isChecked():
return True
else:
return False
signal_freqs = (
safe_cast(self._owner.db.at[signal_name, Signal.INF_FREQ], int),
safe_cast(self._owner.db.at[signal_name, Signal.SUP_FREQ], int)
)
band_filter_ok = False
any_checked = False
for btn, band_limits in zip(self._frequency_filters_btns, Constants.BANDS):
if btn.isChecked():
any_checked = True
if signal_freqs[0] < band_limits.upper and signal_freqs[1] >= band_limits.lower:
band_filter_ok = True
lower_limit_ok = True
upper_limit_ok = True
if self._owner.activate_low_freq_filter_btn.isChecked():
if not signal_freqs[1] >= filters_limit(self._owner.lower_freq_spinbox,
self._owner.lower_freq_filter_unit,
self._owner.lower_freq_confidence, -1):
lower_limit_ok = False
if self._owner.activate_up_freq_filter_btn.isChecked():
if not signal_freqs[0] < filters_limit(self._owner.upper_freq_spinbox,
self._owner.upper_freq_filter_unit,
self._owner.upper_freq_confidence):
upper_limit_ok = False
if any_checked:
return band_filter_ok and lower_limit_ok and upper_limit_ok
else:
return lower_limit_ok and upper_limit_ok
def refresh(self):
"""Extend _BaseFilter.refresh."""
super().refresh()
self._set_band_filter_label(
self._owner.activate_low_band_filter_btn,
self._owner.lower_band_spinbox,
self._owner.lower_band_filter_unit,
self._owner.lower_band_confidence,
self._owner.activate_up_band_filter_btn,
self._owner.upper_band_spinbox,
self._owner.upper_band_filter_unit,
self._owner.upper_band_confidence,
self._owner.band_range_lbl
)
class BandFilter(_BaseFilter, _FreqBandMixIn):
"""Band filter class."""
def __init__(self, owner):
super().__init__(owner)
self.apply_remove_btn = self._owner.apply_remove_band_filter_btn
self.reset_btn = self._owner.reset_band_filters_btn
connect_events_to_func(
events_to_connect=[self._owner.lower_band_spinbox.valueChanged,
self._owner.upper_band_spinbox.valueChanged,
self._owner.lower_band_filter_unit.currentTextChanged,
self._owner.upper_band_filter_unit.currentTextChanged,
self._owner.activate_low_band_filter_btn.toggled],
fun_to_connect=self._set_min_value_upper_limit,
fun_args=[self._owner.lower_band_filter_unit,
self._owner.lower_band_spinbox,
self._owner.upper_band_filter_unit,
self._owner.upper_band_spinbox]
)
connect_events_to_func(
events_to_connect=[self._owner.lower_band_spinbox.valueChanged,
self._owner.upper_band_spinbox.valueChanged,
self._owner.lower_band_filter_unit.currentTextChanged,
self._owner.upper_band_filter_unit.currentTextChanged,
self._owner.activate_low_band_filter_btn.clicked,
self._owner.activate_up_band_filter_btn.clicked,
self._owner.lower_band_confidence.valueChanged,
self._owner.upper_band_confidence.valueChanged],
fun_to_connect=self._set_band_filter_label,
fun_args=[self._owner.activate_low_band_filter_btn,
self._owner.lower_band_spinbox,
self._owner.lower_band_filter_unit,
self._owner.lower_band_confidence,
self._owner.activate_up_band_filter_btn,
self._owner.upper_band_spinbox,
self._owner.upper_band_filter_unit,
self._owner.upper_band_confidence,
self._owner.band_range_lbl]
)
self._owner.activate_low_band_filter_btn.toggled.connect(
partial(self._owner.activate_if_toggled,
self._owner.activate_low_band_filter_btn,
self._owner.lower_band_spinbox,
self._owner.lower_band_filter_unit,
self._owner.lower_band_confidence)
)
self._owner.activate_up_band_filter_btn.toggled.connect(
partial(self._owner.activate_if_toggled,
self._owner.activate_up_band_filter_btn,
self._owner.upper_band_spinbox,
self._owner.upper_band_filter_unit,
self._owner.upper_band_confidence)
)
self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_btn.set_slave_filters(
simple_ones=[
self._owner.include_undef_bands,
self._owner.activate_low_band_filter_btn,
self._owner.activate_up_band_filter_btn
],
radio_1=self._owner.activate_low_band_filter_btn,
ruled_by_radio_1=[
self._owner.lower_band_spinbox,
self._owner.lower_band_filter_unit,
self._owner.lower_band_confidence
],
radio_2=self._owner.activate_up_band_filter_btn,
ruled_by_radio_2=[
self._owner.upper_band_spinbox,
self._owner.upper_band_filter_unit,
self._owner.upper_band_confidence
]
)
@pyqtSlot()
def reset(self):
"""Reset the filter screen."""
self._reset_fb_filters(Ftype.BAND)
def _ok(self, signal_name):
"""Evalaute if the signal matches the band filters."""
if not self.apply_remove_btn.isChecked():
return True
undef_band = is_undef_band(self._owner.db.loc[signal_name])
if undef_band:
if self._owner.include_undef_bands.isChecked():
return True
else:
return False
signal_bands = (
safe_cast(self._owner.db.at[signal_name, Signal.INF_BAND], int),
safe_cast(self._owner.db.at[signal_name, Signal.SUP_BAND], int)
)
lower_limit_ok = True
upper_limit_ok = True
if self._owner.activate_low_band_filter_btn.isChecked():
if not signal_bands[1] >= filters_limit(self._owner.lower_band_spinbox,
self._owner.lower_band_filter_unit,
self._owner.lower_band_confidence, -1):
lower_limit_ok = False
if self._owner.activate_up_band_filter_btn.isChecked():
if not signal_bands[0] < filters_limit(self._owner.upper_band_spinbox,
self._owner.upper_band_filter_unit,
self._owner.upper_band_confidence):
upper_limit_ok = False
return lower_limit_ok and upper_limit_ok
def refresh(self):
"""Extend _BaseFilter.refresh."""
super().refresh()
self._set_band_filter_label(
self._owner.activate_low_freq_filter_btn,
self._owner.lower_freq_spinbox,
self._owner.lower_freq_filter_unit,
self._owner.lower_freq_confidence,
self._owner.activate_up_freq_filter_btn,
self._owner.upper_freq_spinbox,
self._owner.upper_freq_filter_unit,
self._owner.upper_freq_confidence,
self._owner.freq_range_lbl
)
class CatFilter(_BaseFilter):
"""Category filter class."""
def __init__(self, owner):
super().__init__(owner)
self.apply_remove_btn = self._owner.apply_remove_cat_filter_btn
self.reset_btn = self._owner.reset_cat_filters_btn
# Order matters!
self._cat_filter_btns = [
self._owner.military_btn,
self._owner.radar_btn,
self._owner.active_btn,
self._owner.inactive_btn,
self._owner.ham_btn,
self._owner.commercial_btn,
self._owner.aviation_btn,
self._owner.marine_btn,
self._owner.analogue_btn,
self._owner.digital_btn,
self._owner.trunked_btn,
self._owner.utility_btn,
self._owner.sat_btn,
self._owner.navigation_btn,
self._owner.interfering_btn,
self._owner.number_stations_btn,
self._owner.time_signal_btn
]
self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_btn.set_slave_filters(
simple_ones=[
*self._cat_filter_btns,
self._owner.cat_at_least_one,
self._owner.cat_all
]
)
@pyqtSlot()
def reset(self):
"""Reset the category filter screen."""
uncheck_and_emit(self.apply_remove_btn)
for f in self._cat_filter_btns:
if f.isChecked():
f.setChecked(False)
self._owner.cat_at_least_one.setChecked(True)
def _ok(self, signal_name):
"""Evalaute if the signal matches the category filters."""
if not self.apply_remove_btn.isChecked():
return True
cat_code = self._owner.db.at[signal_name, Signal.CATEGORY_CODE]
cat_checked = 0
positive_cases = 0
for index, cat in enumerate(self._cat_filter_btns):
if cat.isChecked():
cat_checked += 1
if cat_code[index] == '1':
positive_cases += 1
if self._owner.cat_at_least_one.isChecked():
return positive_cases > 0
else:
return cat_checked == positive_cases and cat_checked > 0
class ModeFilter(_BaseFilter):
"""Mode filter class."""
def __init__(self, owner):
super().__init__(owner)
self.apply_remove_btn = self._owner.apply_remove_mode_filter_btn
self.reset_btn = self._owner.reset_mode_filters_btn
self._set_mode_tree_widget()
self._owner.mode_tree_widget.itemSelectionChanged.connect(
self._manage_mode_selections
)
self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_btn.set_slave_filters(
simple_ones=[
self._owner.mode_tree_widget,
self._owner.include_unknown_modes_btn
]
)
def _manage_mode_selections(self):
"""Rules the selection of childs items of the 'Mode' QTreeWidget.
If a parent is selected all its children will be selected as well.
"""
selected_items = self._owner.mode_tree_widget.selectedItems()
parents = Constants.MODES.keys()
for parent in parents:
for item in selected_items:
if parent == item.text(0):
for i in range(len(Constants.MODES[parent])):
item.child(i).setSelected(True)
def _set_mode_tree_widget(self):
"""Construct the QTreeWidget for the 'Mode' screen."""
for parent, children in Constants.MODES.items():
iparent = QTreeWidgetItem([parent])
self._owner.mode_tree_widget.addTopLevelItem(iparent)
for child in children:
ichild = QTreeWidgetItem([child])
iparent.addChild(ichild)
self._owner.mode_tree_widget.expandAll()
@pyqtSlot()
def reset(self):
"""Reset the mode filter screen."""
uncheck_and_emit(self.apply_remove_btn)
parents = Constants.MODES.keys()
selected_children = []
for item in self._owner.mode_tree_widget.selectedItems():
if item.text(0) in parents:
item.setSelected(False)
else:
selected_children.append(item)
for children in selected_children:
children.setSelected(False)
if self._owner.include_unknown_modes_btn.isChecked():
self._owner.include_unknown_modes_btn.setChecked(False)
def _ok(self, signal_name):
"""Evalaute if the signal matches the mode filters."""
if not self.apply_remove_btn.isChecked():
return True
signal_mode = self._owner.db.at[signal_name, Signal.MODE]
if signal_mode == Constants.UNKNOWN:
if self._owner.include_unknown_modes_btn.isChecked():
return True
else:
return False
selected_items = [item for item in self._owner.mode_tree_widget.selectedItems()]
selected_items_text = [i.text(0) for i in selected_items]
parents = [
item for item in selected_items_text
if item in Constants.MODES.keys()
]
ok = []
for item in selected_items:
if item.text(0) in parents:
ok.append(item.text(0) in signal_mode)
elif not item.parent().isSelected():
ok.append(item.text(0) == signal_mode)
return any(ok)
class ModulationFilter(_BaseFilter):
"""Modulation filter class."""
def __init__(self, owner):
super().__init__(owner)
self.apply_remove_btn = self._owner.apply_remove_modulation_filter_btn
self.reset_btn = self._owner.reset_modulation_filters_btn
self._owner.search_bar_modulation.textEdited.connect(self._show_matching_modulations)
self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_btn.set_slave_filters(
simple_ones=[
self._owner.search_bar_modulation,
self._owner.modulation_list
]
)
self._owner.modulation_list.itemClicked.connect(self._remove_if_unselected_modulation)
@pyqtSlot(QListWidgetItem)
def _remove_if_unselected_modulation(self, item):
"""If an item is unselected from the modulations list, hide the item."""
if not item.isSelected():
self._show_matching_modulations(self.search_bar_modulation.text())
@pyqtSlot(str)
def _show_matching_modulations(self, text):
"""Show the modulations which matches 'text'.
The match criterion is defined in 'show_matching_strings'."""
show_matching_strings(self._owner.modulation_list, text)
@pyqtSlot()
def reset(self):
"""Reset the modulation filter screen."""
uncheck_and_emit(self.apply_remove_btn)
self._owner.search_bar_modulation.setText('')
show_matching_strings(
self._owner.modulation_list,
self._owner.search_bar_modulation.text()
)
for i in range(self._owner.modulation_list.count()):
if self._owner.modulation_list.item(i).isSelected():
self._owner.modulation_list.item(i).setSelected(False)
def _ok(self, signal_name):
"""Evalaute if the signal matches the modulation filters."""
if not self.apply_remove_btn.isChecked():
return True
signal_modulation = get_field_entries(
self._owner.db.at[signal_name, Signal.MODULATION]
)
for item in self._owner.modulation_list.selectedItems():
if item.text() in signal_modulation:
return True
return False
class LocFilter(_BaseFilter):
"""Location filter class."""
def __init__(self, owner):
super().__init__(owner)
self.apply_remove_btn = self._owner.apply_remove_location_filter_btn
self.reset_btn = self._owner.reset_location_filters_btn
self._owner.search_bar_location.textEdited.connect(
self._show_matching_locations
)
self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_btn.set_slave_filters(
simple_ones=[
self._owner.search_bar_location,
self._owner.locations_list
]
)
self._owner.locations_list.itemClicked.connect(self._remove_if_unselected_location)
@pyqtSlot(str)
def _show_matching_locations(self, text):
"""Show the locations which matches 'text'.
The match criterion is defined in 'show_matching_strings'."""
show_matching_strings(self._owner.locations_list, text)
@pyqtSlot(QListWidgetItem)
def _remove_if_unselected_location(self, item):
"""If an item is unselected from the locations list, hide the item."""
if not item.isSelected():
self._show_matching_locations(self._owner.search_bar_location.text())
@pyqtSlot()
def reset(self):
"""Reset the location filter screen."""
uncheck_and_emit(self.apply_remove_btn)
self._owner.search_bar_location.setText('')
show_matching_strings(
self._owner.locations_list,
self._owner.search_bar_location.text()
)
for i in range(self._owner.locations_list.count()):
if self._owner.locations_list.item(i).isSelected():
self._owner.locations_list.item(i).setSelected(False)
def _ok(self, signal_name):
"""Evalaute if the signal matches the location filters."""
if not self.apply_remove_btn.isChecked():
return True
signal_locations = get_field_entries(
self._owner.db.at[signal_name, Signal.LOCATION]
)
for item in self._owner.locations_list.selectedItems():
if item.text() in signal_locations:
return True
return False
class ACFFilter(_BaseFilter):
"""Autocorrelation function filter class."""
def __init__(self, owner):
super().__init__(owner)
self.apply_remove_btn = self._owner.apply_remove_acf_filter_btn
self.reset_btn = self._owner.reset_acf_filters_btn
self.apply_remove_btn.set_texts(Constants.APPLY, Constants.REMOVE)
self.apply_remove_btn.set_slave_filters(
simple_ones=[
self._owner.include_undef_acf,
self._owner.include_variable_acf,
self._owner.acf_spinbox,
self._owner.acf_confidence
]
)
self._owner.acf_info_btn.clicked.connect(lambda: webbrowser.open(Constants.ACF_DOCS))
connect_events_to_func(
events_to_connect=[self._owner.acf_spinbox.valueChanged,
self._owner.acf_confidence.valueChanged],
fun_to_connect=self._set_acf_interval_label,
fun_args=None
)
@pyqtSlot()
def _set_acf_interval_label(self):
"""Display the actual acf interval for the search."""
tolerance = self._owner.acf_spinbox.value() * self._owner.acf_confidence.value() / 100
if tolerance > 0:
val = round(self._owner.acf_spinbox.value() - tolerance, Constants.MAX_DIGITS)
to_display = f"Selected range:\n\n{val}" + Constants.RANGE_SEPARATOR \
+ f"{round(self._owner.acf_spinbox.value() + tolerance, Constants.MAX_DIGITS)} ms"
else:
to_display = f"Selected value:\n\n{self._owner.acf_spinbox.value()} ms"
self._owner.acf_range_lbl.setText(to_display)
self._owner.acf_range_lbl.setStyleSheet(f"color: {self._owner.active_color}")
@pyqtSlot()
def reset(self):
"""Reset the acf filter screen."""
uncheck_and_emit(self.apply_remove_btn)
if self._owner.include_undef_acf.isChecked():
self._owner.include_undef_acf.setChecked(False)
if self._owner.include_variable_acf.isChecked():
self._owner.include_variable_acf.setChecked(False)
self._owner.acf_spinbox.setValue(50)
self._owner.acf_confidence.setValue(0)
def _ok(self, signal_name):
"""Evalaute if the signal matches the acf filters."""
if not self.apply_remove_btn.isChecked():
return True
signal_acf_list = self._owner.db.at[signal_name, Signal.ACF]
if signal_acf_list[0].unknown: # Unknown acf are the only acf of the signal.
if self._owner.include_undef_acf.isChecked():
return True
else:
return False
else:
tolerance = self._owner.acf_spinbox.value() * self._owner.acf_confidence.value() / 100
upper_limit = self._owner.acf_spinbox.value() + tolerance
lower_limit = self._owner.acf_spinbox.value() - tolerance
for v in signal_acf_list:
if v.is_numeric:
if lower_limit <= v.numeric_value <= upper_limit:
return True
elif self._owner.include_variable_acf.isChecked():
return True
return False
def refresh(self):
"""Extend _BaseFilter.refresh."""
super().refresh()
self._set_acf_interval_label()
class Filters(QObject):
"""Global filter class.
Provides the information about all the filters. Its only public attribute
is filters, which is a dictionary containing instances of all the filters.
The only exposed methods are reset(), ok(signal_name) and refresh().
The class also connects the apply and reset buttons to the relevant functions."""
def __init__(self, owner):
super().__init__()
self.filters = {
"freq_filter": FreqFilter(owner),
"band_filter": BandFilter(owner),
"cat_filter": CatFilter(owner),
"mode_filter": ModeFilter(owner),
"modulation_filter": ModulationFilter(owner),
"location_filter": LocFilter(owner),
"acf_filter": ACFFilter(owner),
}
self._owner = owner
self._owner.reset_filters_btn.clicked.connect(self._reset)
# Connect Apply and Reset buttons clicks to functions.
for f in self._values:
f.apply_remove_btn.clicked.connect(self._display_signals)
f.reset_btn.clicked.connect(f.reset)
@property
def _values(self):
return self.filters.values()
@pyqtSlot()
def _display_signals(self):
self._owner.display_signals()
@pyqtSlot()
def _reset(self):
"""Reset all the filters."""
for f in self._values:
f.reset()
def ok(self, signal_name):
"""Check whether all the filters are passed."""
return all(f._ok(signal_name) for f in self._values)
def refresh(self):
"""Refresh the relevant widgets when changing theme."""
for f in self._values:
f.refresh()