summaryrefslogtreecommitdiffstats
path: root/dpd/src
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2017-12-29 08:32:58 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2017-12-29 08:32:58 +0100
commitbc25ed90fb34398f9fd3066b7237d1306628b16f (patch)
treede9c96d45728e96b6157e1554503e5d90f229495 /dpd/src
parent740d4815fda8737cc7f021999d30aeacd851b032 (diff)
downloaddabmod-bc25ed90fb34398f9fd3066b7237d1306628b16f.tar.gz
dabmod-bc25ed90fb34398f9fd3066b7237d1306628b16f.tar.bz2
dabmod-bc25ed90fb34398f9fd3066b7237d1306628b16f.zip
Transform Const into a global config structure, avoid depending on logging for plot location
Diffstat (limited to 'dpd/src')
-rw-r--r--dpd/src/Adapt.py14
-rw-r--r--dpd/src/Dab_Util.py29
-rw-r--r--dpd/src/ExtractStatistic.py10
-rw-r--r--dpd/src/GlobalConfig.py (renamed from dpd/src/Const.py)35
-rw-r--r--dpd/src/MER.py9
-rw-r--r--dpd/src/Measure.py5
-rw-r--r--dpd/src/Measure_Shoulders.py10
-rw-r--r--dpd/src/Model_AM.py9
-rw-r--r--dpd/src/Model_Lut.py6
-rw-r--r--dpd/src/Model_PM.py9
-rw-r--r--dpd/src/Model_Poly.py7
-rw-r--r--dpd/src/RX_Agc.py9
-rw-r--r--dpd/src/Symbol_align.py10
-rw-r--r--dpd/src/TX_Agc.py6
-rw-r--r--dpd/src/phase_align.py9
-rwxr-xr-xdpd/src/subsample_align.py12
16 files changed, 57 insertions, 132 deletions
diff --git a/dpd/src/Adapt.py b/dpd/src/Adapt.py
index 939e50e..153442b 100644
--- a/dpd/src/Adapt.py
+++ b/dpd/src/Adapt.py
@@ -16,11 +16,6 @@ import os
import datetime
import pickle
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
LUT_LEN = 32
FORMAT_POLY = 1
FORMAT_LUT = 2
@@ -58,8 +53,9 @@ class Adapt:
ZMQ remote control.
"""
- def __init__(self, port, coef_path):
+ def __init__(self, config, port, coef_path):
logging.debug("Instantiate Adapt object")
+ self.c = config
self.port = port
self.coef_path = coef_path
self.host = "localhost"
@@ -229,10 +225,10 @@ class Adapt:
"""Backup current settings to a file"""
dt = datetime.datetime.now().isoformat()
if path is None:
- if logging_path is not None:
- path = logging_path + "/" + dt + "_adapt.pkl"
+ if self.c.plot_location is not None:
+ path = self.c.plot_location + "/" + dt + "_adapt.pkl"
else:
- raise Exception("Cannot dump Adapt without either logging_path or path set")
+ raise Exception("Cannot dump Adapt without either plot_location or path set")
d = {
"txgain": self.get_txgain(),
"rxgain": self.get_rxgain(),
diff --git a/dpd/src/Dab_Util.py b/dpd/src/Dab_Util.py
index 56c9503..bc89a39 100644
--- a/dpd/src/Dab_Util.py
+++ b/dpd/src/Dab_Util.py
@@ -8,12 +8,6 @@
import datetime
import os
import logging
-
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
import matplotlib
@@ -36,10 +30,11 @@ class Dab_Util:
complex IQ samples of a DAB signal
"""
- def __init__(self, sample_rate, plot=False):
+ def __init__(self, config, sample_rate, plot=False):
"""
:param sample_rate: sample rate [sample/sec] to use for calculations
"""
+ self.c = config
self.sample_rate = sample_rate
self.dab_bandwidth = 1536000 # Bandwidth of a dab signal
self.frame_ms = 96 # Duration of a Dab frame
@@ -56,9 +51,9 @@ class Dab_Util:
off = sig_rec.shape[0]
c = np.abs(signal.correlate(sig_orig, sig_rec))
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- corr_path = (logging_path + "/" + dt + "_tx_rx_corr.svg")
+ corr_path = self.c.plot_location + "/" + dt + "_tx_rx_corr.png"
plt.plot(c, label="corr")
plt.legend()
plt.savefig(corr_path)
@@ -110,9 +105,9 @@ class Dab_Util:
Returns an aligned version of sig_tx and sig_rx by cropping and subsample alignment
"""
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_sync_raw.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_sync_raw.png"
fig, axs = plt.subplots(2)
axs[0].plot(np.abs(sig_tx[:128]), label="TX Frame")
@@ -154,9 +149,9 @@ class Dab_Util:
sig_tx = sig_tx[:-1]
sig_rx = sig_rx[:-1]
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_sync_sample_aligned.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_sync_sample_aligned.png"
fig, axs = plt.subplots(2)
axs[0].plot(np.abs(sig_tx[:128]), label="TX Frame")
@@ -178,9 +173,9 @@ class Dab_Util:
sig_rx = sa.subsample_align(sig_rx, sig_tx)
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_sync_subsample_aligned.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_sync_subsample_aligned.png"
fig, axs = plt.subplots(2)
axs[0].plot(np.abs(sig_tx[:128]), label="TX Frame")
@@ -202,9 +197,9 @@ class Dab_Util:
sig_rx = pa.phase_align(sig_rx, sig_tx)
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_sync_phase_aligned.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_sync_phase_aligned.png"
fig, axs = plt.subplots(2)
axs[0].plot(np.abs(sig_tx[:128]), label="TX Frame")
diff --git a/dpd/src/ExtractStatistic.py b/dpd/src/ExtractStatistic.py
index e917909..639513a 100644
--- a/dpd/src/ExtractStatistic.py
+++ b/dpd/src/ExtractStatistic.py
@@ -8,14 +8,9 @@
import numpy as np
import matplotlib.pyplot as plt
-
import datetime
import os
import logging
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
def _check_input_extract(tx_dpd, rx_received):
@@ -66,10 +61,9 @@ class ExtractStatistic:
self.plot = c.ES_plot
def _plot_and_log(self, tx_values, rx_values, phase_diffs_values, phase_diffs_values_lists):
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
-
+ if self.plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_ExtractStatistic.png"
+ fig_path = self.c.plot_location + "/" + dt + "_ExtractStatistic.png"
sub_rows = 3
sub_cols = 1
fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6))
diff --git a/dpd/src/Const.py b/dpd/src/GlobalConfig.py
index d80cfac..684a881 100644
--- a/dpd/src/Const.py
+++ b/dpd/src/GlobalConfig.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# DPD Computation Engine, constants.
+# DPD Computation Engine, constants and global configuration
#
# Source for DAB standard: etsi_EN_300_401_v010401p p145
#
@@ -9,19 +9,20 @@
import numpy as np
+class GlobalConfig:
+ def __init__(self, cli_args, plot_location):
+ self.sample_rate = cli_args.sample_rate
+ assert self.sample_rate == 8192000 # By now only constants for 8192000
-class Const:
- def __init__(self, sample_rate, target_median, plot):
- assert sample_rate == 8192000 # By now only constants for 8192000
- self.sample_rate = sample_rate
+ self.plot_location = plot_location
# DAB frame
# Time domain
- self.T_F = sample_rate / 2048000 * 196608 # Transmission frame duration
- self.T_NULL = sample_rate / 2048000 * 2656 # Null symbol duration
- self.T_S = sample_rate / 2048000 * 2552 # Duration of OFDM symbols of indices l = 1, 2, 3,... L;
- self.T_U = sample_rate / 2048000 * 2048 # Inverse of carrier spacing
- self.T_C = sample_rate / 2048000 * 504 # Duration of cyclic prefix
+ self.T_F = self.sample_rate / 2048000 * 196608 # Transmission frame duration
+ self.T_NULL = self.sample_rate / 2048000 * 2656 # Null symbol duration
+ self.T_S = self.sample_rate / 2048000 * 2552 # Duration of OFDM symbols of indices l = 1, 2, 3,... L;
+ self.T_U = self.sample_rate / 2048000 * 2048 # Inverse of carrier spacing
+ self.T_C = self.sample_rate / 2048000 * 504 # Duration of cyclic prefix
# Frequency Domain
# example: np.delete(fft[3328:4865], 768)
@@ -34,10 +35,10 @@ class Const:
# time per sample = 1 / sample_rate
# frequency per bin = 1kHz
# phase difference per sample offset = delta_t * 2 * pi * delta_freq
- self.phase_offset_per_sample = 1. / sample_rate * 2 * np.pi * 1000
+ self.phase_offset_per_sample = 1. / self.sample_rate * 2 * np.pi * 1000
# Constants for ExtractStatistic
- self.ES_plot = plot
+ self.ES_plot = cli_args.plot
self.ES_start = 0.0
self.ES_end = 1.0
self.ES_n_bins = 64 # Number of bins between ES_start and ES_end
@@ -45,7 +46,7 @@ class Const:
# Constants for Measure_Shoulder
self.MS_enable = False
- self.MS_plot = plot
+ self.MS_plot = cli_args.plot
meas_offset = 976 # Offset from center frequency to measure shoulder [kHz]
meas_width = 100 # Size of frequency delta to measure shoulder [kHz]
@@ -63,10 +64,10 @@ class Const:
self.MS_n_proc = 4
# Constants for MER
- self.MER_plot = plot
+ self.MER_plot = cli_args.plot
# Constants for Model
- self.MDL_plot = True or plot # Override default
+ self.MDL_plot = cli_args.plot
# Constants for Model_PM
# Set all phase offsets to zero for TX amplitude < MPM_tx_min
@@ -74,13 +75,13 @@ class Const:
# Constants for TX_Agc
self.TAGC_max_txgain = 89 # USRP B200 specific
- self.TAGC_tx_median_target = target_median
+ self.TAGC_tx_median_target = cli_args.target_median
self.TAGC_tx_median_max = self.TAGC_tx_median_target * 1.4
self.TAGC_tx_median_min = self.TAGC_tx_median_target / 1.4
# Constants for RX_AGC
self.RAGC_min_rxgain = 25 # USRP B200 specific
- self.RAGC_rx_median_target = target_median
+ self.RAGC_rx_median_target = cli_args.target_median
# The MIT License (MIT)
#
diff --git a/dpd/src/MER.py b/dpd/src/MER.py
index a4c3591..693058d 100644
--- a/dpd/src/MER.py
+++ b/dpd/src/MER.py
@@ -8,11 +8,6 @@
import datetime
import os
import logging
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except:
- logging_path = None
-
import numpy as np
import matplotlib
matplotlib.use('agg')
@@ -76,9 +71,9 @@ class MER:
spectrum = self._calc_spectrum(tx)
- if self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_MER" + debug_name + ".svg"
+ fig_path = self.c.plot_location + "/" + dt + "_MER" + debug_name + ".png"
else:
fig_path = None
diff --git a/dpd/src/Measure.py b/dpd/src/Measure.py
index e4333d9..b7423c6 100644
--- a/dpd/src/Measure.py
+++ b/dpd/src/Measure.py
@@ -15,8 +15,9 @@ import logging
class Measure:
"""Collect Measurement from DabMod"""
- def __init__(self, samplerate, port, num_samples_to_request):
+ def __init__(self, config, samplerate, port, num_samples_to_request):
logging.info("Instantiate Measure object")
+ self.c = config
self.samplerate = samplerate
self.sizeof_sample = 8 # complex floats
self.port = port
@@ -104,7 +105,7 @@ class Measure:
rx_median = np.median(np.abs(rxframe))
rxframe = rxframe / rx_median * np.median(np.abs(txframe))
- du = DU.Dab_Util(self.samplerate)
+ du = DU.Dab_Util(self.c, self.samplerate)
txframe_aligned, rxframe_aligned = du.subsample_align(txframe, rxframe)
logging.info(
diff --git a/dpd/src/Measure_Shoulders.py b/dpd/src/Measure_Shoulders.py
index 2249ac6..fd90050 100644
--- a/dpd/src/Measure_Shoulders.py
+++ b/dpd/src/Measure_Shoulders.py
@@ -9,12 +9,6 @@ import datetime
import os
import logging
import multiprocessing
-
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
import matplotlib.pyplot as plt
@@ -82,11 +76,11 @@ class Measure_Shoulders:
self.plot = c.MS_plot
def _plot(self, signal):
- if logging_path is None:
+ if self.c.plot_location is None:
return
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_sync_subsample_aligned.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_sync_subsample_aligned.png"
fft = calc_fft_db(signal, 100, self.c)
peak, idxs_peak = _calc_peak(fft, self.c)
diff --git a/dpd/src/Model_AM.py b/dpd/src/Model_AM.py
index 596ca4a..9800d83 100644
--- a/dpd/src/Model_AM.py
+++ b/dpd/src/Model_AM.py
@@ -8,11 +8,6 @@
import datetime
import os
import logging
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
import matplotlib.pyplot as plt
@@ -57,12 +52,12 @@ class Model_AM:
self.plot = plot
def _plot(self, tx_dpd, rx_received, coefs_am, coefs_am_new):
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
tx_range, rx_est = calc_line(coefs_am, 0, 0.6)
tx_range_new, rx_est_new = calc_line(coefs_am_new, 0, 0.6)
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_Model_AM.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_Model_AM.png"
sub_rows = 1
sub_cols = 1
fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6))
diff --git a/dpd/src/Model_Lut.py b/dpd/src/Model_Lut.py
index b349433..e70fdb0 100644
--- a/dpd/src/Model_Lut.py
+++ b/dpd/src/Model_Lut.py
@@ -7,14 +7,8 @@
import os
import logging
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
-
class Lut:
"""Implements a model that calculates lookup table coefficients"""
diff --git a/dpd/src/Model_PM.py b/dpd/src/Model_PM.py
index e721d1a..3aafea0 100644
--- a/dpd/src/Model_PM.py
+++ b/dpd/src/Model_PM.py
@@ -8,11 +8,6 @@
import datetime
import os
import logging
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
import matplotlib.pyplot as plt
@@ -43,12 +38,12 @@ class Model_PM:
self.plot = plot
def _plot(self, tx_dpd, phase_diff, coefs_pm, coefs_pm_new):
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
tx_range, phase_diff_est = self.calc_line(coefs_pm, 0, 0.6)
tx_range_new, phase_diff_est_new = self.calc_line(coefs_pm_new, 0, 0.6)
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_Model_PM.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_Model_PM.png"
sub_rows = 1
sub_cols = 1
fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6))
diff --git a/dpd/src/Model_Poly.py b/dpd/src/Model_Poly.py
index 1cf8ecd..3ec717b 100644
--- a/dpd/src/Model_Poly.py
+++ b/dpd/src/Model_Poly.py
@@ -7,11 +7,6 @@
import os
import logging
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
import src.Model_AM as Model_AM
@@ -56,8 +51,6 @@ class Poly:
self.model_am = Model_AM.Model_AM(c, plot=self.plot)
self.model_pm = Model_PM.Model_PM(c, plot=self.plot)
- self.plot = c.MDL_plot
-
def reset_coefs(self):
self.coefs_am = np.zeros(5, dtype=np.float32)
self.coefs_am[0] = 1
diff --git a/dpd/src/RX_Agc.py b/dpd/src/RX_Agc.py
index 7b5e0b6..f778dee 100644
--- a/dpd/src/RX_Agc.py
+++ b/dpd/src/RX_Agc.py
@@ -9,11 +9,6 @@ import datetime
import os
import logging
import time
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
import matplotlib
matplotlib.use('agg')
@@ -73,14 +68,14 @@ class Agc:
def plot_estimates(self):
"""Plots the estimate of for Max, Median, Mean for different
number of samples."""
- if logging_path is None:
+ if self.c.plot_location is None:
return
self.adapt.set_rxgain(self.min_rxgain)
time.sleep(1)
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_agc.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_agc.png"
fig, axs = plt.subplots(2, 2, figsize=(3*6,1*6))
axs = axs.ravel()
diff --git a/dpd/src/Symbol_align.py b/dpd/src/Symbol_align.py
index f2802ee..2a17a65 100644
--- a/dpd/src/Symbol_align.py
+++ b/dpd/src/Symbol_align.py
@@ -8,12 +8,6 @@
import datetime
import os
import logging
-
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except:
- logging_path = None
-
import numpy as np
import scipy
import matplotlib
@@ -75,9 +69,9 @@ class Symbol_align:
offset = peaks[np.argmin([tx_product_avg[peak] for peak in peaks])]
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot and logging_path is not None:
+ if self.plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_Symbol_align.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_Symbol_align.png"
fig = plt.figure(figsize=(9, 9))
diff --git a/dpd/src/TX_Agc.py b/dpd/src/TX_Agc.py
index 7555450..309193d 100644
--- a/dpd/src/TX_Agc.py
+++ b/dpd/src/TX_Agc.py
@@ -9,12 +9,6 @@ import datetime
import os
import logging
import time
-
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
import matplotlib
diff --git a/dpd/src/phase_align.py b/dpd/src/phase_align.py
index 21a210c..8654333 100644
--- a/dpd/src/phase_align.py
+++ b/dpd/src/phase_align.py
@@ -7,11 +7,6 @@
import datetime
import os
import logging
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
import matplotlib.pyplot as plt
@@ -27,9 +22,9 @@ def phase_align(sig, ref_sig, plot=False):
real_diffs = np.cos(angle_diff)
imag_diffs = np.sin(angle_diff)
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and plot and logging_path is not None:
+ if plot and self.c.plot_location is not None:
dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_phase_align.svg"
+ fig_path = self.c.plot_location + "/" + dt + "_phase_align.png"
plt.subplot(511)
plt.hist(angle_diff, bins=60, label="Angle Diff")
diff --git a/dpd/src/subsample_align.py b/dpd/src/subsample_align.py
index a5e9f8c..20ae56b 100755
--- a/dpd/src/subsample_align.py
+++ b/dpd/src/subsample_align.py
@@ -7,16 +7,10 @@
import datetime
import logging
import os
-try:
- logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename)
-except AttributeError:
- logging_path = None
-
import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
-
def gen_omega(length):
if (length % 2) == 1:
raise ValueError("Needs an even length array.")
@@ -34,7 +28,7 @@ def gen_omega(length):
return omega
-def subsample_align(sig, ref_sig, plot=False):
+def subsample_align(sig, ref_sig, plot_location=None):
"""Do subsample alignment for sig relative to the reference signal
ref_sig. The delay between the two must be less than sample
@@ -74,13 +68,13 @@ def subsample_align(sig, ref_sig, plot=False):
if optim_result.success:
best_tau = optim_result.x
- if plot and logging_path is not None:
+ if plot_location is not None:
corr = np.vectorize(correlate_for_delay)
ixs = np.linspace(-1, 1, 100)
taus = corr(ixs)
dt = datetime.datetime.now().isoformat()
- tau_path = (logging_path + "/" + dt + "_tau.svg")
+ tau_path = (plot_location + "/" + dt + "_tau.png")
plt.plot(ixs, taus)
plt.title("Subsample correlation, minimum is best: {}".format(best_tau))
plt.savefig(tau_path)