Coverage for / opt / hostedtoolcache / Python / 3.11.14 / x64 / lib / python3.11 / site-packages / rattlesnake / components / spectral_processing.py: 41%
280 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-27 18:22 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-27 18:22 +0000
1# -*- coding: utf-8 -*-
2"""
3Controller subsystem that handles computation of FRFs, CPSDs, and other spectral
4quantities of interest
6Rattlesnake Vibration Control Software
7Copyright (C) 2021 National Technology & Engineering Solutions of Sandia, LLC
8(NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
9Government retains certain rights in this software.
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19GNU General Public License for more details.
21You should have received a copy of the GNU General Public License
22along with this program. If not, see <https://www.gnu.org/licenses/>.
23"""
25import multiprocessing as mp
26import time
27from enum import Enum
29import numpy as np
31from .abstract_message_process import AbstractMessageProcess
32from .utilities import GlobalCommands, VerboseMessageQueue, flush_queue
34WAIT_TIME = 0.05
37class SpectralProcessingCommands(Enum):
38 """Collection of instructions that the FRF Computation Process might get"""
40 INITIALIZE_PARAMETERS = 0
41 RUN_SPECTRAL_PROCESSING = 1
42 CLEAR_SPECTRAL_PROCESSING = 2
43 STOP_SPECTRAL_PROCESSING = 3
44 SENT_SPECTRAL_DATA = 4
45 SHUTDOWN_ACHIEVED = 5
48class AveragingTypes(Enum):
49 """Collection of averating types that can exist"""
51 LINEAR = 0
52 EXPONENTIAL = 1
55class Estimator(Enum):
56 """Collection of FRF Estimators that can exist"""
58 H1 = 0
59 H2 = 1
60 H3 = 2
61 HV = 3
64class SpectralProcessingMetadata:
65 """Metadata required to define the signal processing process"""
67 def __init__(
68 self,
69 averaging_type,
70 averages,
71 exponential_averaging_coefficient,
72 frf_estimator,
73 num_response_channels,
74 num_reference_channels,
75 frequency_spacing,
76 sample_rate,
77 num_frequency_lines,
78 compute_cpsd=True,
79 compute_frf=True,
80 compute_coherence=True,
81 compute_apsd=True,
82 ):
83 self.averaging_type = averaging_type
84 self.averages = averages
85 self.exponential_averaging_coefficient = exponential_averaging_coefficient
86 self.frf_estimator = frf_estimator
87 self.num_response_channels = num_response_channels
88 self.num_reference_channels = num_reference_channels
89 self.frequency_spacing = frequency_spacing
90 self.sample_rate = sample_rate
91 self.num_frequency_lines = num_frequency_lines
92 self.compute_cpsd = compute_cpsd
93 self.compute_frf = compute_frf
94 self.compute_coherence = compute_coherence
95 self.compute_apsd = compute_apsd
97 def __eq__(self, other):
98 try:
99 return np.all(
100 [np.all(value == other.__dict__[field]) for field, value in self.__dict__.items()]
101 )
102 except (AttributeError, KeyError):
103 return False
105 @property
106 def requires_full_spectral_response(self):
107 """Checks if the requested outputs require calculation the full response CPSD matrix"""
108 if (
109 self.compute_frf and self.frf_estimator in [Estimator.H2, Estimator.H3]
110 ) or self.compute_cpsd:
111 return True
112 else:
113 return False
115 @property
116 def requires_diagonal_spectral_response(self):
117 """Checks if the requested outputs require calculation of the
118 diagonals of the response matrix"""
119 if (
120 (self.compute_frf and self.frf_estimator in [Estimator.HV])
121 or self.compute_apsd
122 or self.compute_coherence
123 ):
124 return True
125 else:
126 return False
128 @property
129 def requires_full_spectral_reference(self):
130 """Checks if the requested outputs require calculation of the full reference CPSD"""
131 if (
132 (self.compute_frf and self.frf_estimator in [Estimator.H1, Estimator.H3, Estimator.HV])
133 or self.compute_cpsd
134 or self.compute_coherence
135 ):
136 return True
137 else:
138 return False
140 @property
141 def requires_diagonal_spectral_reference(self):
142 """Checks if the requested outputs require calculation of the diagonals of the reference
143 matrix"""
144 if self.compute_apsd:
145 return True
146 else:
147 return False
149 @property
150 def requires_spectral_reference_response(self):
151 """Checks if the requested outputs require calculation of the cross spectra between
152 reference and response"""
153 if self.compute_frf or self.compute_coherence:
154 return True
155 else:
156 return False
159class SpectralProcessingProcess(AbstractMessageProcess):
160 """Class defining a subprocess that computes a FRF from a time history."""
162 def __init__(
163 self,
164 process_name: str,
165 command_queue: VerboseMessageQueue,
166 data_in_queue: mp.queues.Queue,
167 data_out_queue: mp.queues.Queue,
168 environment_command_queue: VerboseMessageQueue,
169 gui_update_queue: mp.queues.Queue,
170 log_file_queue: mp.queues.Queue,
171 environment_name: str,
172 ):
173 """
174 Constructor for the FRF Computation Process
176 Sets up the ``command_map`` and initializes internal data
178 Parameters
179 ----------
180 process_name : str
181 Name for the process that will be used in the Log file.
182 command_queue : VerboseMessageQueue :
183 The queue containing instructions for the FRF process
184 data_for_frf_queue : mp.queues.Queue :
185 Queue containing input data for the FRF computation
186 updated_frf_queue : mp.queues.Queue :
187 Queue where frf process will put computed frfs
188 gui_update_queue : mp.queues.Queue :
189 Queue for gui updates
190 log_file_queue : mp.queues.Queue :
191 Queue for writing to the log file
192 environment_name : str
193 Name of the environment that controls this subprocess.
195 """
196 super().__init__(process_name, log_file_queue, command_queue, gui_update_queue)
197 self.map_command(
198 SpectralProcessingCommands.INITIALIZE_PARAMETERS, self.initialize_parameters
199 )
200 self.map_command(
201 SpectralProcessingCommands.RUN_SPECTRAL_PROCESSING,
202 self.run_spectral_processing,
203 )
204 self.map_command(
205 SpectralProcessingCommands.CLEAR_SPECTRAL_PROCESSING,
206 self.clear_spectral_processing,
207 )
208 self.map_command(
209 SpectralProcessingCommands.STOP_SPECTRAL_PROCESSING,
210 self.stop_spectral_processing,
211 )
212 self.environment_name = environment_name
213 self.data_in_queue = data_in_queue
214 self.data_out_queue = data_out_queue
215 self.environment_command_queue = environment_command_queue
216 self.response_spectral_matrix = None
217 self.reference_spectral_matrix = None
218 self.response_reference_spectral_matrix = None
219 self.reference_diagonal_matrix = None
220 self.response_diagonal_matrix = None
221 self.response_fft = None
222 self.reference_fft = None
223 self.spectral_processing_parameters = None
224 self.frames_computed = 0
226 def initialize_parameters(self, data: SpectralProcessingMetadata):
227 """Initializes the signal processing parameters from the environment.
229 Parameters
230 ----------
231 data :
232 Container containing the setting specific to the environment.
234 """
235 if self.spectral_processing_parameters is None:
236 reshape_arrays = True
237 elif (
238 self.spectral_processing_parameters.num_frequency_lines != data.num_frequency_lines
239 or self.spectral_processing_parameters.num_response_channels
240 != data.num_response_channels
241 or self.spectral_processing_parameters.num_reference_channels
242 != data.num_reference_channels
243 or self.spectral_processing_parameters.averages != data.averages
244 or self.spectral_processing_parameters.averaging_type != data.averaging_type
245 ):
246 reshape_arrays = True
247 else:
248 reshape_arrays = False
249 self.spectral_processing_parameters = data
250 if reshape_arrays:
251 self.log("Initializing Empty Arrays")
252 self.frames_computed = 0
253 self.response_spectral_matrix = None
254 self.reference_spectral_matrix = None
255 self.response_reference_spectral_matrix = None
256 self.reference_diagonal_matrix = None
257 self.response_diagonal_matrix = None
258 if self.spectral_processing_parameters.averaging_type == AveragingTypes.LINEAR:
259 self.response_fft = np.nan * np.ones(
260 (
261 self.spectral_processing_parameters.averages,
262 self.spectral_processing_parameters.num_response_channels,
263 self.spectral_processing_parameters.num_frequency_lines,
264 ),
265 dtype=complex,
266 )
267 self.reference_fft = np.nan * np.ones(
268 (
269 self.spectral_processing_parameters.averages,
270 self.spectral_processing_parameters.num_reference_channels,
271 self.spectral_processing_parameters.num_frequency_lines,
272 ),
273 dtype=complex,
274 )
275 # print(self.response_fft.shape)
276 else:
277 self.response_fft = None
278 self.reference_fft = None
280 def run_spectral_processing(self, data):
281 """Continuously compute FRFs from time histories.
283 This function accepts data from the ``data_for_frf_queue`` and computes
284 FRF matrices from the time data. It uses a rolling buffer to append
285 data. The oldest data is pushed out of the buffer by the newest data.
286 The test level is also passed with the response data and output
287 data. The test level is used to ensure that no frame uses
288 discontinuous data.
290 Parameters
291 ----------
292 data : Ignored
293 This parameter is not used by the function but must be present
294 due to the calling signature of functions called through the
295 ``command_map``
297 """
298 data = flush_queue(self.data_in_queue, timeout=WAIT_TIME)
299 if len(data) == 0:
300 time.sleep(WAIT_TIME)
301 self.command_queue.put(
302 self.process_name,
303 (SpectralProcessingCommands.RUN_SPECTRAL_PROCESSING, None),
304 )
305 return
306 frames_received = len(data)
307 self.log(f"Received {frames_received} Frames")
308 if self.spectral_processing_parameters.averaging_type == AveragingTypes.LINEAR:
309 response_fft, reference_fft = [value for value in zip(*data)]
310 self.response_fft = np.concatenate(
311 (
312 self.response_fft[frames_received:],
313 response_fft[-self.response_fft.shape[0] :],
314 ),
315 axis=0,
316 )
317 self.reference_fft = np.concatenate(
318 (
319 self.reference_fft[frames_received:],
320 reference_fft[-self.reference_fft.shape[0] :],
321 ),
322 axis=0,
323 )
324 self.log(
325 f"Buffered Frames (Resp Shape: {self.response_fft.shape}, "
326 f"Ref Shape: {self.reference_fft.shape})"
327 )
328 # Exclude any with NaNs
329 exclude_averages = np.any(np.isnan(self.response_fft), axis=(-1, -2))
330 self.log(f"Computed Number Averages {(~exclude_averages).sum()}")
331 # Return if there is actually no data
332 if np.all(exclude_averages):
333 self.command_queue.put(
334 self.process_name,
335 (SpectralProcessingCommands.RUN_SPECTRAL_PROCESSING, None),
336 )
337 return
338 mean_fft = np.mean(np.abs(self.reference_fft[~exclude_averages]), axis=(-1, -2))
339 self.log(f"Mean FFT Value Over Averaged Frames: \n {mean_fft}")
341 # Now we compute the spectral matrices depending on what is required.
342 # Compute the response power spectra
343 response_spectral_time = time.time()
344 if self.spectral_processing_parameters.requires_full_spectral_response:
345 self.log("Computing Full Spectral Response Matrix")
346 self.response_spectral_matrix = (
347 np.einsum(
348 "aif,ajf->fij",
349 self.response_fft[~exclude_averages],
350 np.conj(self.response_fft[~exclude_averages]),
351 )
352 / self.response_fft[~exclude_averages].shape[0]
353 )
354 # Get the diagonal matrix as well
355 self.response_diagonal_matrix = np.einsum("fii->fi", self.response_spectral_matrix)
356 elif self.spectral_processing_parameters.requires_diagonal_spectral_response:
357 self.log("Computing Diagonal of Spectral Response Matrix")
358 # self.response_diagonal_matrix = np.einsum(
359 # 'aif,aif->fi',
360 # self.response_fft[~exclude_averages],
361 # np.conj(self.response_fft[~exclude_averages])
362 # )/self.response_fft[~exclude_averages].shape[0]
363 self.response_diagonal_matrix = np.mean(
364 self.response_fft[~exclude_averages]
365 * np.conj(self.response_fft[~exclude_averages]),
366 axis=0,
367 ).T
368 if (
369 self.spectral_processing_parameters.requires_full_spectral_response
370 or self.spectral_processing_parameters.requires_diagonal_spectral_response
371 ):
372 self.log(
373 "Computed Response Spectral Matrix in "
374 f"{time.time() - response_spectral_time:0.2f} seconds"
375 )
377 # Compute the reference power spectra
378 reference_spectral_time = time.time()
379 if self.spectral_processing_parameters.requires_full_spectral_reference:
380 self.log("Computing Full Spectral Reference Matrix")
381 self.reference_spectral_matrix = (
382 np.einsum(
383 "aif,ajf->fij",
384 self.reference_fft[~exclude_averages],
385 np.conj(self.reference_fft[~exclude_averages]),
386 )
387 / self.reference_fft[~exclude_averages].shape[0]
388 )
389 # Get the diagonal matrix as well
390 self.reference_diagonal_matrix = np.einsum(
391 "fii->fi", self.reference_spectral_matrix
392 )
393 elif self.spectral_processing_parameters.requires_diagonal_spectral_reference:
394 self.log("Computing Diagonal of Spectral Reference Matrix")
395 self.reference_diagonal_matrix = (
396 np.einsum(
397 "aif,aif->fi",
398 self.reference_fft[~exclude_averages],
399 np.conj(self.reference_fft[~exclude_averages]),
400 )
401 / self.reference_fft[~exclude_averages].shape[0]
402 )
403 if (
404 self.spectral_processing_parameters.requires_full_spectral_reference
405 or self.spectral_processing_parameters.requires_diagonal_spectral_reference
406 ):
407 self.log(
408 f"Computed Reference Spectral Matrix in "
409 f"{time.time() - reference_spectral_time:0.2f} seconds"
410 )
412 # Compute cross spectra between reference and response
413 if self.spectral_processing_parameters.requires_spectral_reference_response:
414 cross_spectral_time = time.time()
415 self.log("Computing Full Cross Spectral Response/Reference Matrix")
416 self.response_reference_spectral_matrix = (
417 np.einsum(
418 "aif,ajf->fij",
419 self.response_fft[~exclude_averages],
420 np.conj(self.reference_fft[~exclude_averages]),
421 )
422 / self.response_fft[~exclude_averages].shape[0]
423 )
424 self.log(
425 "Computed Crossspectral Matrix in "
426 f"{time.time() - cross_spectral_time:0.2f} seconds"
427 )
428 frames = self.spectral_processing_parameters.averages - np.sum(exclude_averages)
430 else: # For exponential averaging
431 for frame in data:
432 response_fft, reference_fft = frame
434 # Compute response spectra
435 response_spectral_time = time.time()
436 if self.spectral_processing_parameters.requires_full_spectral_response:
437 self.log("Computing Full Spectral Response Matrix")
438 if self.response_spectral_matrix is None:
439 self.response_spectral_matrix = np.einsum(
440 "if,jf->fij", response_fft, np.conj(response_fft)
441 )
442 else:
443 self.response_spectral_matrix = (
444 self.spectral_processing_parameters.exponential_averaging_coefficient
445 * np.einsum("if,jf->fij", response_fft, np.conj(response_fft))
446 + (
447 1
448 - self.spectral_processing_parameters.exponential_averaging_coefficient
449 )
450 * self.response_spectral_matrix
451 )
452 # Get the diagonal matrix as well
453 self.response_diagonal_matrix = np.einsum(
454 "fii->fi", self.response_spectral_matrix
455 )
456 elif self.spectral_processing_parameters.requires_diagonal_spectral_response:
457 self.log("Computing Diagonal of Spectral Response Matrix")
458 if self.response_diagonal_matrix is None:
459 self.response_diagonal_matrix = np.einsum(
460 "if,if->fi", response_fft, np.conj(response_fft)
461 )
462 else:
463 self.response_diagonal_matrix = (
464 self.spectral_processing_parameters.exponential_averaging_coefficient
465 * np.einsum("if,if->fi", response_fft, np.conj(response_fft))
466 + (
467 1
468 - self.spectral_processing_parameters.exponential_averaging_coefficient
469 )
470 * self.response_diagonal_matrix
471 )
472 if (
473 self.spectral_processing_parameters.requires_full_spectral_response
474 or self.spectral_processing_parameters.requires_diagonal_spectral_response
475 ):
476 self.log(
477 "Computed Response Spectral Matrix in "
478 f"{time.time() - response_spectral_time:0.2f} seconds"
479 )
481 # Compute the reference spectra
482 reference_spectral_time = time.time()
483 if self.spectral_processing_parameters.requires_full_spectral_reference:
484 self.log("Computing Full Spectral Reference Matrix")
485 if self.reference_spectral_matrix is None:
486 self.reference_spectral_matrix = np.einsum(
487 "if,jf->fij", reference_fft, np.conj(reference_fft)
488 )
489 else:
490 self.reference_spectral_matrix = (
491 self.spectral_processing_parameters.exponential_averaging_coefficient
492 * np.einsum("if,jf->fij", reference_fft, np.conj(reference_fft))
493 + (
494 1
495 - self.spectral_processing_parameters.exponential_averaging_coefficient
496 )
497 * self.reference_spectral_matrix
498 )
499 # Get the diagonal matrix as well
500 self.reference_diagonal_matrix = np.einsum(
501 "fii->fi", self.reference_spectral_matrix
502 )
503 elif self.spectral_processing_parameters.requires_diagonal_spectral_reference:
504 self.log("Computing Diagonal of Spectral Reference Matrix")
505 if self.reference_diagonal_matrix is None:
506 self.reference_diagonal_matrix = np.einsum(
507 "if,if->fi", reference_fft, np.conj(reference_fft)
508 )
509 else:
510 self.reference_diagonal_matrix = (
511 self.spectral_processing_parameters.exponential_averaging_coefficient
512 * np.einsum("if,if->fi", reference_fft, np.conj(reference_fft))
513 + (
514 1
515 - self.spectral_processing_parameters.exponential_averaging_coefficient
516 )
517 * self.reference_diagonal_matrix
518 )
519 if (
520 self.spectral_processing_parameters.requires_full_spectral_reference
521 or self.spectral_processing_parameters.requires_diagonal_spectral_reference
522 ):
523 self.log(
524 "Computed Reference Spectral Matrix in "
525 f"{time.time() - reference_spectral_time:0.2f} seconds"
526 )
528 # Compute reference and response cross spectra
529 if self.spectral_processing_parameters.requires_spectral_reference_response:
530 cross_spectral_time = time.time()
531 self.log("Computing Full Cross Spectral Response/Reference Matrix")
532 if self.response_reference_spectral_matrix is None:
533 self.response_reference_spectral_matrix = np.einsum(
534 "if,jf->fij", response_fft, np.conj(reference_fft)
535 )
536 else:
537 self.response_reference_spectral_matrix = (
538 self.spectral_processing_parameters.exponential_averaging_coefficient
539 * np.einsum("if,jf->fij", response_fft, np.conj(reference_fft))
540 + (
541 1
542 - self.spectral_processing_parameters.exponential_averaging_coefficient
543 )
544 * self.response_reference_spectral_matrix
545 )
546 self.log(
547 "Computed Crossspectral Matrix in "
548 f"{time.time() - cross_spectral_time:0.2f} seconds"
549 )
550 self.frames_computed += 1
552 frames = self.frames_computed
553 self.log(
554 f"Computed Spectral Matrices for {frames} frames in "
555 f"{time.time() - response_spectral_time:0.2f} seconds"
556 )
557 gffpinv = None
558 gfxpinv = None
559 if self.spectral_processing_parameters.compute_frf:
560 frf_time = time.time()
561 if self.spectral_processing_parameters.frf_estimator == Estimator.H1:
562 if gffpinv is None:
563 gffpinv = np.linalg.pinv(
564 self.reference_spectral_matrix, rcond=1e-12, hermitian=True
565 )
566 frf = self.response_reference_spectral_matrix @ gffpinv
567 elif self.spectral_processing_parameters.frf_estimator == Estimator.H2:
568 gfx = self.response_reference_spectral_matrix.conj().transpose(0, 2, 1)
569 gfxpinv = np.linalg.pinv(gfx, rcond=1e-12, hermitian=True)
570 frf = self.response_spectral_matrix @ gfxpinv
571 elif self.spectral_processing_parameters.frf_estimator == Estimator.H3:
572 if gffpinv is None:
573 gffpinv = np.linalg.pinv(
574 self.reference_spectral_matrix, rcond=1e-12, hermitian=True
575 )
576 gfx = self.response_reference_spectral_matrix.conj().transpose(0, 2, 1)
577 if gfxpinv is None:
578 gfxpinv = np.linalg.pinv(gfx, rcond=1e-12, hermitian=True)
579 frf = (
580 self.response_spectral_matrix @ gfxpinv
581 + self.response_reference_spectral_matrix @ gffpinv
582 ) / 2
583 elif self.spectral_processing_parameters.frf_estimator == Estimator.HV:
584 gxx = self.response_diagonal_matrix.T[..., np.newaxis, np.newaxis]
585 gxf = np.einsum("fij->ifj", self.response_reference_spectral_matrix)[
586 ..., np.newaxis, :
587 ]
588 gff = self.reference_spectral_matrix
589 gff = np.broadcast_to(gff, gxx.shape[:-2] + gff.shape[-2:])
590 gffx = np.block([[gff, np.conj(np.moveaxis(gxf, -2, -1))], [gxf, gxx]])
591 # Compute eigenvalues
592 _, evect = np.linalg.eigh(np.moveaxis(gffx, -2, -1))
593 # Get the evect corresponding to the minimum eigenvalue
594 evect = evect[..., 0] # Assumes evals are sorted ascending
595 frf = np.moveaxis(
596 -evect[..., :-1] / evect[..., -1:], # Scale so last value is -1
597 -3,
598 -2,
599 )
600 else:
601 raise ValueError(f"Invalid frf_estimator {Estimator.H1}")
602 self.log(f"Computed FRF in {time.time() - frf_time:0.2f} seconds")
603 cond_time = time.time()
604 frf_condition = np.linalg.cond(frf)
605 self.log(f"Computed FRF Condition Number in {time.time() - cond_time:0.2f} seconds")
606 else:
607 frf = None
608 frf_condition = None
609 if self.spectral_processing_parameters.compute_coherence:
610 coh_time = time.time()
611 if gffpinv is None:
612 gffpinv = np.linalg.pinv(
613 self.reference_spectral_matrix, rcond=1e-12, hermitian=True
614 )
615 coherence = (
616 np.einsum(
617 "fij,fjk,fik->fi",
618 self.response_reference_spectral_matrix,
619 gffpinv,
620 self.response_reference_spectral_matrix.conj(),
621 )
622 / self.response_diagonal_matrix
623 ).real
624 self.log(f"Computed Coherence in {time.time() - coh_time:0.2f} seconds")
625 else:
626 coherence = None
627 if self.spectral_processing_parameters.compute_cpsd:
628 cpsd_time = time.time()
629 reference_spectral_matrix = self.reference_spectral_matrix.copy()
630 response_spectral_matrix = self.response_spectral_matrix.copy()
631 # Normalize
632 response_spectral_matrix *= (
633 # Window correction was done in the data collector
634 self.spectral_processing_parameters.frequency_spacing
635 / self.spectral_processing_parameters.sample_rate**2
636 )
637 response_spectral_matrix[1:-1] *= 2
638 reference_spectral_matrix *= (
639 # Window correction was done in the data collector
640 self.spectral_processing_parameters.frequency_spacing
641 / self.spectral_processing_parameters.sample_rate**2
642 )
643 reference_spectral_matrix[1:-1] *= 2
644 self.log(f"Computed CPSDs in {time.time() - cpsd_time:0.2f} seconds")
645 elif self.spectral_processing_parameters.compute_apsd:
646 apsd_time = time.time()
647 reference_spectral_matrix = self.reference_diagonal_matrix.copy()
648 response_spectral_matrix = self.response_diagonal_matrix.copy()
649 # Normalize
650 response_spectral_matrix *= (
651 # Window correction was done in the data collector
652 self.spectral_processing_parameters.frequency_spacing
653 / self.spectral_processing_parameters.sample_rate**2
654 )
655 response_spectral_matrix[1:-1] *= 2
656 reference_spectral_matrix *= (
657 # Window correction was done in the data collector
658 self.spectral_processing_parameters.frequency_spacing
659 / self.spectral_processing_parameters.sample_rate**2
660 )
661 reference_spectral_matrix[1:-1] *= 2
662 self.log(f"Computed APSDs in {time.time() - apsd_time:0.2f} seconds")
663 else:
664 response_spectral_matrix = None
665 reference_spectral_matrix = None
666 frequencies = (
667 np.arange(self.spectral_processing_parameters.num_frequency_lines)
668 * self.spectral_processing_parameters.frequency_spacing
669 )
670 self.log("Sending Updated Spectral Data")
671 self.data_out_queue.put(
672 (
673 frames,
674 frequencies,
675 frf,
676 coherence,
677 response_spectral_matrix,
678 reference_spectral_matrix,
679 frf_condition,
680 )
681 )
682 # Keep running
683 self.command_queue.put(
684 self.process_name,
685 (SpectralProcessingCommands.RUN_SPECTRAL_PROCESSING, None),
686 )
688 def clear_spectral_processing(self, data): # pylint: disable=unused-argument
689 """Clears all data in the buffer so the FRF starts fresh from new data
691 Parameters
692 ----------
693 data : Ignored
694 This parameter is not used by the function but must be present
695 due to the calling signature of functions called through the
696 ``command_map``
698 """
699 self.frames_computed = 0
700 self.response_spectral_matrix = None
701 self.reference_spectral_matrix = None
702 self.response_reference_spectral_matrix = None
703 if self.spectral_processing_parameters.averaging_type == AveragingTypes.LINEAR:
704 self.response_fft[:] = np.nan
705 self.reference_fft[:] = np.nan
706 else:
707 self.response_fft = None
708 self.reference_fft = None
710 def stop_spectral_processing(self, data):
711 """Stops computing FRFs from time data.
713 Parameters
714 ----------
715 data : Ignored
716 This parameter is not used by the function but must be present
717 due to the calling signature of functions called through the
718 ``command_map``
720 """
721 time.sleep(WAIT_TIME)
722 flushed_data = self.command_queue.flush(self.process_name)
723 # Put back any quit message that may have been pulled off
724 for message, data in flushed_data:
725 if message == GlobalCommands.QUIT:
726 self.command_queue.put(self.process_name, (message, data))
727 flush_queue(self.data_out_queue)
728 self.environment_command_queue.put(
729 self.process_name, (SpectralProcessingCommands.SHUTDOWN_ACHIEVED, None)
730 )
733def spectral_processing_process(
734 environment_name: str,
735 command_queue: VerboseMessageQueue,
736 data_in_queue: mp.queues.Queue,
737 data_out_queue: mp.queues.Queue,
738 environment_command_queue: VerboseMessageQueue,
739 gui_update_queue: mp.queues.Queue,
740 log_file_queue: mp.queues.Queue,
741 process_name=None,
742):
743 """Function passed to multiprocessing as the FRF computation process
745 This process creates the ``FRFComputationProcess`` object and calls the
746 ``run`` function.
749 Parameters
750 ----------
751 environment_name : str :
752 Name of the environment that this subprocess belongs to.
753 command_queue : VerboseMessageQueue :
754 The queue containing instructions for the FRF process
755 data_for_frf_queue : mp.queues.Queue :
756 Queue containing input data for the FRF computation
757 updated_frf_queue : mp.queues.Queue :
758 Queue where frf process will put computed frfs
759 gui_update_queue : mp.queues.Queue :
760 Queue for gui updates
761 log_file_queue : mp.queues.Queue :
762 Queue for writing to the log file
764 """
766 spectral_processing_instance = SpectralProcessingProcess(
767 (
768 environment_name + " Spectral Processing Computation"
769 if process_name is None
770 else process_name
771 ),
772 command_queue,
773 data_in_queue,
774 data_out_queue,
775 environment_command_queue,
776 gui_update_queue,
777 log_file_queue,
778 environment_name,
779 )
780 spectral_processing_instance.run()