Software Organization¶
The software is organized as shown in the following figure
- Communication:
- The modules communicate via Qt’s signal-slot mechanism (see: Signalling: What’s up?).
- Data Persistence:
- Common data is stored in dicts that can be accessed globally (see: Persistence: Where’s the data?).
- Customization:
- The software can be customized a.o. via the file
conf.py
(see: Customization).
Signalling: What’s up?¶
The figure above shows the general pyfda hierarchy. When parameters or settings are
changed in a widget, a Qt signal is emitted that can be processed by other widgets
with a sig_rx
slot for receiving information. The dict dict_sig
is attached
to the signal as a “payload”, providing information about the sender and the type
of event . sig_rx
is connected to the
process_sig_rx()
method that processes the dict.
Many Qt signals can be connected to one Qt slot and one signal to many slots,
so signals of input and plot widgets are collected in
pyfda.input_widgets.input_tab_widgets
and pyfda.plot_widgets.plot_tab_widgets
respectively and connected collectively.
When a redraw / calculations can take a long time, it makes sense to perform these operations only when the widget is visible and store the need for a redraw in a flag.
class MyWidget(QWidget):
sig_resize = pyqtSignal() # emit a local signal upon resize
sig_rx = pyqtSignal(object) # incoming signal
sig_tx = pyqtSignal(object) # outgoing signal
def __init__(self, parent):
super(MyWidget, self).__init__(parent)
self.data_changed = True # initialize flags
self.view_changed = True
self.filt_changed = True
self.sig_rx.connect(self.process_sig_rx)
# usually done in method ``_construct_UI()``
def process_sig_rx(self, dict_sig=None):
"""
Process signals coming in via subwidgets and sig_rx
"""
if dict_sig['sender'] == __name__: # only needed when a ``sig_tx signal`` is fired
logger.debug("Infinite loop detected")
return
if self.isVisible():
if 'data_changed' in dict_sig or self.data_changed:
self.recalculate_some_data() # this may take time ...
self.data_changed = False
if 'view_changed' in dict_sig and dict_sig['view_changed'] == 'new_limits'\
or self.view_changed:
self._update_my_plot() # ... while this just updates the display
self.view_changed = False
if 'filt_changed' in dict_sig or self.filt_changed:
self.update_wdg_UI() # new filter needs new UI options
self.filt_changed = False
else:
if 'data_changed' in dict_sig or 'view_changed' in dict_sig:
self.data_changed = True
self.view_changed = True
if 'filt_changed' in dict_sig:
self.filt_changed = True
Information is transmitted via the global sig_tx
signal:
dict_sig = {'sender':__name__, 'fx_sim':'set_results', 'fx_results':self.fx_results}
self.sig_tx.emit(dict_sig)
The following dictionary keys are generally used, individual ones can be created as needed.
‘sender’: | Fully qualified name of the sending widget, usually given as |
---|---|
‘filt_changed’: | A different filter type (response type, algorithm, …) has been selected or loaded, requiring an update of the UI in some widgets. |
‘data_changed’: | A filter has been designed and the actual data (e.g. coefficients) has changed, you can add the (short) name or a data description as the dict value. When this key is sent, most widgets have to be updated. |
‘specs_changed’: | |
Filter specifications have changed - this will influence only a few widgets like the plot_hf widget that plots the filter specifications as an overlay or the input_info widget that compares filter performance to filter specifications. |
|
‘view_changed’: | When e.g. the range of the frequency axis is changed from
\(0 \ldots f_S/2\) to \(-f_S/2 \ldots f_S/2\), this information can
be propagated with the |
‘ui_changed’: | Propagate a change of the UI to other widgets, examples are:
|
‘fx_sim’: | Signal the phase / status of a fixpoint simulation (‘finished’, ‘error’) |
Persistence: Where’s the data?¶
At startup, a dictionary is constructed with information about the filter
classes and their methods. The central dictionary fb.dict
is initialized.