class Window(QWidget): """Basic window class.""" def __init__(self): super().__init__() layout = QVBoxLayout() self.setLayout(layout) self.label = QLabel("Output from processes will be shown here", self) layout.addWidget(self.label) self.progress = QProgressBar(self) layout.addWidget(self.progress) self.button = QPushButton("Start", self) self.button.clicked.connect(self.on_click) layout.addWidget(self.button) self.scheduler: Scheduler = None def on_click(self): """ Called when the button is clicked. """ if self.scheduler is None or not self.scheduler.is_running(): # "Start" was clicked. Start the coroutine which runs the scheduler. asyncio.ensure_future(self.do_calculations()) self.button.setText("Cancel") else: # "Cancel" was clicked. Terminate the scheduler. self.scheduler.terminate() self.button.setText("Start") self.progress.setValue(0) async def do_calculations(self): """ Does the calculations using a scheduler, and shows the output in the label. """ self.scheduler = Scheduler(progress_callback=self.on_progress) num_processes = 16 args = [] for _ in range(num_processes): sleep_time = random.randint(1, 8) # Add to list of arguments. Must be tuple. args.append((sleep_time, )) # Run all processes and `await` the results: an ordered list containing one int from each process. output: List[int] = await self.scheduler.map( target=long_calculation, args=args, ) # (If the scheduler was terminated before completion, we don't want the results). if not self.scheduler.terminated: text = ", ".join([str(i) for i in output]) self.label.setText(f"Output: {text}") self.button.setText("Start") def on_progress(self, done: int, total: int) -> None: """ Updates the progress bar when scheduler finishes a task. """ if done == 0: self.progress.setMaximum(total) self.progress.setValue(done) def closeEvent(self, event: QtGui.QCloseEvent) -> None: """ Terminates the scheduler when the window exits. """ if self.scheduler: self.scheduler.terminate()
class MPHandler: """ A class providing functions which perform mathematical computations using a Scheduler. Important: - Keep a reference to any instances of `MPHandler` to prevent them from being garbage collected before tasks have completed. - Calling any function on a running MPHandler will stop any tasks currently in progress. """ def __init__(self): self.scheduler: Scheduler = None async def coro_transform( self, params: TFParams, on_progress: Callable[[int, int], None]) -> List[tuple]: """ Performs a wavelet transform or windowed Fourier transform of signals. Used in "time-frequency analysis". :param params: the parameters which are used in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler(progress_callback=on_progress) signals: Signals = params.signals params.remove_signals( ) # Don't want to pass large unneeded object to other process. for time_series in signals: self.scheduler.add( target=_time_frequency, args=(time_series, params), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_phase_coherence( self, signals: SignalPairs, params: PCParams, on_progress: Callable[[int, int], None], ) -> List[tuple]: """ Performs wavelet phase coherence between signal pairs. Used in "wavelet phase coherence". :param signals: the pairs of signals :param params: the parameters which are used in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler(progress_callback=on_progress) for i in range(signals.pair_count()): pair = signals.get_pair_by_index(i) self.scheduler.add( target=_phase_coherence, args=(pair, params), subtasks=params.surr_count, process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_ridge_extraction( self, params: REParams, on_progress: Callable[[int, int], None]) -> List[tuple]: """ Performs ridge extraction on wavelet transforms. Used in "ridge extraction and filtering". :param params: the parameters which are used in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler(progress_callback=on_progress) signals = params.signals num_transforms = len(signals) intervals = params.intervals for i in range(num_transforms): for j in range(len(intervals)): fmin, fmax = intervals[j] params.set_item(_fmin, fmin) params.set_item(_fmax, fmax) self.scheduler.add( target=_ridge_extraction, args=(signals[i], params), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_bandpass_filter( self, signals: Signals, intervals: tuple, on_progress: Callable[[int, int], None], ) -> List[tuple]: """ Performs bandpass filter on signals. Used in "ridge extraction and filtering". :param signals: the signals :param intervals: the intervals to calculate bandpass filter on :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler(progress_callback=on_progress) for s in signals: fs = s.frequency for i in range(len(intervals)): fmin, fmax = intervals[i] self.scheduler.add( target=_bandpass_filter, args=(s, fmin, fmax, fs), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_bayesian( self, signals: SignalPairs, paramsets: List[ParamSet], on_progress: Callable[[int, int], None], ) -> List[tuple]: """ Performs Bayesian inference on signal pairs. Used in "dynamical Bayesian inference". :param signals: the signals :param paramsets: the parameter sets to use in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler(progress_callback=on_progress) for params in paramsets: for pair in signals.get_pairs(): self.scheduler.add( target=_dynamic_bayesian_inference, args=(*pair, params), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_bispectrum_analysis( self, signals: SignalPairs, params: BAParams, on_progress: Callable[[int, int], None], ) -> List[tuple]: """ Performs wavelet bispectrum analysis on signal pairs. Used in "wavelet bispectrum analysis". :param signals: the signal pairs :param params: the parameters to use in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler(progress_callback=on_progress) for pair in signals.get_pairs(): self.scheduler.add( target=_bispectrum_analysis, args=(*pair, params), subtasks=4, process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_biphase( self, signals: SignalPairs, fs: float, f0: float, fr: Tuple[float, float], on_progress: Callable[[int, int], None], ) -> List[tuple]: """ Calculates biphase and biamplitude. Used in "wavelet bispectrum analysis". :param signals: the signal pairs :param fs: the sampling frequency :param f0: the resolution :param fr: 'x' and 'y' frequencies :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler(progress_callback=on_progress) for pair in signals.get_pairs(): opt = pair[0].output_data.opt self.scheduler.add( target=_biphase, args=(*pair, fs, f0, fr, opt), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_preprocess(self, signal: TimeSeries, fmin: float, fmax: float) -> List[Tuple]: """ Performs preprocessing on a single signal. :param signal: the signal as a 1D array :param fmin: the minimum frequency :param fmax: the maximum frequency :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler() self.scheduler.add( target=_preprocess, args=(signal.signal, signal.frequency, fmin, fmax), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() def stop(self): """ Stops the tasks in progress. The MPHandler instance can be reused. """ if self.scheduler: self.scheduler.terminate()
class MPHandler: """ A class providing functions which perform mathematical computations using a Scheduler. Important: - Keep a reference to any instances of `MPHandler` to prevent them from being garbage collected before tasks have completed. - Calling any function on a running MPHandler will stop any tasks currently in progress. """ # On Linux, we don't need to run in a thread because processes can be forked; we also need to avoid # using a thread because this will cause issues with the LD_LIBRARY_PATH. should_run_in_thread = not OS.is_linux() # On macOS, multiprocess has issues so we need to use threads for everything. only_threads = OS.is_mac_os() def __init__(self): self.scheduler: Scheduler = None async def coro_transform( self, params: TFParams, on_progress: Callable[[int, int], None]) -> List[Tuple]: """ Performs a wavelet transform or windowed Fourier transform of signals. Used in "time-frequency analysis". :param params: the parameters which are used in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) signals: Signals = params.signals params.remove_signals( ) # Don't want to pass large unneeded object to other process. return await self.scheduler.map( target=_time_frequency, args=[(time_series, params, True) for time_series in signals], process_type=mp.Process, queue_type=mp.Queue, ) async def coro_harmonics( self, signals: Signals, params: DHParams, preprocess: bool, on_progress: Callable[[int, int], None], ) -> List[Tuple]: """ Detects harmonics in signals. :param signals: the signals :param params: the parameters to pass to the harmonic finder :param preprocess: whether to perform pre-processing on the signals :param on_progress: the progress callback :return: list containing the output from each process """ # Whether to parallelize the algorithm for each calculation. parallel = len(signals) < Scheduler.optimal_process_count() self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) args = [( preprocess, sig.signal, params, *params.args(), parallel, params.crop, ) for sig in signals] return await self.scheduler.map(target=harmonic_wrapper, args=args) async def coro_phase_coherence( self, signals: SignalPairs, params: PCParams, on_progress: Callable[[int, int], None], ) -> List[Tuple]: """ Performs wavelet phase coherence between signal pairs. Used in "wavelet phase coherence". :param signals: the pairs of signals :param params: the parameters which are used in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) return await self.scheduler.map( target=_phase_coherence, args=[(pair, params) for pair in signals.get_pairs()], subtasks=params.surr_count, process_type=mp.Process, queue_type=mp.Queue, ) async def coro_ridge_extraction( self, params: REParams, on_progress: Callable[[int, int], None]) -> List[Tuple]: """ Performs ridge extraction on wavelet transforms. Used in "ridge extraction and filtering". :param params: the parameters which are used in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) signals = params.signals num_transforms = len(signals) intervals = params.intervals for i in range(num_transforms): for j in range(len(intervals)): fmin, fmax = intervals[j] params.set_item(_fmin, fmin) params.set_item(_fmax, fmax) self.scheduler.add( target=_ridge_extraction, args=(signals[i], params), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_bandpass_filter( self, signals: Signals, intervals: Tuple, on_progress: Callable[[int, int], None], ) -> List[Tuple]: """ Performs bandpass filter on signals. Used in "ridge extraction and filtering". :param signals: the signals :param intervals: the intervals to calculate bandpass filter on :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) for s in signals: fs = s.frequency for i in range(len(intervals)): fmin, fmax = intervals[i] self.scheduler.add( target=_bandpass_filter, args=(s, fmin, fmax, fs), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_bayesian( self, signals: SignalPairs, paramsets: List[ParamSet], on_progress: Callable[[int, int], None], ) -> List[Tuple]: """ Performs Bayesian inference on signal pairs. Used in "dynamical Bayesian inference". :param signals: the signals :param paramsets: the parameter sets to use in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) for params in paramsets: for pair in signals.get_pairs(): self.scheduler.add( target=_dynamic_bayesian_inference, args=(*pair, params), process_type=mp.Process, queue_type=mp.Queue, ) return await self.scheduler.run() async def coro_bispectrum_analysis( self, signals: SignalPairs, params: BAParams, on_progress: Callable[[int, int], None], ) -> List[Tuple]: """ Performs wavelet bispectrum analysis on signal pairs. Used in "wavelet bispectrum analysis". :param signals: the signal pairs :param params: the parameters to use in the algorithm :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) return await self.scheduler.map( target=_bispectrum_analysis, args=[(*pair, params) for pair in signals.get_pairs()], subtasks=4, process_type=mp.Process, queue_type=mp.Queue, ) async def coro_biphase( self, signals: SignalPairs, fs: float, f0: float, fr: Tuple[float, float], on_progress: Callable[[int, int], None], ) -> List[Tuple]: """ Calculates biphase and biamplitude. Used in "wavelet bispectrum analysis". :param signals: the signal pairs :param fs: the sampling frequency :param f0: the resolution :param fr: 'x' and 'y' frequencies :param on_progress: progress callback :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) args = [(s1, s2, fs, f0, fr, s1.output_data.opt) for s1, s2 in signals.get_pairs()] return await self.scheduler.map(target=_biphase, args=args, process_type=mp.Process, queue_type=mp.Queue) async def coro_group_coherence(self, sig1a: ndarray, sig1b: ndarray, fs: float, percentile: Optional[float], on_progress: Callable[[int, int], None], *args, **kwargs) -> List[Tuple]: """ Calculates group coherence. Parameters ---------- sig1a : ndarray The set of signals A for group 1. sig1b : ndarray The set of signals B for group 1. fs : float The sampling frequency of the signals. percentile : Optional[float] The percentile at which the surrogates will be subtracted. on_progress : Callable Function called to report progress. args Arguments to pass to the wavelet transform. kwargs Keyword arguments to pass to the wavelet transform. Returns ------- freq : ndarray [1D array] The frequencies. coh1 : ndarray [2D array] The residual coherence for group 1. surr1 : ndarray [3D array] The surrogates for group 1. """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) return await self.scheduler.map( target=functools.partial( pymodalib.group_coherence, sig1a, sig1b, fs, percentile, True, *args, **kwargs, ), args=[ tuple(), ], ) async def coro_dual_group_coherence(self, sig1a: ndarray, sig1b: ndarray, sig2a: ndarray, sig2b: ndarray, fs: float, percentile: Optional[float], on_progress: Callable[[int, int], None], *args, **kwargs) -> List[Tuple]: """ Calculates group coherence. Parameters ---------- sig1a : ndarray The set of signals A for group 1. sig1b : ndarray The set of signals B for group 1. sig2a : ndarray The set of signals A for group 2. sig2b : ndarray The set of signals B for group 2. fs : float The sampling frequency of the signals. percentile : Optional[float] The percentile at which the surrogates will be subtracted. on_progress : Callable Function called to report progress. args Arguments to pass to the wavelet transform. kwargs Keyword arguments to pass to the wavelet transform. Returns ------- freq : ndarray [1D array] The frequencies. coh1 : ndarray [2D array] The residual coherence for group 1. coh2 : ndarray [2D array] The residual coherence for group 2. surr1 : ndarray [3D array] The surrogates for group 1. surr2 : ndarray [3D array] The surrogates for group 2. """ self.stop() self.scheduler = Scheduler( progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) return await self.scheduler.map( target=functools.partial( pymodalib.dual_group_coherence, sig1a, sig1b, sig2a, sig2b, fs, percentile, *args, **kwargs, ), args=[ tuple(), ], ) async def coro_statistical_test( self, freq: ndarray, coh1: ndarray, coh2: ndarray, bands: List[Tuple[float, float]], on_progress: Callable[[int, int], None], ) -> Dict[Tuple[float, float], float]: """ Performs a statistical test on the results of group phase coherence, to check for significance. Parameters ---------- freq : ndarray [1D array] The frequencies from group coherence. coh1 : ndarray [2D array] The coherence of the first group. coh2 : ndarray [2D array] The coherence of the second group. bands : List[Tuple[float,float]] List containing the frequency bands which will be tested for significance. on_progress : Callable Function called to report progress. Returns ------- pvalues : Dict[Tuple[float, float], float] A list containing the p-values for each frequency band. """ self.stop() self.scheduler = Scheduler( run_in_thread=self.should_run_in_thread, progress_callback=on_progress, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) from pymodalib.algorithms.group_coherence import statistical_test results = (await self.scheduler.map( target=statistical_test, args=[( freq, coh1, coh2, bands, )], ))[0] return dict(zip(bands, results)) async def coro_preprocess(self, signals: Union[TimeSeries, List[TimeSeries]], fmin: float, fmax: float) -> List[ndarray]: """ Performs preprocessing on a single signal. :param signals: the signal or signals to perform pre-processing on :param fmin: the minimum frequency :param fmax: the maximum frequency :return: list containing the output from each process """ self.stop() self.scheduler = Scheduler( run_in_thread=True, raise_exceptions=True, capture_stdout=True, only_threads=self.only_threads, ) if isinstance(signals, TimeSeries): signals = [signals] args = [(s.signal, s.frequency, fmin, fmax) for s in signals] return await self.scheduler.map( target=pymodalib.preprocess, args=args, process_type=mp.Process, queue_type=mp.Queue, ) def stop(self): """ Stops the tasks in progress. The MPHandler instance can be reused. """ if self.scheduler: self.scheduler.terminate()