From 44f940e85eadbac0558499e16a7bba14a0ebae89 Mon Sep 17 00:00:00 2001 From: billetd Date: Sat, 7 Feb 2026 13:44:51 +0100 Subject: [PATCH 1/8] Initial function --- pydarn/utils/detrend.py | 178 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 pydarn/utils/detrend.py diff --git a/pydarn/utils/detrend.py b/pydarn/utils/detrend.py new file mode 100644 index 00000000..8271a9f9 --- /dev/null +++ b/pydarn/utils/detrend.py @@ -0,0 +1,178 @@ +import copy +import pydarn +import datetime as dt +import numpy as np +from typing import List + +def detrend_running_mean(timeseries: List[float], + half_k: int, + n_times: int=None + ): + """ + Detrend a given timeseries with a running mean low-pass filter. + + Parameters + ----------- + timeseries: List[float] + List timeseries data to detrend + half_k: int + Half the window size for the running mean window + n_times: int + Size of the timeseries list. + Default: None, and the code will work it out + Giving the size is faster for loops if you already have it. + + Returns + ------- + detrended: List[float] + Detrended timeseries data + """ + + if n_times is None: + n_times = len(timeseries) + + detrended = [] + for i in range(n_times): + # If the original value is None, keep it None and move on + if timeseries[i] is None: + detrended.append(None) + continue + + # Define the window boundaries (clamped to edges - beware of edge effects) + start = max(0, i - half_k) + end = min(n_times, i + half_k + 1) + + # Extract window and filter out None values + window_values = [v for v in timeseries[start:end] if v is not None] + + if window_values: + # Calculate running mean and subtract + running_mean = sum(window_values) / len(window_values) + detrended.append(timeseries[i] - running_mean) + else: + # Fallback if the entire window is Nones + detrended.append(0) + + return detrended + + +def detrend(fitacf_data: List[dict], + parameter: str = 'all', + window_length: int = 600, + ): + """ + Detrend a series of input fitacf records using a low-pass moving average filter. + Useful for the study of period ULF waves or generally removing background flows. + + Parameters + ----------- + fitacf_data: List[dict] + List of dictionaries where each dictionary contains a fitacf record (from pydarn.read_fitacf()) + parameter: str + The parameter to be detrended + Default: 'both' (Velocity and SNR) + Options: 'both', 'v', 'p_l' + window_length: int + Length of the detrending low-pass filter in seconds + + Returns + ------- + fitacf_data_detrended: List[dict] + Copy of input dmap data with detrended data substituted + """ + + # Make a copy of the fitacf for the detrended data to be substituted into + fitacf_data_detrended = copy.deepcopy(fitacf_data) + + # Max beams and range gates for this data + no_beams = pydarn.SuperDARNRadars.radars[pydarn.RadarID(fitacf_data[0]['stid'])].hardware_info.beams + no_rang = fitacf_data[0]['nrang'] + + # Grab slist and time lists for all records. "None" indicates no data for that record. + slists = [rec.get('slist') for rec in fitacf_data] + rec_times = [dt.datetime(rec.get('time.yr'), + rec.get('time.mo'), + rec.get('time.dy'), + rec.get('time.hr'), + rec.get('time.mt'), + rec.get('time.sc'), + rec.get('time.us')) + for rec in fitacf_data] + + # Handle parameter choice(s) + params = [] + if parameter == 'both' or parameter == 'v': + params.append('v') + if parameter == 'both' or parameter == 'p_l': + params.append('p_l') + + # Grab the data to be detrended + values = [] + for param in params: + values.append([rec.get(param) for rec in fitacf_data]) + + # Iterate over beams + for bmnum in range(0, no_beams): + + # Get all the slists and times for each record on this beam + this_beam_indexes = [i for i, d in enumerate(fitacf_data) if d.get("bmnum") == bmnum] + this_beam_slists = [slists[i]for i in this_beam_indexes] + this_beam_times = [rec_times[i]for i in this_beam_indexes] + + # Calculate the interval between samples in seconds + time_delta = (this_beam_times[1] - this_beam_times[0]).total_seconds() + + # Convert window length from seconds to number of records (k) + # We use an odd number for k to make centering cleaner + k = int(window_length / time_delta) + if k % 2 == 0: k += 1 + half_k = k // 2 + n_times = len(this_beam_times) + + # Get the velocities and/or the powers for this beam + this_beam_values = [] + for value in values: + this_beam_values.append([s for rec, s in zip(fitacf_data, value) if rec.get('bmnum') == bmnum]) + + # Iterate over range gates + all_range_detrends = [] + for rangnum in range(0, no_rang): + indexes = [ + (np.where(sublist == rangnum)[0][0] if rangnum in sublist else None) + if sublist is not None else None + for sublist in this_beam_slists + ] + + detrended_timeseries = [] + for this_beam_value in this_beam_values: + timeseries = [ + sublist[idx] if (sublist is not None and idx is not None) else None + for sublist, idx in zip(this_beam_value, indexes) + ] + + # This bit is just a sinwave substitution to test that the detrending code is working + # timeseries = [np.sin(x) for x in np.linspace(0, 6*np.pi, num=len(this_beam_times))] + + # Running mean detrend + detrended = detrend_running_mean(timeseries, half_k, n_times) + + # Collect detrended timeseries for all parameters + detrended_timeseries.append(detrended) + + # # Testing the detrend + # fig, ax, = plt.subplots() + # ax.plot(this_beam_times, detrended, 'b-') + # ax.plot(this_beam_times, timeseries, 'r') + # fig.autofmt_xdate() + # plt.show() + + # Collect detrended timeseries for all beams List[List] + all_range_detrends.append(detrended_timeseries) + + # Insert values into copied dmap List[dict] + for beam_order, fitacf_index in enumerate(this_beam_indexes): + for param_no, param in enumerate(params): + this_beam_detrended = [sublist[param_no][beam_order] for sublist in all_range_detrends] + fitacf_data_detrended[fitacf_index][param] = np.array([x for x in this_beam_detrended if x is not None]) + + return fitacf_data_detrended From bdd304d8566caeb756f1ac13fa49b63bda19b824 Mon Sep 17 00:00:00 2001 From: billetd Date: Sat, 7 Feb 2026 15:15:25 +0100 Subject: [PATCH 2/8] Class-ified things. Removed test code. --- pydarn/utils/detrend.py | 329 +++++++++++++++++++++------------------- 1 file changed, 169 insertions(+), 160 deletions(-) diff --git a/pydarn/utils/detrend.py b/pydarn/utils/detrend.py index 8271a9f9..4e21eb8e 100644 --- a/pydarn/utils/detrend.py +++ b/pydarn/utils/detrend.py @@ -4,175 +4,184 @@ import numpy as np from typing import List -def detrend_running_mean(timeseries: List[float], - half_k: int, - n_times: int=None - ): - """ - Detrend a given timeseries with a running mean low-pass filter. - - Parameters - ----------- - timeseries: List[float] - List timeseries data to detrend - half_k: int - Half the window size for the running mean window - n_times: int - Size of the timeseries list. - Default: None, and the code will work it out - Giving the size is faster for loops if you already have it. - - Returns - ------- - detrended: List[float] - Detrended timeseries data - """ - - if n_times is None: - n_times = len(timeseries) - - detrended = [] - for i in range(n_times): - # If the original value is None, keep it None and move on - if timeseries[i] is None: - detrended.append(None) - continue - - # Define the window boundaries (clamped to edges - beware of edge effects) - start = max(0, i - half_k) - end = min(n_times, i + half_k + 1) - # Extract window and filter out None values - window_values = [v for v in timeseries[start:end] if v is not None] - - if window_values: - # Calculate running mean and subtract - running_mean = sum(window_values) / len(window_values) - detrended.append(timeseries[i] - running_mean) - else: - # Fallback if the entire window is Nones - detrended.append(0) - - return detrended - - -def detrend(fitacf_data: List[dict], - parameter: str = 'all', - window_length: int = 600, - ): +class Detrend: """ - Detrend a series of input fitacf records using a low-pass moving average filter. - Useful for the study of period ULF waves or generally removing background flows. - - Parameters - ----------- - fitacf_data: List[dict] - List of dictionaries where each dictionary contains a fitacf record (from pydarn.read_fitacf()) - parameter: str - The parameter to be detrended - Default: 'both' (Velocity and SNR) - Options: 'both', 'v', 'p_l' - window_length: int - Length of the detrending low-pass filter in seconds - - Returns + Methods ------- - fitacf_data_detrended: List[dict] - Copy of input dmap data with detrended data substituted + detrend_running_mean + detrend_fitacf """ - # Make a copy of the fitacf for the detrended data to be substituted into - fitacf_data_detrended = copy.deepcopy(fitacf_data) - - # Max beams and range gates for this data - no_beams = pydarn.SuperDARNRadars.radars[pydarn.RadarID(fitacf_data[0]['stid'])].hardware_info.beams - no_rang = fitacf_data[0]['nrang'] - - # Grab slist and time lists for all records. "None" indicates no data for that record. - slists = [rec.get('slist') for rec in fitacf_data] - rec_times = [dt.datetime(rec.get('time.yr'), - rec.get('time.mo'), - rec.get('time.dy'), - rec.get('time.hr'), - rec.get('time.mt'), - rec.get('time.sc'), - rec.get('time.us')) - for rec in fitacf_data] - - # Handle parameter choice(s) - params = [] - if parameter == 'both' or parameter == 'v': - params.append('v') - if parameter == 'both' or parameter == 'p_l': - params.append('p_l') - - # Grab the data to be detrended - values = [] - for param in params: - values.append([rec.get(param) for rec in fitacf_data]) - - # Iterate over beams - for bmnum in range(0, no_beams): - - # Get all the slists and times for each record on this beam - this_beam_indexes = [i for i, d in enumerate(fitacf_data) if d.get("bmnum") == bmnum] - this_beam_slists = [slists[i]for i in this_beam_indexes] - this_beam_times = [rec_times[i]for i in this_beam_indexes] - - # Calculate the interval between samples in seconds - time_delta = (this_beam_times[1] - this_beam_times[0]).total_seconds() - - # Convert window length from seconds to number of records (k) - # We use an odd number for k to make centering cleaner - k = int(window_length / time_delta) - if k % 2 == 0: k += 1 - half_k = k // 2 - n_times = len(this_beam_times) - - # Get the velocities and/or the powers for this beam - this_beam_values = [] - for value in values: - this_beam_values.append([s for rec, s in zip(fitacf_data, value) if rec.get('bmnum') == bmnum]) - - # Iterate over range gates - all_range_detrends = [] - for rangnum in range(0, no_rang): - indexes = [ - (np.where(sublist == rangnum)[0][0] if rangnum in sublist else None) - if sublist is not None else None - for sublist in this_beam_slists - ] - - detrended_timeseries = [] - for this_beam_value in this_beam_values: - timeseries = [ - sublist[idx] if (sublist is not None and idx is not None) else None - for sublist, idx in zip(this_beam_value, indexes) + def __str__(self): + return "This class is static class that provides"\ + " the following methods: \n"\ + " - detrend_running_mean()\n"\ + " - dentrend()\n"\ + + @classmethod + def detrend_running_mean(cls, + timeseries: List[float], + half_k: int, + n_times: int=None + ): + """ + Detrend a given timeseries with a running mean low-pass filter. + + Parameters + ----------- + timeseries: List[float] + List timeseries data to detrend + half_k: int + Half the window size for the running mean window + n_times: int + Size of the timeseries list. + Default: None, and the code will work it out + Giving the size is faster for loops if you already have it. + + Returns + ------- + detrended: List[float] + Detrended timeseries data + """ + + if n_times is None: + n_times = len(timeseries) + + detrended = [] + for i in range(n_times): + # If the original value is None, keep it None and move on + if timeseries[i] is None: + detrended.append(None) + continue + + # Define the window boundaries (clamped to edges - beware of edge effects) + start = max(0, i - half_k) + end = min(n_times, i + half_k + 1) + + # Extract window and filter out None values + window_values = [v for v in timeseries[start:end] if v is not None] + + if window_values: + # Calculate running mean and subtract + running_mean = sum(window_values) / len(window_values) + detrended.append(timeseries[i] - running_mean) + else: + # Fallback if the entire window is Nones + detrended.append(0) + + return detrended + + + @classmethod + def detrend_fitacf(cls, + fitacf_data: List[dict], + parameter: str = 'all', + window_length: int = 600, + ): + """ + Detrend a series of input fitacf records using a low-pass moving average filter. + Useful for the study of period ULF waves or generally removing background flows. + + Parameters + ----------- + fitacf_data: List[dict] + List of dictionaries where each dictionary contains a fitacf record (from pydarn.read_fitacf()) + parameter: str + The parameter to be detrended + Default: 'both' (Velocity and SNR) + Options: 'both', 'v', 'p_l' + window_length: int + Length of the detrending low-pass filter in seconds + + Returns + ------- + fitacf_data_detrended: List[dict] + Copy of input dmap data with detrended data substituted + """ + + # Make a copy of the fitacf for the detrended data to be substituted into + fitacf_data_detrended = copy.deepcopy(fitacf_data) + + # Max beams and range gates for this data + no_beams = pydarn.SuperDARNRadars.radars[pydarn.RadarID(fitacf_data[0]['stid'])].hardware_info.beams + no_rang = fitacf_data[0]['nrang'] + + # Grab slist and time lists for all records. "None" indicates no data for that record. + slists = [rec.get('slist') for rec in fitacf_data] + rec_times = [dt.datetime(rec.get('time.yr'), + rec.get('time.mo'), + rec.get('time.dy'), + rec.get('time.hr'), + rec.get('time.mt'), + rec.get('time.sc'), + rec.get('time.us')) + for rec in fitacf_data] + + # Handle parameter choice(s) + params = [] + if parameter == 'both' or parameter == 'v': + params.append('v') + if parameter == 'both' or parameter == 'p_l': + params.append('p_l') + + # Grab the data to be detrended + values = [] + for param in params: + values.append([rec.get(param) for rec in fitacf_data]) + + # Iterate over beams + for bmnum in range(0, no_beams): + + # Get all the slists and times for each record on this beam + this_beam_indexes = [i for i, d in enumerate(fitacf_data) if d.get("bmnum") == bmnum] + this_beam_slists = [slists[i]for i in this_beam_indexes] + this_beam_times = [rec_times[i]for i in this_beam_indexes] + + # Calculate the interval between samples in seconds + time_delta = (this_beam_times[1] - this_beam_times[0]).total_seconds() + + # Convert window length from seconds to number of records (k) + # We use an odd number for k to make centering cleaner + k = int(window_length / time_delta) + if k % 2 == 0: k += 1 + half_k = k // 2 + n_times = len(this_beam_times) + + # Get the velocities and/or the powers for this beam + this_beam_values = [] + for value in values: + this_beam_values.append([s for rec, s in zip(fitacf_data, value) if rec.get('bmnum') == bmnum]) + + # Iterate over range gates + all_range_detrends = [] + for rangnum in range(0, no_rang): + indexes = [ + (np.where(sublist == rangnum)[0][0] if rangnum in sublist else None) + if sublist is not None else None + for sublist in this_beam_slists ] - # This bit is just a sinwave substitution to test that the detrending code is working - # timeseries = [np.sin(x) for x in np.linspace(0, 6*np.pi, num=len(this_beam_times))] - - # Running mean detrend - detrended = detrend_running_mean(timeseries, half_k, n_times) + detrended_timeseries = [] + for this_beam_value in this_beam_values: + timeseries = [ + sublist[idx] if (sublist is not None and idx is not None) else None + for sublist, idx in zip(this_beam_value, indexes) + ] - # Collect detrended timeseries for all parameters - detrended_timeseries.append(detrended) + # Running mean detrend + detrended = Detrend.detrend_running_mean(timeseries, half_k, n_times) - # # Testing the detrend - # fig, ax, = plt.subplots() - # ax.plot(this_beam_times, detrended, 'b-') - # ax.plot(this_beam_times, timeseries, 'r') - # fig.autofmt_xdate() - # plt.show() + # Collect detrended timeseries for all parameters + detrended_timeseries.append(detrended) - # Collect detrended timeseries for all beams List[List] - all_range_detrends.append(detrended_timeseries) + # Collect detrended timeseries for all beams List[List] + all_range_detrends.append(detrended_timeseries) - # Insert values into copied dmap List[dict] - for beam_order, fitacf_index in enumerate(this_beam_indexes): - for param_no, param in enumerate(params): - this_beam_detrended = [sublist[param_no][beam_order] for sublist in all_range_detrends] - fitacf_data_detrended[fitacf_index][param] = np.array([x for x in this_beam_detrended if x is not None]) + # Insert values into copied dmap List[dict] + for beam_order, fitacf_index in enumerate(this_beam_indexes): + for param_no, param in enumerate(params): + this_beam_detrended = [sublist[param_no][beam_order] for sublist in all_range_detrends] + fitacf_data_detrended[fitacf_index][param] = np.array([x for x in this_beam_detrended if x is not None]) - return fitacf_data_detrended + return fitacf_data_detrended From ee03ac6066e870806e9429ebd07a4f69c430c2a7 Mon Sep 17 00:00:00 2001 From: billetd Date: Sat, 7 Feb 2026 15:34:22 +0100 Subject: [PATCH 3/8] Formatting --- pydarn/utils/detrend.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pydarn/utils/detrend.py b/pydarn/utils/detrend.py index 4e21eb8e..11015699 100644 --- a/pydarn/utils/detrend.py +++ b/pydarn/utils/detrend.py @@ -144,7 +144,8 @@ def detrend_fitacf(cls, # Convert window length from seconds to number of records (k) # We use an odd number for k to make centering cleaner k = int(window_length / time_delta) - if k % 2 == 0: k += 1 + if k % 2 == 0: + k += 1 half_k = k // 2 n_times = len(this_beam_times) From 8ddc6dc46f1f899761f45fc6997e8f10f8f85ac2 Mon Sep 17 00:00:00 2001 From: billetd Date: Sat, 7 Feb 2026 21:28:49 +0100 Subject: [PATCH 4/8] Added Savitsky-Golay filter option. --- pydarn/utils/detrend.py | 80 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/pydarn/utils/detrend.py b/pydarn/utils/detrend.py index 11015699..cab4fa85 100644 --- a/pydarn/utils/detrend.py +++ b/pydarn/utils/detrend.py @@ -2,6 +2,7 @@ import pydarn import datetime as dt import numpy as np +from scipy.signal import savgol_filter from typing import List @@ -19,6 +20,66 @@ def __str__(self): " - detrend_running_mean()\n"\ " - dentrend()\n"\ + + @classmethod + def detrend_savgol(cls, + timeseries: List[float], + half_k: int, + **kwargs + ): + """ + Detrend a given timeseries with a Savitsky-Golay filter. + + Parameters + ----------- + timeseries: List[float] + List timeseries data to detrend + half_k: int + Half the window size for the running mean window + **kwargs: + Optional inputs for detrending using scipy's `savgol_filter()` + E.g., polyorder, mode + Defaults are polyorder = 2, mode = 'interp' (edge truncation) + Returns + ------- + detrended: List[float] + Detrended timeseries data + """ + + # Convert to numpy array for processing + y = np.array(timeseries, dtype=float) + + # Map out where the Nones are + mask = np.isnan(y) + # If everything is None, return as is + if np.all(mask): + return [None] * len(timeseries) + indices = np.arange(len(y)) + + # Linearly interpolate over "None"s so the filter can work + # Will result in anomalous velocities/SNR's if data is sparse + y_interp = np.copy(y) + y_interp[mask] = np.interp(indices[mask], indices[~mask], y[~mask]) + + # Generate filter series + window_len = (half_k * 2) + 1 + if 'polyorder' not in kwargs: + polyorder = 2 + background = savgol_filter(y_interp, window_length=window_len, polyorder=polyorder, **kwargs) + + # Detrend + detrended_tmp = y_interp - background + + # Restore the original None positions + # Convert back to list and replace masked values + detrended = detrended_tmp.tolist() + for i in range(len(detrended)): + if mask[i]: + detrended[i] = None + + return detrended + + @classmethod def detrend_running_mean(cls, timeseries: List[float], @@ -78,9 +139,11 @@ def detrend_fitacf(cls, fitacf_data: List[dict], parameter: str = 'all', window_length: int = 600, + detrend_type: str='mean', + **kwargs ): """ - Detrend a series of input fitacf records using a low-pass moving average filter. + Detrend a series of input fitacf records using a low-pass filter. Useful for the study of period ULF waves or generally removing background flows. Parameters @@ -93,6 +156,14 @@ def detrend_fitacf(cls, Options: 'both', 'v', 'p_l' window_length: int Length of the detrending low-pass filter in seconds + detrend_type: str + Type of detrending to be used + Default: 'mean' - subtract a running mean of window_length + Option: 'savgol' - subtract a Savitsky-Golay filter instead + **kwargs: + Optional inputs for detrending using scipy's `savgol_filter()` + E.g., polyorder, mode + Defaults are polyorder = 2, mode = 'interp' (edge truncation) Returns ------- @@ -171,7 +242,12 @@ def detrend_fitacf(cls, ] # Running mean detrend - detrended = Detrend.detrend_running_mean(timeseries, half_k, n_times) + if detrend_type == 'mean': + detrended = Detrend.detrend_running_mean(timeseries, half_k, n_times) + elif detrend_type == 'savgol': + detrended = Detrend.detrend_savgol(timeseries, half_k, **kwargs) + else: + raise NameError('No valid detrending type specified') # Collect detrended timeseries for all parameters detrended_timeseries.append(detrended) From 475b939c2f3424f4487396aea4bcb99c784f485c Mon Sep 17 00:00:00 2001 From: billetd Date: Sat, 7 Feb 2026 21:41:48 +0100 Subject: [PATCH 5/8] Fixed docstrings. Updated polyorder handling. --- pydarn/utils/detrend.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pydarn/utils/detrend.py b/pydarn/utils/detrend.py index cab4fa85..2f80a8b2 100644 --- a/pydarn/utils/detrend.py +++ b/pydarn/utils/detrend.py @@ -10,15 +10,17 @@ class Detrend: """ Methods ------- + detrend_savgol detrend_running_mean detrend_fitacf """ def __str__(self): return "This class is static class that provides"\ - " the following methods: \n"\ - " - detrend_running_mean()\n"\ - " - dentrend()\n"\ + " the following methods: \n" \ + " - detrend_savgol()\n" \ + " - detrend_running_mean()\n"\ + " - dentrend()\n"\ @classmethod @@ -63,7 +65,9 @@ def detrend_savgol(cls, # Generate filter series window_len = (half_k * 2) + 1 - if 'polyorder' not in kwargs: + try: + polyorder = kwargs['polyorder'] + except KeyError: polyorder = 2 background = savgol_filter(y_interp, window_length=window_len, polyorder=polyorder, **kwargs) From cfb8ca157a59515af0aee14cdca6fbae659ef143 Mon Sep 17 00:00:00 2001 From: billetd Date: Sat, 7 Feb 2026 21:48:26 +0100 Subject: [PATCH 6/8] Fix broken kwargs --- pydarn/utils/detrend.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pydarn/utils/detrend.py b/pydarn/utils/detrend.py index 2f80a8b2..0f105979 100644 --- a/pydarn/utils/detrend.py +++ b/pydarn/utils/detrend.py @@ -66,10 +66,10 @@ def detrend_savgol(cls, # Generate filter series window_len = (half_k * 2) + 1 try: - polyorder = kwargs['polyorder'] + kwargs['polyorder'] except KeyError: - polyorder = 2 - background = savgol_filter(y_interp, window_length=window_len, polyorder=polyorder, **kwargs) + kwargs['polyorder'] = 2 + background = savgol_filter(y_interp, window_length=window_len, **kwargs) # Detrend detrended_tmp = y_interp - background From 075206c6685fa294cbe37b130d073ef8087bfc3d Mon Sep 17 00:00:00 2001 From: Daniel Billett Date: Tue, 17 Feb 2026 12:33:23 -0600 Subject: [PATCH 7/8] Incorrect default fixed --- pydarn/utils/detrend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydarn/utils/detrend.py b/pydarn/utils/detrend.py index 0f105979..8e7eafd3 100644 --- a/pydarn/utils/detrend.py +++ b/pydarn/utils/detrend.py @@ -141,7 +141,7 @@ def detrend_running_mean(cls, @classmethod def detrend_fitacf(cls, fitacf_data: List[dict], - parameter: str = 'all', + parameter: str = 'both', window_length: int = 600, detrend_type: str='mean', **kwargs From 706b911f956b2857c6a64777946e659ebed97d30 Mon Sep 17 00:00:00 2001 From: Carley Date: Fri, 1 May 2026 12:59:11 -0600 Subject: [PATCH 8/8] suggestions from code review --- pydarn/__init__.py | 1 + pydarn/utils/detrend.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pydarn/__init__.py b/pydarn/__init__.py index 0241e8fd..30ef44d5 100644 --- a/pydarn/__init__.py +++ b/pydarn/__init__.py @@ -55,6 +55,7 @@ from .utils.terminator import terminator from .utils.recalculate_elevation import recalculate_elevation from .utils.filters import Boxcar +from .utils.detrend import Detrend # import plotting from .plotting.color_maps import PyDARNColormaps diff --git a/pydarn/utils/detrend.py b/pydarn/utils/detrend.py index 8e7eafd3..25706736 100644 --- a/pydarn/utils/detrend.py +++ b/pydarn/utils/detrend.py @@ -1,7 +1,7 @@ import copy -import pydarn import datetime as dt import numpy as np +from pydarn import SuperDARNRadars, RadarID from scipy.signal import savgol_filter from typing import List @@ -179,7 +179,7 @@ def detrend_fitacf(cls, fitacf_data_detrended = copy.deepcopy(fitacf_data) # Max beams and range gates for this data - no_beams = pydarn.SuperDARNRadars.radars[pydarn.RadarID(fitacf_data[0]['stid'])].hardware_info.beams + no_beams = SuperDARNRadars.radars[RadarID(fitacf_data[0]['stid'])].hardware_info.beams no_rang = fitacf_data[0]['nrang'] # Grab slist and time lists for all records. "None" indicates no data for that record.