From b156b4521876ef4eb497cfebd9c3f7193e5c0bdf Mon Sep 17 00:00:00 2001 From: kelcyno <88055123+kelcyno@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:40:46 -0500 Subject: [PATCH 001/187] Merge Split algorithm added This file is a post processing step to the tobac tracking step. This is a first iteration in addressing split/merged cells. --- tobac/merge_split.py | 345 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 tobac/merge_split.py diff --git a/tobac/merge_split.py b/tobac/merge_split.py new file mode 100644 index 00000000..0cbbc7e5 --- /dev/null +++ b/tobac/merge_split.py @@ -0,0 +1,345 @@ +#Tobac merge and split v0.1 + +from geopy.distance import geodesic +from networkx import * +import numpy as np +from pandas.core.common import flatten +import xarray as xr + +def compress_all(nc_grids, min_dims=2): + for var in nc_grids: + if len(nc_grids[var].dims) >= min_dims: + # print("Compressing ", var) + nc_grids[var].encoding["zlib"] = True + nc_grids[var].encoding["complevel"] = 4 + nc_grids[var].encoding["contiguous"] = False + return nc_grids + + +def standardize_track_dataset(TrackedFeatures, Mask, Projection): + """ Combine a feature mask with the feature data table into a common dataset. + Also renames th + + returned by tobac.themes.tobac_v1.segmentation + with the TrackedFeatures dataset returned by tobac.themes.tobac_v1.linking_trackpy. + + Also rename the variables to be more desciptive and comply with cf-tree. + + Convert the default cell parent ID to an integer table. + + Add a cell dimension to reflect + + Projection is an xarray DataArray + + TODO: Add metadata attributes to + + """ + feature_standard_names = { + # new variable name, and long description for the NetCDF attribute + 'frame':('feature_time_index', + 'positional index of the feature along the time dimension of the mask, from 0 to N-1'), + 'hdim_1':('feature_hdim1_coordinate', + 'position of the feature along the first horizontal dimension in grid point space; a north-south coordinate for dim order (time, y, x).' + 'The numbering is consistent with positional indexing of the coordinate, but can be' + 'fractional, to account for a centroid not aligned to the grid.'), + 'hdim_2':('feature_hdim2_coordinate', + 'position of the feature along the second horizontal dimension in grid point space; an east-west coordinate for dim order (time, y, x)' + 'The numbering is consistent with positional indexing of the coordinate, but can be' + 'fractional, to account for a centroid not aligned to the grid.'), + 'idx':('feature_id_this_frame',), + 'num':('feature_grid_cell_count', + 'Number of grid points that are within the threshold of this feature'), + 'threshold_value':('feature_threshold_max', + "Feature number within that frame; starts at 1, increments by 1 to the number of features for each frame, and resets to 1 when the frame increments"), + 'feature':('feature_id', + "Unique number of the feature; starts from 1 and increments by 1 to the number of features"), + 'time':('feature_time','time of the feature, consistent with feature_time_index'), + 'timestr':('feature_time_str','String representation of the feature time, YYYY-MM-DD HH:MM:SS'), + 'projection_y_coordinate':('feature_projection_y_coordinate','y position of the feature in the projection given by ProjectionCoordinateSystem'), + 'projection_x_coordinate':('feature_projection_x_coordinate','x position of the feature in the projection given by ProjectionCoordinateSystem'), + 'lat':('feature_latitude','latitude of the feature'), + 'lon':('feature_longitude','longitude of the feature'), + 'ncells':('feature_ncells','number of grid cells for this feature (meaning uncertain)'), + 'areas':('feature_area',), + 'isolated':('feature_isolation_flag',), + 'num_objects':('number_of_feature_neighbors',), + 'cell':('feature_parent_cell_id',), + 'time_cell':('feature_parent_cell_elapsed_time',), + 'segmentation_mask':('2d segmentation mask',) + } + new_feature_var_names = {k:feature_standard_names[k][0] for k in feature_standard_names.keys() + if k in TrackedFeatures.variables.keys()} + + TrackedFeatures = TrackedFeatures.drop(['cell_parent_track_id']) + # Combine Track and Mask variables. Use the 'feature' variable as the coordinate variable instead of + # the 'index' variable and call the dimension 'feature' + ds = xr.merge([TrackedFeatures.swap_dims({'index':'feature'}).drop('index').rename_vars(new_feature_var_names), + Mask]) + + # Add the projection data back in + ds['ProjectionCoordinateSystem']=Projection + + # Convert the cell ID variable from float to integer + if 'int' not in str(TrackedFeatures.cell.dtype): + # The raw output from the tracking is actually an object array + # array([nan, 2, 3], dtype=object) + # (and is cast to a float array when saved as NetCDF, I think). + # Cast to float. + int_cell = xr.zeros_like(TrackedFeatures.cell, dtype='int64') + + cell_id_data = TrackedFeatures.cell.astype('float64') + valid_cell = np.isfinite(cell_id_data) + valid_cell_ids = cell_id_data[valid_cell] + if not (np.unique(valid_cell_ids) > 0).all(): + raise AssertionError('Lowest cell ID cell is less than one, conflicting with use of zero to indicate an untracked cell') + int_cell[valid_cell] = valid_cell_ids.astype('int64') + #ds['feature_parent_cell_id'] = int_cell + return ds + + + +def merge_split(TRACK,distance = 25000,frame_len = 5): + ''' + function to postprocess tobac track data for merge/split cells + Input: + TRACK: xarray dataset of tobac Track information + + distance: float, optional distance threshold prior to adding a pair of points + into the minimum spanning tree. Default is 25000 meters. + + frame_len: float, optional threshold for the spanning length within which two points + can be separated. Default is five (5) frames. + + + Output: + d: xarray dataset of + feature position along 1st horizontal dimension + hdim2_index: float + feature position along 2nd horizontal dimension + + Example: + d = merge_split(Track) + ds = standardize_track_dataset(Track, refl_mask, data['ProjectionCoordinateSystem']) + # both_ds = xarray.combine_by_coords((ds,d), compat='override') + both_ds = xr.merge([ds, d],compat ='override') + both_ds = compress_all(both_ds) + both_ds.to_netcdf(os.path.join(savedir,'Track_features_merges.nc')) + + ''' + track_groups = TRACK.groupby('cell') + cell_ids = {cid:len(v) for cid, v in track_groups.groups.items()} + id_data = np.fromiter(cell_ids.keys(), dtype=int) + count_data = np.fromiter(cell_ids.values(), dtype=int) + all_frames = np.sort(np.unique(TRACK.frame)) + a_points = list() + b_points = list() + a_names = list() + b_names = list() + dist = list() + + + for i in id_data: + #print(i) + a_pointx = track_groups[i].projection_x_coordinate[-1].values + a_pointy = track_groups[i].projection_y_coordinate[-1].values + for j in id_data: + b_pointx = track_groups[j].projection_x_coordinate[0].values + b_pointy = track_groups[j].projection_y_coordinate[0].values + d = np.linalg.norm(np.array((a_pointx, a_pointy)) - np.array((b_pointx, b_pointy))) + if d <= distance: + a_points.append([a_pointx,a_pointy]) + b_points.append([b_pointx, b_pointy]) + dist.append(d) + a_names.append(i) + b_names.append(j) + + + + + +# for i in id_data: +# a_pointx = track_groups[i].grid_longitude[-1].values +# a_pointy = track_groups[i].grid_latitude[-1].values +# for j in id_data: +# b_pointx = track_groups[j].grid_longitude[0].values +# b_pointy = track_groups[j].grid_latitude[0].values +# d = geodesic((a_pointy,a_pointx),(b_pointy,b_pointx)).km +# if d <= distance: +# a_points.append([a_pointx,a_pointy]) +# b_points.append([b_pointx, b_pointy]) +# dist.append(d) +# a_names.append(i) +# b_names.append(j) + + id = [] + for i in range(len(dist)-1, -1, -1): + if a_names[i] == b_names[i]: + id.append(i) + a_points.pop(i) + b_points.pop(i) + dist.pop(i) + a_names.pop(i) + b_names.pop(i) + else: + continue + + g = Graph() + for i in np.arange(len(dist)): + g.add_edge(a_names[i], b_names[i],weight=dist[i]) + + tree = minimum_spanning_edges(g) + tree_list = list(minimum_spanning_edges(g)) + + new_tree = [] + for i,j in enumerate(tree_list): + frame_a = np.nanmax(track_groups[j[0]].frame.values) + frame_b = np.nanmin(track_groups[j[1]].frame.values) + if np.abs(frame_a - frame_b) <= frame_len: + new_tree.append(tree_list[i][0:2]) + new_tree_arr = np.array(new_tree) + + TRACK['cell_parent_track_id'] = np.zeros(len(TRACK['cell'].values)) + cell_id = np.unique(TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))]) + track_id = dict() #same size as number of total merged tracks + + arr = np.array([0]) + for p in cell_id: + j = np.where(arr == int(p)) + if len(j[0]) > 0: + continue + else: + k = np.where(new_tree_arr == p) + if len(k[0]) == 0: + track_id[p] = [p] + arr = np.append(arr,p) + else: + temp1 = list(np.unique(new_tree_arr[k[0]])) + temp = list(np.unique(new_tree_arr[k[0]])) + + for l in range(15): + for i in temp1: + k2 = np.where(new_tree_arr == i) + temp.append(list(np.unique(new_tree_arr[k2[0]]).squeeze())) + temp = list(flatten(temp)) + temp = list(np.unique(temp)) + + if len(temp1) == len(temp): + break + temp1 = np.array(temp) + + for i in temp1: + k2 = np.where(new_tree_arr == i) + temp.append(list(np.unique(new_tree_arr[k2[0]]).squeeze())) + + temp = list(flatten(temp)) + temp = list(np.unique(temp)) + arr = np.append(arr,np.unique(temp)) + + track_id[np.nanmax(np.unique(temp))] = list(np.unique(temp)) + + + + storm_id = [0] #default because we don't track larger storm systems *yet* + print('found storm id') + + + track_parent_storm_id = np.repeat(0, len(track_id)) #This will always be zero when we don't track larger storm systems *yet* + print('found track parent storm ids') + + track_ids = np.array(list(track_id.keys())) + print('found track ids') + + + cell_id = list(np.unique(TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))])) + print('found cell ids') + + cell_parent_track_id = [] + + for i, id in enumerate(track_id): + + if len(track_id[int(id)]) == 1: + cell_parent_track_id.append(int(id)) + + else: + cell_parent_track_id.append(np.repeat(int(id),len(track_id[int(id)]))) + + + cell_parent_track_id = list(flatten(cell_parent_track_id)) + print('found cell parent track ids') + + feature_parent_cell_id = list(TRACK.cell.values.astype(float)) + + print('found feature parent cell ids') + + #This version includes all the feature regardless of if they are used in cells or not. + feature_id = list(TRACK.feature.values.astype(int)) + print('found feature ids') + + feature_parent_storm_id = np.repeat(0,len(feature_id)) #we don't do storms atm + print('found feature parent storm ids') + + feature_parent_track_id = [] + feature_parent_track_id = np.zeros(len(feature_id)) + for i, id in enumerate(feature_id): + cellid = feature_parent_cell_id[i] + if np.isnan(cellid): + feature_parent_track_id[i] = -1 + else: + j = np.where(cell_id == cellid) + j = np.squeeze(j) + trackid = cell_parent_track_id[j] + feature_parent_track_id[i] = trackid + + print('found feature parent track ids') + + + storm_child_track_count = [len(track_id)] + print('found storm child track count') + + track_child_cell_count = [] + for i,id in enumerate(track_id): + track_child_cell_count.append(len(track_id[int(id)])) + print('found track child cell count') + + + cell_child_feature_count = [] + for i,id in enumerate(cell_id): + cell_child_feature_count.append(len(track_groups[id].feature.values)) + print('found cell child feature count') + + storm_child_cell_count = [len(cell_id)] + storm_child_feature_count = [len(feature_id)] + + storm_dim = 'nstorms' + track_dim = 'ntracks' + cell_dim = 'ncells' + feature_dim = 'nfeatures' + + d = xr.Dataset({ + 'storm_id': (storm_dim, storm_id), + 'track_id': (track_dim, track_ids), + 'track_parent_storm_id': (track_dim, track_parent_storm_id), + 'cell_id': (cell_dim, cell_id), + 'cell_parent_track_id': (cell_dim, cell_parent_track_id), + 'feature_id': (feature_dim, feature_id), + 'feature_parent_cell_id': (feature_dim, feature_parent_cell_id), + 'feature_parent_track_id': (feature_dim, feature_parent_track_id), + 'feature_parent_storm_id': (feature_dim, feature_parent_storm_id), + 'storm_child_track_count': (storm_dim, storm_child_track_count), + 'storm_child_cell_count': (storm_dim, storm_child_cell_count), + 'storm_child_feature_count': (storm_dim, storm_child_feature_count), + 'track_child_cell_count': (track_dim, track_child_cell_count), + 'cell_child_feature_count': (cell_dim, cell_child_feature_count), + }) + d = d.set_coords(['feature_id','cell_id', 'track_id', 'storm_id']) + + assert len(track_id) == len(track_parent_storm_id) + assert len(cell_id) == len(cell_parent_track_id) + assert len(feature_id) == len(feature_parent_cell_id) + assert sum(storm_child_track_count) == len(track_id) + assert sum(storm_child_cell_count) == len(cell_id) + assert sum(storm_child_feature_count) == len(feature_id) + assert sum(track_child_cell_count) == len(cell_id) + assert sum([sum(cell_child_feature_count),(len(np.where(feature_parent_track_id < 0)[0]))]) == len(feature_id) + + return d \ No newline at end of file From 9eefd15ff9bda396ea9db130312371a767b6c672 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 12:09:46 +0200 Subject: [PATCH 002/187] add spectral filtering function to utils --- tobac/utils.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/tobac/utils.py b/tobac/utils.py index 2509b97f..cbaa6fdc 100644 --- a/tobac/utils.py +++ b/tobac/utils.py @@ -578,3 +578,95 @@ def get_indices_of_labels_from_reg_prop_dict(region_property_dict): curr_loc_indices[index] = len(curr_y_ixs) return (curr_loc_indices, y_indices, x_indices) + + + +def spectral_filtering( + dxy, field_in, lambda_min, lambda_max, return_transfer_function=False +): + """ + This function creates and applies a 2D transfer function that can be used as a bandpass filter to remove + certain wavelengths of an atmospheric input field (e.g. vorticity, IVT, etc). + + Parameters: + ----------- + dxy : float + grid spacing in m + field_in: numpy.array + 2D field with input data + lambda_min: float + minimum wavelength in km + lambda_max: float + maximum wavelength in km + return_transfer_function: boolean, optional + default: False. If set to True, then the 2D transfer function and the corresponding wavelengths are returned. + + Returns: + -------- + filtered_field: numpy.array + spectrally filtered 2D field of data (with same shape as input data) + transfer_function: tuple + Two 2D fields, where the first one corresponds to the wavelengths in the spectral space of the domain and the second one + to the 2D transfer function of the bandpass filter. Only returned, if return_transfer_function is True. + """ + import numpy as np + from scipy import signal + from scipy import fft + + # check if valid value for dxy is given + if dxy <= 0: + raise ValueError( + "Invalid value for dxy. Please provide the grid spacing in meter." + ) + + # convert grid spacing to km to get same units as given wavelengths + dxy = dxy / 1000 + + # get number of grid cells in x and y direction + Ni = field_in.shape[-2] + Nj = field_in.shape[-1] + # wavenumber space + m, n = np.meshgrid(np.arange(Ni), np.arange(Nj), indexing="ij") + + # if domain is squared: + if Ni == Nj: + wavenumber = np.sqrt(m**2 + n**2) + lambda_mn = (2 * Ni * (dx)) / wavenumber + else: + # if domain is a rectangle: + # alpha is the normalized wavenumber in wavenumber space + alpha = np.sqrt(m**2 / Ni**2 + n**2 / Nj**2) + # compute wavelengths for target grid in km + lambda_mn = 2 * dxy / alpha + + ############### create a 2D bandpass filter (butterworth) ####################### + b, a = signal.iirfilter( + 2, + [1 / lambda_max, 1 / lambda_min], + btype="band", + ftype="butter", + fs=1 / dxy, + output="ba", + ) + w, h = signal.freqz(b, a, 1 / lambda_mn.flatten(), fs=1 / dxy) + transfer_function = np.reshape(abs(h), lambda_mn.shape) + + # 2-dimensional discrete cosine transformation to convert data to spectral space + spectral = fft.dctn(field_in.data) + # multiplication of spectral coefficients with transfer function + filtered = spectral * transfer_function + # inverse discrete cosine transformation to go back from spectral to original space + filtered_field = fft.idctn(filtered) + + if return_transfer_function is True: + return (lambda_mn, transfer_function), filtered_field + else: + return filtered_field + + + + + + + + From ed471659cf168b69ebfd049ae6578f2437df9e6d Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 12:35:16 +0200 Subject: [PATCH 003/187] added wavelength parameter in feature detection functions --- tobac/feature_detection.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index dea74933..cc1eb310 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -2,9 +2,9 @@ import numpy as np import pandas as pd from . import utils as tb_utils +from tobac.utils import spectral_filtering import warnings - def feature_position( hdim1_indices, hdim2_indices, @@ -336,6 +336,7 @@ def feature_detection_multithreshold_timestep( n_min_threshold=0, min_distance=0, feature_number_start=1, + wavelength_filtering= None ): """ function to find features in each timestep based on iteratively finding regions above/below a set of thresholds @@ -363,6 +364,10 @@ def feature_detection_multithreshold_timestep( minimum distance between detected features (m) feature_number_start: int feature number to start with + wavelength_filtering: tuple, optional + minimum and maximum wavelengths in km, if spectral filtering of input field is desired + + Output: features_threshold: pandas DataFrame detected features for individual timestep @@ -384,6 +389,12 @@ def feature_detection_multithreshold_timestep( track_data = gaussian_filter( track_data, sigma=sigma_threshold ) # smooth data slightly to create rounded, continuous field + + # spectrally filter the input data, if desired + if wavelength_filtering is not None: + track_data = spectral_filtering(dxy, track_data, wavelength_filtering[0], wavelength_filtering[1]) + + # create empty lists to store regions and features for individual timestep features_thresholds = pd.DataFrame() for i_threshold, threshold_i in enumerate(threshold): @@ -436,6 +447,7 @@ def feature_detection_multithreshold( n_min_threshold=0, min_distance=0, feature_number_start=1, + wavelength_filtering = None ): """Function to perform feature detection based on contiguous regions above/below a threshold Input: @@ -458,6 +470,8 @@ def feature_detection_multithreshold( minimum number of identified features min_distance: float minimum distance between detected features (m) + wavelength_filtering: tuple, optional + minimum and maximum wavelengths in km, if spectral filtering of input field is desired Output: features: pandas DataFrame detected features @@ -496,6 +510,7 @@ def feature_detection_multithreshold( n_min_threshold=n_min_threshold, min_distance=min_distance, feature_number_start=feature_number_start, + wavelenth_filtering = wavelength_filtering ) # check if list of features is not empty, then merge features from different threshold values # into one DataFrame and append to list for individual timesteps: From 24b29514fa1fbc006e97361c5025d5423ae594b7 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 12:45:08 +0200 Subject: [PATCH 004/187] add check to ensure that wavelength input cannot be larger than the distance along both dimensions --- tobac/feature_detection.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index cc1eb310..0ef27358 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -496,6 +496,14 @@ def feature_detection_multithreshold( if type(threshold) in [int, float]: threshold = [threshold] + # if wavelength_filtering is given, check that value cannot be larger than distances along x and y + if wavelength_filtering is not None: + distance_x = field_in.shape[1] * (dxy/ 1000) + distance_y = field_in.shape[2] * (dxy/ 1000) + distance = min(distance_x, distance_y) + if wavelength_filtering[0] > distance or wavelength_filtering[1] > distance: + raise ValueError("The given wavelengths cannot be larger than the total distance in km along the axes of the domain.") + for i_time, data_i in enumerate(data_time): time_i = data_i.coord("time").units.num2date(data_i.coord("time").points[0]) features_thresholds = feature_detection_multithreshold_timestep( From ebcead6276e961588995c8f012b5c2230a3595ad Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 13:34:26 +0200 Subject: [PATCH 005/187] add dxy to feature_detection_multithreshold_timestep because it is needed if spectral filtering is desired --- tobac/feature_detection.py | 4 +- tobac/utils.py | 158 ++++++++++++++++++------------------- 2 files changed, 81 insertions(+), 81 deletions(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index 0ef27358..651f54dc 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -336,6 +336,7 @@ def feature_detection_multithreshold_timestep( n_min_threshold=0, min_distance=0, feature_number_start=1, + dxy = -1, wavelength_filtering= None ): """ @@ -518,7 +519,8 @@ def feature_detection_multithreshold( n_min_threshold=n_min_threshold, min_distance=min_distance, feature_number_start=feature_number_start, - wavelenth_filtering = wavelength_filtering + dxy= dxy, + wavelength_filtering = wavelength_filtering ) # check if list of features is not empty, then merge features from different threshold values # into one DataFrame and append to list for individual timesteps: diff --git a/tobac/utils.py b/tobac/utils.py index cbaa6fdc..a5088dc8 100644 --- a/tobac/utils.py +++ b/tobac/utils.py @@ -581,87 +581,85 @@ def get_indices_of_labels_from_reg_prop_dict(region_property_dict): -def spectral_filtering( - dxy, field_in, lambda_min, lambda_max, return_transfer_function=False -): - """ - This function creates and applies a 2D transfer function that can be used as a bandpass filter to remove - certain wavelengths of an atmospheric input field (e.g. vorticity, IVT, etc). - - Parameters: - ----------- - dxy : float - grid spacing in m - field_in: numpy.array - 2D field with input data - lambda_min: float - minimum wavelength in km - lambda_max: float - maximum wavelength in km - return_transfer_function: boolean, optional - default: False. If set to True, then the 2D transfer function and the corresponding wavelengths are returned. - - Returns: - -------- - filtered_field: numpy.array - spectrally filtered 2D field of data (with same shape as input data) - transfer_function: tuple - Two 2D fields, where the first one corresponds to the wavelengths in the spectral space of the domain and the second one - to the 2D transfer function of the bandpass filter. Only returned, if return_transfer_function is True. - """ - import numpy as np - from scipy import signal - from scipy import fft - - # check if valid value for dxy is given - if dxy <= 0: - raise ValueError( - "Invalid value for dxy. Please provide the grid spacing in meter." - ) +def spectral_filtering(dxy, field_in, lambda_min, lambda_max, return_transfer_function=False): + """ + This function creates and applies a 2D transfer function that can be used as a bandpass filter to remove + certain wavelengths of an atmospheric input field (e.g. vorticity, IVT, etc). + + Parameters: + ----------- + dxy : float + grid spacing in m + field_in: numpy.array + 2D field with input data + lambda_min: float + minimum wavelength in km + lambda_max: float + maximum wavelength in km + return_transfer_function: boolean, optional + default: False. If set to True, then the 2D transfer function and the corresponding wavelengths are returned. + + Returns: + -------- + filtered_field: numpy.array + spectrally filtered 2D field of data (with same shape as input data) + transfer_function: tuple + Two 2D fields, where the first one corresponds to the wavelengths in the spectral space of the domain and the second one + to the 2D transfer function of the bandpass filter. Only returned, if return_transfer_function is True. + """ + import numpy as np + from scipy import signal + from scipy import fft - # convert grid spacing to km to get same units as given wavelengths - dxy = dxy / 1000 - - # get number of grid cells in x and y direction - Ni = field_in.shape[-2] - Nj = field_in.shape[-1] - # wavenumber space - m, n = np.meshgrid(np.arange(Ni), np.arange(Nj), indexing="ij") - - # if domain is squared: - if Ni == Nj: - wavenumber = np.sqrt(m**2 + n**2) - lambda_mn = (2 * Ni * (dx)) / wavenumber - else: - # if domain is a rectangle: - # alpha is the normalized wavenumber in wavenumber space - alpha = np.sqrt(m**2 / Ni**2 + n**2 / Nj**2) - # compute wavelengths for target grid in km - lambda_mn = 2 * dxy / alpha - - ############### create a 2D bandpass filter (butterworth) ####################### - b, a = signal.iirfilter( - 2, - [1 / lambda_max, 1 / lambda_min], - btype="band", - ftype="butter", - fs=1 / dxy, - output="ba", - ) - w, h = signal.freqz(b, a, 1 / lambda_mn.flatten(), fs=1 / dxy) - transfer_function = np.reshape(abs(h), lambda_mn.shape) - - # 2-dimensional discrete cosine transformation to convert data to spectral space - spectral = fft.dctn(field_in.data) - # multiplication of spectral coefficients with transfer function - filtered = spectral * transfer_function - # inverse discrete cosine transformation to go back from spectral to original space - filtered_field = fft.idctn(filtered) - - if return_transfer_function is True: - return (lambda_mn, transfer_function), filtered_field - else: - return filtered_field + # check if valid value for dxy is given + if dxy <= 0: + raise ValueError( + "Invalid value for dxy. Please provide the grid spacing in meter." + ) + + # convert grid spacing to km to get same units as given wavelengths + dxy = dxy / 1000 + + # get number of grid cells in x and y direction + Ni = field_in.shape[-2] + Nj = field_in.shape[-1] + # wavenumber space + m, n = np.meshgrid(np.arange(Ni), np.arange(Nj), indexing="ij") + + # if domain is squared: + if Ni == Nj: + wavenumber = np.sqrt(m**2 + n**2) + lambda_mn = (2 * Ni * (dx)) / wavenumber + else: + # if domain is a rectangle: + # alpha is the normalized wavenumber in wavenumber space + alpha = np.sqrt(m**2 / Ni**2 + n**2 / Nj**2) + # compute wavelengths for target grid in km + lambda_mn = 2 * dxy / alpha + + ############### create a 2D bandpass filter (butterworth) ####################### + b, a = signal.iirfilter( + 2, + [1 / lambda_max, 1 / lambda_min], + btype="band", + ftype="butter", + fs=1 / dxy, + output="ba", + ) + w, h = signal.freqz(b, a, 1 / lambda_mn.flatten(), fs=1 / dxy) + transfer_function = np.reshape(abs(h), lambda_mn.shape) + + # 2-dimensional discrete cosine transformation to convert data to spectral space + spectral = fft.dctn(field_in.data) + # multiplication of spectral coefficients with transfer function + filtered = spectral * transfer_function + # inverse discrete cosine transformation to go back from spectral to original space + filtered_field = fft.idctn(filtered) + + if return_transfer_function is True: + return (lambda_mn, transfer_function), filtered_field + else: + return filtered_field From 66d363e9e9d21a2fa411d0b8434d005672a89432 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 14:00:30 +0200 Subject: [PATCH 006/187] formatting --- tobac/feature_detection.py | 26 +++++++++++++++----------- tobac/utils.py | 15 ++++----------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index 651f54dc..cf68ecaa 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -5,6 +5,7 @@ from tobac.utils import spectral_filtering import warnings + def feature_position( hdim1_indices, hdim2_indices, @@ -336,8 +337,8 @@ def feature_detection_multithreshold_timestep( n_min_threshold=0, min_distance=0, feature_number_start=1, - dxy = -1, - wavelength_filtering= None + dxy=-1, + wavelength_filtering=None, ): """ function to find features in each timestep based on iteratively finding regions above/below a set of thresholds @@ -367,7 +368,7 @@ def feature_detection_multithreshold_timestep( feature number to start with wavelength_filtering: tuple, optional minimum and maximum wavelengths in km, if spectral filtering of input field is desired - + Output: features_threshold: pandas DataFrame @@ -393,8 +394,9 @@ def feature_detection_multithreshold_timestep( # spectrally filter the input data, if desired if wavelength_filtering is not None: - track_data = spectral_filtering(dxy, track_data, wavelength_filtering[0], wavelength_filtering[1]) - + track_data = spectral_filtering( + dxy, track_data, wavelength_filtering[0], wavelength_filtering[1] + ) # create empty lists to store regions and features for individual timestep features_thresholds = pd.DataFrame() @@ -448,7 +450,7 @@ def feature_detection_multithreshold( n_min_threshold=0, min_distance=0, feature_number_start=1, - wavelength_filtering = None + wavelength_filtering=None, ): """Function to perform feature detection based on contiguous regions above/below a threshold Input: @@ -499,11 +501,13 @@ def feature_detection_multithreshold( # if wavelength_filtering is given, check that value cannot be larger than distances along x and y if wavelength_filtering is not None: - distance_x = field_in.shape[1] * (dxy/ 1000) - distance_y = field_in.shape[2] * (dxy/ 1000) + distance_x = field_in.shape[1] * (dxy / 1000) + distance_y = field_in.shape[2] * (dxy / 1000) distance = min(distance_x, distance_y) if wavelength_filtering[0] > distance or wavelength_filtering[1] > distance: - raise ValueError("The given wavelengths cannot be larger than the total distance in km along the axes of the domain.") + raise ValueError( + "The given wavelengths cannot be larger than the total distance in km along the axes of the domain." + ) for i_time, data_i in enumerate(data_time): time_i = data_i.coord("time").units.num2date(data_i.coord("time").points[0]) @@ -519,8 +523,8 @@ def feature_detection_multithreshold( n_min_threshold=n_min_threshold, min_distance=min_distance, feature_number_start=feature_number_start, - dxy= dxy, - wavelength_filtering = wavelength_filtering + dxy=dxy, + wavelength_filtering=wavelength_filtering, ) # check if list of features is not empty, then merge features from different threshold values # into one DataFrame and append to list for individual timesteps: diff --git a/tobac/utils.py b/tobac/utils.py index a5088dc8..e115d835 100644 --- a/tobac/utils.py +++ b/tobac/utils.py @@ -580,8 +580,9 @@ def get_indices_of_labels_from_reg_prop_dict(region_property_dict): return (curr_loc_indices, y_indices, x_indices) - -def spectral_filtering(dxy, field_in, lambda_min, lambda_max, return_transfer_function=False): +def spectral_filtering( + dxy, field_in, lambda_min, lambda_max, return_transfer_function=False +): """ This function creates and applies a 2D transfer function that can be used as a bandpass filter to remove certain wavelengths of an atmospheric input field (e.g. vorticity, IVT, etc). @@ -651,7 +652,7 @@ def spectral_filtering(dxy, field_in, lambda_min, lambda_max, return_transfer_fu # 2-dimensional discrete cosine transformation to convert data to spectral space spectral = fft.dctn(field_in.data) - # multiplication of spectral coefficients with transfer function + # multiplication of spectral coefficients with transfer function filtered = spectral * transfer_function # inverse discrete cosine transformation to go back from spectral to original space filtered_field = fft.idctn(filtered) @@ -660,11 +661,3 @@ def spectral_filtering(dxy, field_in, lambda_min, lambda_max, return_transfer_fu return (lambda_mn, transfer_function), filtered_field else: return filtered_field - - - - - - - - From 25a3f8d7881011fa5aa57f88d76a91851751f2da Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 15:08:52 +0200 Subject: [PATCH 007/187] add test for spectral_filtering --- tobac/tests/test_utils.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tobac/tests/test_utils.py diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py new file mode 100644 index 00000000..769f01d1 --- /dev/null +++ b/tobac/tests/test_utils.py @@ -0,0 +1,28 @@ +import numpy as np +import tobac.utils as tb_utils + + + +def test_spectral_filtering(): + """Testing tobac.utils.spectral_filtering with random test data.""" + + # generate 3D data with random values + random_data = np.random.rand(100,300,500) + + # define grid spacing [m] and minimum and maximum wavelength [km] + dxy = 4000 + lambda_min, lambda_max = 400, 1000 + + # use spectral filtering function on random data + transfer_function, filtered_data = tb_utils.spectral_filtering(dxy, random_data, lambda_min, lambda_max, return_transfer_function = True) + + # a few checks on the output + # wavelength space + wavelengths = transfer_function[0] + assert wavelengths[0,0] == np.inf + assert wavelengths[1,0] == (dxy/1000) *random_data.shape[-2]*2 + assert wavelengths[0,1] == (dxy/1000) *random_data.shape[-1]*2 + + # filtered/ smoothed field + assert (filtered_data.max() - filtered_data.min()) < (random_data.max() - random_data.min()) + From 34795888560dce82d2d723ad0fd5384b93635236 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 15:16:12 +0200 Subject: [PATCH 008/187] add better description of assert statements --- tobac/tests/test_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index 769f01d1..c20de1ad 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -2,7 +2,6 @@ import tobac.utils as tb_utils - def test_spectral_filtering(): """Testing tobac.utils.spectral_filtering with random test data.""" @@ -16,10 +15,11 @@ def test_spectral_filtering(): # use spectral filtering function on random data transfer_function, filtered_data = tb_utils.spectral_filtering(dxy, random_data, lambda_min, lambda_max, return_transfer_function = True) - # a few checks on the output - # wavelength space + # a few checks on the output wavelengths = transfer_function[0] + # first element in wavelengths-space is inf because normalized wavelengths are 0 here assert wavelengths[0,0] == np.inf + # the first elements should correspond to twice the distance of the corresponding axis (in km) assert wavelengths[1,0] == (dxy/1000) *random_data.shape[-2]*2 assert wavelengths[0,1] == (dxy/1000) *random_data.shape[-1]*2 From 3b10583d58bdfe1d6846a72af04d7f2155f5ed7c Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 15:17:49 +0200 Subject: [PATCH 009/187] formatting --- tobac/tests/test_utils.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index c20de1ad..ccd46d95 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -6,23 +6,26 @@ def test_spectral_filtering(): """Testing tobac.utils.spectral_filtering with random test data.""" # generate 3D data with random values - random_data = np.random.rand(100,300,500) + random_data = np.random.rand(100, 300, 500) # define grid spacing [m] and minimum and maximum wavelength [km] dxy = 4000 lambda_min, lambda_max = 400, 1000 # use spectral filtering function on random data - transfer_function, filtered_data = tb_utils.spectral_filtering(dxy, random_data, lambda_min, lambda_max, return_transfer_function = True) + transfer_function, filtered_data = tb_utils.spectral_filtering( + dxy, random_data, lambda_min, lambda_max, return_transfer_function=True + ) # a few checks on the output wavelengths = transfer_function[0] # first element in wavelengths-space is inf because normalized wavelengths are 0 here - assert wavelengths[0,0] == np.inf - # the first elements should correspond to twice the distance of the corresponding axis (in km) - assert wavelengths[1,0] == (dxy/1000) *random_data.shape[-2]*2 - assert wavelengths[0,1] == (dxy/1000) *random_data.shape[-1]*2 + assert wavelengths[0, 0] == np.inf + # the first elements should correspond to twice the distance of the corresponding axis (in km) + assert wavelengths[1, 0] == (dxy / 1000) * random_data.shape[-2] * 2 + assert wavelengths[0, 1] == (dxy / 1000) * random_data.shape[-1] * 2 # filtered/ smoothed field - assert (filtered_data.max() - filtered_data.min()) < (random_data.max() - random_data.min()) - + assert (filtered_data.max() - filtered_data.min()) < ( + random_data.max() - random_data.min() + ) From 7139e8a9d976e57bfae5b7ec90a146e560e80e03 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 15:35:00 +0200 Subject: [PATCH 010/187] add comment in test_utils.py --- tobac/tests/test_utils.py | 1 + tobac/utils.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index ccd46d95..e2277853 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -22,6 +22,7 @@ def test_spectral_filtering(): # first element in wavelengths-space is inf because normalized wavelengths are 0 here assert wavelengths[0, 0] == np.inf # the first elements should correspond to twice the distance of the corresponding axis (in km) + # this is because the maximum spatial scale is half a wavelength through the domain assert wavelengths[1, 0] == (dxy / 1000) * random_data.shape[-2] * 2 assert wavelengths[0, 1] == (dxy / 1000) * random_data.shape[-1] * 2 diff --git a/tobac/utils.py b/tobac/utils.py index e115d835..765a3606 100644 --- a/tobac/utils.py +++ b/tobac/utils.py @@ -630,7 +630,7 @@ def spectral_filtering( # if domain is squared: if Ni == Nj: wavenumber = np.sqrt(m**2 + n**2) - lambda_mn = (2 * Ni * (dx)) / wavenumber + lambda_mn = (2 * Ni * (dxy)) / wavenumber else: # if domain is a rectangle: # alpha is the normalized wavenumber in wavenumber space From 00bfcf706dda92461014bae702c99d90cb50d75f Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 15:36:26 +0200 Subject: [PATCH 011/187] add comment in test_utils.py --- tobac/tests/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index e2277853..7dcfa4e5 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -26,7 +26,7 @@ def test_spectral_filtering(): assert wavelengths[1, 0] == (dxy / 1000) * random_data.shape[-2] * 2 assert wavelengths[0, 1] == (dxy / 1000) * random_data.shape[-1] * 2 - # filtered/ smoothed field + # check that filtered/ smoothed field exhibits smaller range of values assert (filtered_data.max() - filtered_data.min()) < ( random_data.max() - random_data.min() ) From 50e59ffa61b393e96fec16f2a3d08570ed334d63 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 16:13:01 +0200 Subject: [PATCH 012/187] added parametrizing fixture so that feature_detection_multithreshold_timesteps is tested with and without spectral filtering option --- tobac/tests/test_feature_detection.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tobac/tests/test_feature_detection.py b/tobac/tests/test_feature_detection.py index 86d668c5..2f5ab267 100644 --- a/tobac/tests/test_feature_detection.py +++ b/tobac/tests/test_feature_detection.py @@ -3,7 +3,9 @@ import pytest -def test_feature_detection_multithreshold_timestep(): + +@pytest.mark.parametrize("test_threshs, dxy, wavelength_filtering", [([1.5], -1, None), ([1.5], 10000, (100,500)) ], ) +def test_feature_detection_multithreshold_timestep(test_threshs, dxy, wavelength_filtering): """ Tests ```tobac.feature_detection.feature_detection_multithreshold_timestep """ @@ -20,9 +22,6 @@ def test_feature_detection_multithreshold_timestep(): test_hdim_1_sz = 5 test_hdim_2_sz = 5 test_amp = 2 - test_threshs = [ - 1.5, - ] test_min_num = 2 test_data = np.zeros(test_dset_size) @@ -36,7 +35,7 @@ def test_feature_detection_multithreshold_timestep(): ) test_data_iris = testing.make_dataset_from_arr(test_data, data_type="iris") fd_output = feature_detection.feature_detection_multithreshold_timestep( - test_data_iris, 0, threshold=test_threshs, n_min_threshold=test_min_num + test_data_iris, 0, threshold=test_threshs, n_min_threshold=test_min_num,dxy = dxy, wavelength_filtering =wavelength_filtering ) # Make sure we have only one feature @@ -44,3 +43,19 @@ def test_feature_detection_multithreshold_timestep(): # Make sure that the location of the feature is correct assert fd_output.iloc[0]["hdim_1"] == pytest.approx(test_hdim_1_pt) assert fd_output.iloc[0]["hdim_2"] == pytest.approx(test_hdim_2_pt) + + + + + + + + + + + + + + + + From a8c6be51f50a0f3d6da3e29887789060d0e69da0 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 8 Jun 2022 16:27:51 +0200 Subject: [PATCH 013/187] formatting --- tobac/tests/test_feature_detection.py | 33 +++++++++++---------------- tobac/tests/test_utils.py | 4 ++-- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/tobac/tests/test_feature_detection.py b/tobac/tests/test_feature_detection.py index 2f5ab267..f3731dbb 100644 --- a/tobac/tests/test_feature_detection.py +++ b/tobac/tests/test_feature_detection.py @@ -3,9 +3,13 @@ import pytest - -@pytest.mark.parametrize("test_threshs, dxy, wavelength_filtering", [([1.5], -1, None), ([1.5], 10000, (100,500)) ], ) -def test_feature_detection_multithreshold_timestep(test_threshs, dxy, wavelength_filtering): +@pytest.mark.parametrize( + "test_threshs, dxy, wavelength_filtering", + [([1.5], -1, None), ([1.5], 10000, (100, 500))], +) +def test_feature_detection_multithreshold_timestep( + test_threshs, dxy, wavelength_filtering +): """ Tests ```tobac.feature_detection.feature_detection_multithreshold_timestep """ @@ -35,7 +39,12 @@ def test_feature_detection_multithreshold_timestep(test_threshs, dxy, wavelength ) test_data_iris = testing.make_dataset_from_arr(test_data, data_type="iris") fd_output = feature_detection.feature_detection_multithreshold_timestep( - test_data_iris, 0, threshold=test_threshs, n_min_threshold=test_min_num,dxy = dxy, wavelength_filtering =wavelength_filtering + test_data_iris, + 0, + threshold=test_threshs, + n_min_threshold=test_min_num, + dxy=dxy, + wavelength_filtering=wavelength_filtering, ) # Make sure we have only one feature @@ -43,19 +52,3 @@ def test_feature_detection_multithreshold_timestep(test_threshs, dxy, wavelength # Make sure that the location of the feature is correct assert fd_output.iloc[0]["hdim_1"] == pytest.approx(test_hdim_1_pt) assert fd_output.iloc[0]["hdim_2"] == pytest.approx(test_hdim_2_pt) - - - - - - - - - - - - - - - - diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index 7dcfa4e5..0f9755dd 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -22,11 +22,11 @@ def test_spectral_filtering(): # first element in wavelengths-space is inf because normalized wavelengths are 0 here assert wavelengths[0, 0] == np.inf # the first elements should correspond to twice the distance of the corresponding axis (in km) - # this is because the maximum spatial scale is half a wavelength through the domain + # this is because the maximum spatial scale is half a wavelength through the domain assert wavelengths[1, 0] == (dxy / 1000) * random_data.shape[-2] * 2 assert wavelengths[0, 1] == (dxy / 1000) * random_data.shape[-1] * 2 - # check that filtered/ smoothed field exhibits smaller range of values + # check that filtered/ smoothed field exhibits smaller range of values assert (filtered_data.max() - filtered_data.min()) < ( random_data.max() - random_data.min() ) From d9591b6e816843bccba49ec87ac368257013f8c5 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Wed, 8 Jun 2022 23:34:52 +0200 Subject: [PATCH 014/187] revised analysis.py docstrings --- tobac/analysis.py | 657 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 631 insertions(+), 26 deletions(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index bfb373de..3f325781 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -1,3 +1,31 @@ +"""Provide tools to analyse and visualize the tracked objects. +This module provides a set of routines that enables performing analyses +and deriving statistics for individual clouds, such as the time series +of integrated properties and vertical profiles. It also provides +routines to calculate summary statistics of the entire populatin of +tracked clouds in the cloud field like histograms of cloud areas/volumes +or cloud mass and a detailed cell lifetime analysis. These analysis +routines are all built in a modular manner. Thus, users can reuse the +most basic methods for interacting with the data structure of the +package in their own analysis procedures in Python. This includes +functions perfomring simple tasks like looping over all identified +objects or cloud trajectories and masking arrays for the analysis of +individual cloud objects. Plotting routines include both visualizations +for individual convective cells and their properties. [1]_ + +References +---------- +.. [1] Heikenfeld, M., Marinescu, P. J., Christensen, M., Watson-Parris, + D., Senf, F., van den Heever, S. C., and Stier, P.: tobac v1.0: + towards a flexible framework for tracking and analysis of clouds in + diverse datasets, Geosci. Model Dev. Discuss., + https://doi.org/10.5194/gmd-2019-105 , in review, 2019, 10. + +Notes +----- +unsure about page numer in the reference +""" + import pandas as pd import numpy as np import logging @@ -19,6 +47,49 @@ def cell_statistics_all( dimensions=["x", "y"], **kwargs ): + """ + Parameters + ---------- + input_cubes : iris.cube.Cube + + track : dask.dataframe.DataFrame + + mask : iris.cube.Cube + Cube containing mask (int id for tracked volumes 0 everywhere + else). + + aggregators : list + list of iris.analysis.Aggregator instances + + output_path : str, optional + Default is './'. + + cell_selection : optional + Default is None. + + output_name : str, optional + Default is 'Profiles'. + + width : int, optional + Default is 10000. + + z_coord : str, optional + Name of the vertical coordinate in the cube. Default is + 'model_level_number'. + + dimensions : list of str, optional + Default is ['x', 'y']. + + **kwargs + + Returns + ------- + None + + Notes + ----- + Not sure what this function does + """ if cell_selection is None: cell_selection = np.unique(track["cell"]) for cell in cell_selection: @@ -50,6 +121,50 @@ def cell_statistics( dimensions=["x", "y"], **kwargs ): + """ + Parameters + ---------- + input_cubes : iris.cube.Cube + + track : dask.dataframe.DataFrame + + mask : iris.cube.Cube + Cube containing mask (int id for tracked volumes 0 everywhere + else). + + aggregators list + list of iris.analysis.Aggregator instances + + cell : int + Integer id of cell to create masked cube for output. + + output_path : str, optional + Default is './'. + + output_name : str, optional + Default is 'Profiles'. + + width : int, optional + Default is 10000. + + z_coord : str, optional + Name of the vertical coordinate in the cube. Default is + 'model_level_number'. + + dimensions : list of str, optional + Default is ['x', 'y']. + + **kwargs + + Returns + ------- + None + + Notes + ----- + Not sure what this function does + """ + from iris.cube import Cube, CubeList from iris.coords import AuxCoord from iris import Constraint, save @@ -180,7 +295,36 @@ def cog_cell( Mask=None, savedir=None, ): - + """ + Parameters + ---------- + cell : int + Integer id of cell to create masked cube for output. + + Tracks : optional + Default is None. + + M_total : subset of cube, optional + Default is None. + + M_liquid : subset of cube, optional + Default is None. + + M_frozen : subset of cube, optional + Default is None. + + savedir : str + Default is None. + + Returns + ------- + None + + Notes + ----- + Not sure what this function does + """ + from iris import Constraint logging.debug("Start calculating COG for " + str(cell)) @@ -227,6 +371,46 @@ def cog_cell( def lifetime_histogram( Track, bin_edges=np.arange(0, 200, 20), density=False, return_values=False ): + """Compute the lifetime histogram of linked features. + + Parameters + ---------- + Track : pandas.DataFrame + Dataframe of linked features, containing the columns 'cell' + and 'time_cell'. + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of equal-width + bins in the given range. If bins is a ndarray, it defines a + monotonically increasing array of bin edges, including the + rightmost edge. Default is np.arange(0, 200, 20). + + density : bool, optional + If False, the result will contain the number of samples in + each bin. If True, the result is the value of the probability + density function at the bin, normalized such that the integral + over the range is 1. Default is False. + + return_values : bool, optional + Bool determining wether the lifetimes of the features are + returned from this function. Default is False. + + Returns + ------- + hist : ndarray + The values of the histogram. + + bin_edges : ndarray + The edges of the histogram. + + bin_centers : ndarray + The centers of the histogram intervalls. + + minutes, optional : ndarray + Numpy.array of the lifetime of each feature in minutes. + + """ + Track_cell = Track.groupby("cell") minutes = (Track_cell["time_cell"].max() / pd.Timedelta(minutes=1)).values hist, bin_edges = np.histogram(minutes, bin_edges, density=density) @@ -238,13 +422,27 @@ def lifetime_histogram( def haversine(lat1, lon1, lat2, lon2): - """Computes the Haversine distance in kilometres between two points (based on implementation CIS https://github.com/cedadev/cis) - :param lat1: first point or points as array, each as array of latitude in degrees - :param lon1: first point or points as array, each as array of longitude in degrees - :param lat2: second point or points as array, each as array of latitude in degrees - :param lon2: second point or points as array, each as array of longitude in degrees - :return: distance between the two points in kilometres + """Computes the Haversine distance in kilometers. + + Calculates the Haversine distance between two points + (based on implementation CIS https://github.com/cedadev/cis). + + Parameters + ---------- + lat1, lon1 : array of latitude, longitude + First point or points as array in degrees. + + lat2, lon2 : array of latitude, longitude + Second point or points as array in degrees. + + Returns + ------- + arclen * RADIUS_EARTH : array + Array of Distance(s) between the two points(-arrays) in + kilometers. + """ + RADIUS_EARTH = 6378.0 lat1 = np.radians(lat1) lat2 = np.radians(lat2) @@ -261,10 +459,31 @@ def haversine(lat1, lon1, lat2, lon2): def calculate_distance(feature_1, feature_2, method_distance=None): - """Computes distance between two features based on either lat/lon coordinates or x/y coordinates - :param feature_1: first feature or points as array, each as array of latitude, longitude in degrees - :param feature_2: second feature or points as array, each as array of latitude, longitude in degrees - :return: distance between the two features in metres + """Compute the distance between two features. It is based on + either lat/lon coordinates or x/y coordinates. + + Parameters + ---------- + feature_1, feature_2 : pandas.DataFrame or pandas.Series + Dataframes containing multiple features or pandas.Series + of one feature. Need to contain either projection_x_coordinate + and projection_y_coordinate or latitude and longitude + coordinates. + + method_distance : {None, 'xy', 'latlon'}, optional + Method of distance calculation. 'xy' uses the length of the + vector between the two features, 'latlon' uses the haversine + distance. None checks wether the required coordinates are + present and starts with 'xy'. Default is None. + + Returns + ------- + distance : float or pandas.Series + Float with the distance between the two features in meters if + the input are two pandas.Series containing one feature, + pandas.Series of the distancesif one of the inputs contains + multiple features. + """ if method_distance is None: if ( @@ -312,6 +531,34 @@ def calculate_distance(feature_1, feature_2, method_distance=None): def calculate_velocity_individual(feature_old, feature_new, method_distance=None): + """Calculate the mean velocity of a feature between two timeframes. + + Parameters + ---------- + feature_old : pandas.Series + pandas.Series of a feature at a certain timeframe. Needs to + contain a 'time' column and either projection_x_coordinate + and projection_y_coordinate or latitude and longitude coordinates. + + feature_new : pandas.Series + pandas.Series of the same feature at a later timeframe. Needs + to contain a 'time' column and either projection_x_coordinate + and projection_y_coordinate or latitude and longitude coordinates. + + method_distance : {None, 'xy', 'latlon'}, optional + Method of distance calculation, used to calculate the velocit. + 'xy' uses the length of the vector between the two features, + 'latlon' uses the haversine distance. None checks wether the + required coordinates are present and starts with 'xy'. + Default is None. + + Returns + ------- + velocity : float + Value of the approximate velocity. + + """ + distance = calculate_distance( feature_old, feature_new, method_distance=method_distance ) @@ -321,6 +568,34 @@ def calculate_velocity_individual(feature_old, feature_new, method_distance=None def calculate_velocity(track, method_distance=None): + """Calculate the velocities of a set of linked features. + + Parameters + ---------- + track : pandas.DataFrame + Dataframe of linked features, containing the columns 'cell', + 'time' and either 'projection_x_coordinate' and + 'projection_y_coordinate' or 'latitude' and 'longitude'. + + method_distance : {None, 'xy', 'latlon'}, optional + Method of distance calculation, used to calculate the + velocity. 'xy' uses the length of the vector between the + two features, 'latlon' uses the haversine distance. None + checks wether the required coordinates are present and + starts with 'xy'. Default is None. + + Returns + ------- + track : pandas.DataFrame + DataFrame from the input, with an additional column 'v', + contain the value of the velocity for every feature at + every possible timestep + + Notes + ----- + needs short summary, description and type of track + """ + for cell_i, track_i in track.groupby("cell"): index = track_i.index.values for i, index_i in enumerate(index[:-1]): @@ -340,6 +615,52 @@ def velocity_histogram( method_distance=None, return_values=False, ): + """Create an velocity histogram of the features. If the DataFrame + does not contain a velocity column, the velocities are calculated. + + Parameters + ---------- + track: pandas.DataFrame + DataFrame of the linked features, containing the columns 'cell', + 'time' and either 'projection_x_coordinate' and + 'projection_y_coordinate' or 'latitude' and 'longitude'. + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of equal-width + bins in the given range. If bins is a ndarray, it defines a + monotonically increasing array of bin edges, including the + rightmost edge. Default is np.arange(0, 30000, 500). + + density : bool, optional + If False, the result will contain the number of samples in + each bin. If True, the result is the value of the probability + density function at the bin, normalized such that the integral + over the range is 1. Default is False. + + methods_distance : {None, 'xy', 'latlon'}, optional + Method of distance calculation, used to calculate the velocity. + 'xy' uses the length of the vector between the two features, + 'latlon' uses the haversine distance. None checks wether the + required coordinates are present and starts with 'xy'. + Default is None. + + return_values : bool, optional + Bool determining wether the velocities of the features are + returned from this function. Default is False. + + Returns + ------- + hist : ndarray + The values of the histogram. + + bin_edges : ndarray + The edges of the histogram. + + velocities , optional : ndarray + Numpy array with the velocities of each feature. + + """ + if "v" not in track.columns: logging.info("calculate velocities") track = calculate_velocity(track) @@ -354,6 +675,30 @@ def velocity_histogram( def calculate_nearestneighbordistance(features, method_distance=None): + """Calculate the distance between a feature and the nearest other + feature in the same timeframe. + + Parameters + ---------- + features : pandas.DataFrame + DataFrame of the features whose nearest neighbor distance is to + be calculated. Needs to contain either projection_x_coordinate + and projection_y_coordinate or latitude and longitude coordinates. + + method_distance : {None, 'xy', 'latlon'}, optional + Method of distance calculation. 'xy' uses the length of the vector + between the two features, 'latlon' uses the haversine distance. + None checks wether the required coordinates are present and starts + with 'xy'. Default is None. + + Returns + ------- + features : pandas.DataFrame + DataFrame of the features with a new column 'min_distance', + containing the calculated minimal distance to other features. + + """ + from itertools import combinations features["min_distance"] = np.nan @@ -393,6 +738,49 @@ def nearestneighbordistance_histogram( method_distance=None, return_values=False, ): + """Create an nearest neighbor distance histogram of the features. + If the DataFrame does not contain a 'min_distance' column, the + distances are calculated. + + ---------- + features + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of equal-width + bins in the given range. If bins is a ndarray, it defines a + monotonically increasing array of bin edges, including the + rightmost edge. Default is np.arange(0, 30000, 500). + + density : bool, optional + If False, the result will contain the number of samples in + each bin. If True, the result is the value of the probability + density function at the bin, normalized such that the integral + over the range is 1. Default is False. + + method_distance : {None, 'xy', 'latlon'}, optional + Method of distance calculation. 'xy' uses the length of the + vector between the two features, 'latlon' uses the haversine + distance. None checks wether the required coordinates are + present and starts with 'xy'. Default is None. + + return_values : bool, optional + Bool determining wether the nearest neighbor distance of the + features are returned from this function. Default is False. + + Returns + ------- + hist : ndarray + The values of the histogram. + + bin_edges : ndarray + The edges of the histogram. + + distances, optional : ndarray + A numpy array with the nearest neighbor distances of each + feature. + + """ + if "min_distance" not in features.columns: logging.debug("calculate nearest neighbor distances") features = calculate_nearestneighbordistance( @@ -410,34 +798,34 @@ def nearestneighbordistance_histogram( # Treatment of 2D lat/lon coordinates to be added: def calculate_areas_2Dlatlon(_2Dlat_coord, _2Dlon_coord): - """ - Calculate an array of cell areas when given two 2D arrays of latitude and - longitude values + """Calculate an array of cell areas when given two 2D arrays + of latitude and longitude values - NOTE: This currently assuems that the lat/lon grid is orthogonal, which is - not strictly true! It's close enough for most cases, but should be updated - in future to use the cross product of the distances to the neighbouring - cells. This will require the use of a more advanced calculation. I would - advise using pyproj at some point in the future to solve this issue and - replace haversine distance. + NOTE: This currently assuems that the lat/lon grid is orthogonal, + which is not strictly true! It's close enough for most cases, but + should be updated in future to use the cross product of the + distances to the neighbouring cells. This will require the use + of a more advanced calculation. I would advise using pyproj + at some point in the future to solve this issue and replace + haversine distance. Parameters ---------- _2Dlat_coord : AuxCoord - Iris auxilliary coordinate containing a 2d grid of latitudes for each - point + Iris auxilliary coordinate containing a 2d grid of latitudes + for each point. _2Dlon_coord : AuxCoord - Iris auxilliary coordinate containing a 2d grid of longitudes for each - point - + Iris auxilliary coordinate containing a 2d grid of longitudes + for each point. Returns ------- area : ndarray - A numpy array approximating the area of each cell + A numpy array approximating the area of each cell. """ + hdist1 = ( haversine( _2Dlat_coord.points[:-1], @@ -474,6 +862,48 @@ def calculate_areas_2Dlatlon(_2Dlat_coord, _2Dlon_coord): def calculate_area(features, mask, method_area=None): + """Calculate the area of the segments for each feature. + + Parameters + ---------- + features : pandas.DataFrame + DataFrame of the features whose area is to be calculated. + + mask : iris.cube.Cube + Cube containing mask (int for tracked volumes 0 everywhere + else). Needs to contain either projection_x_coordinate and + projection_y_coordinate or latitude and longitude + coordinates. + + method_area : {None, 'xy', 'latlon'}, optional + Flag determining how the area is calculated. 'xy' uses the + areas of the individual pixels, 'latlon' uses the + area_weights method of iris.analysis.cartography, None + checks wether the required coordinates are present and + starts with 'xy'. Default is None. + + Returns + ------- + features : pandas.DataFrame + DataFrame of the features with a new column 'area', + containing the calculated areas. + + Raises + ------ + ValueError + If neither latitude/longitude nor + projection_x_coordinate/projection_y_coordinate are + present in mask_coords. + + If latitude/longitude coordinates are 2D. + + If latitude/longitude shapes are not supported. + + If method is undefined, i.e. method is neither None, + 'xy' nor 'latlon'. + + """ + from tobac.utils import mask_features_surface, mask_features from iris import Constraint from iris.analysis.cartography import area_weights @@ -541,6 +971,61 @@ def area_histogram( return_values=False, representative_area=False, ): + """Create an area histogram of the features. If the DataFrame + does not contain an area column, the areas are calculated. + + Parameters + ---------- + features : pandas.DataFrame + DataFrame of the features. + + mask : iris.cube.Cube + Cube containing mask (int for tracked volumes 0 + everywhere else). Needs to contain either + projection_x_coordinate and projection_y_coordinate or + latitude and longitude coordinates. The output of a + segmentation should be used here. + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is a ndarray, + it defines a monotonically increasing array of bin edges, + including the rightmost edge. + Default is np.arange(0, 30000, 500). + + density : bool, optional + If False, the result will contain the number of samples + in each bin. If True, the result is the value of the + probability density function at the bin, normalized such + that the integral over the range is 1. Default is False. + + return_values : bool, optional + Bool determining wether the areas of the features are + returned from this function. Default is False. + + representive_area: bool, optional + If False, no weights will associated to the values. + If True, the weights for each area will be the areas + itself, i.e. each bin count will have the value of + the sum of all areas within the edges of the bin. + Default is False. + + Returns + ------- + hist : ndarray + The values of the histogram. + + bin_edges : ndarray + The edges of the histogram. + + bin_centers : ndarray + The centers of the histogram intervalls. + + areas : ndarray, optional + A numpy array approximating the area of each feature. + + """ + if "area" not in features.columns: logging.info("calculate area") features = calculate_area(features, mask, method_area) @@ -563,6 +1048,56 @@ def area_histogram( def histogram_cellwise( Track, variable=None, bin_edges=None, quantity="max", density=False ): + """Create a histogram of the maximum, minimum or mean of + a variable for the cells of a track. Essentially a wrapper + of the numpy.histogram() method. + + Parameters + ---------- + Track : pandas.DataFrame + The track containing the variable to create the histogram + from. + + variable : string, optional + Column of the DataFrame with the variable on which the + histogram is to be based on. Default is None. + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is a ndarray, + it defines a monotonically increasing array of bin edges, + including the rightmost edge. + + quantity : {'max', 'min', 'mean'}, optional + Flag determining wether to use maximum, minimum or mean + of a variable from all timeframes the cell covers. + Default is 'max'. + + density : bool, optional + If False, the result will contain the number of samples + in each bin. If True, the result is the value of the + probability density function at the bin, normalized such + that the integral over the range is 1. + Default is False. + + Returns + ------- + hist : ndarray + The values of the histogram + + bin_edges : ndarray + The edges of the histogram + + bin_centers : ndarray + The centers of the histogram intervalls + + Raises + ------ + ValueError + If quantity is not 'max', 'min' or 'mean'. + + """ + Track_cell = Track.groupby("cell") if quantity == "max": variable_cell = Track_cell[variable].max().values @@ -579,6 +1114,46 @@ def histogram_cellwise( def histogram_featurewise(Track, variable=None, bin_edges=None, density=False): + """Create a histogram of a variable from the features of a + track. Essentially a wrapper of the numpy.histogram() + method. + + Parameters + ---------- + Track : pandas.DataFrame + The track containing the variable to create the + histogram from. + + variable : string, optional + Column of the DataFrame with the variable on which the + histogram is to be based on. Default is None. + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is + a sequence, it defines a monotonically increasing + array of bin edges, including the rightmost edge. + + density : bool, optional + If False, the result will contain the number of + samples in each bin. If True, the result is the + value of the probability density function at the + bin, normalized such that the integral over the + range is 1. Default is False. + + Returns + ------- + hist : ndarray + The values of the histogram + + bin_edges : ndarray + The edges of the histogram + + bin_centers : ndarray + The centers of the histogram intervalls + + """ + hist, bin_edges = np.histogram(Track[variable].values, bin_edges, density=density) bin_centers = bin_edges[:-1] + 0.5 * np.diff(bin_edges) @@ -588,6 +1163,36 @@ def histogram_featurewise(Track, variable=None, bin_edges=None, density=False): def calculate_overlap( track_1, track_2, min_sum_inv_distance=None, min_mean_inv_distance=None ): + """Count the number of time frames in which the + individual cells of two tracks are present together + and calculate their mean and summed inverse distance. + + Parameters + ---------- + track_1, track_2 : pandas.DataFrame + The tracks conaining the cells to analyze. + + min_sum_inv_distance : float, optional + Minimum of the inverse net distance for two + cells to be counted as overlapping. + Default is None. + + min_mean_inv_distance : float, optional + Minimum of the inverse mean distance for two cells + to be counted as overlapping. Default is None. + + Returns + ------- + overlap : pandas.DataFrame + DataFrame containing the columns cell_1 and cell_2 + with the index of the cells from the tracks, + n_overlap with the number of frames both cells are + present in, mean_inv_distance with the mean inverse + distance and sum_inv_distance with the summed + inverse distance of the cells. + + """ + cells_1 = track_1["cell"].unique() # n_cells_1_tot=len(cells_1) cells_2 = track_2["cell"].unique() From 2e180c4db9f15dfd6eefe75622547815868dd2ab Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 9 Jun 2022 08:41:41 +0200 Subject: [PATCH 015/187] segmentation.py revised --- tobac/segmentation.py | 235 +++++++++++++++++++++++++++++++++--------- 1 file changed, 186 insertions(+), 49 deletions(-) diff --git a/tobac/segmentation.py b/tobac/segmentation.py index 2a76ebc6..9cbd6c8c 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -1,3 +1,36 @@ +"""Provide segmentation techniques. + +Segmentation techniques are used to associate areas or volumes to each +identified feature. The segmentation is implemented using watershedding +techniques from the field of image processing with a fixed threshold +value. This value has to be set specifically for every type of input +data and application. The segmentation can be performed for both +two-dimensional and three-dimensional data. At each timestep, a marker +is set at the position (weighted mean center) of each feature identified +in the detection step in an array otherwise filled with zeros. In case +of the three-dimentional watershedding, all cells in the column above +the weighted mean center position of the identified features fulfilling +the threshold condition are set to the respective marker. The algorithm +then fills the area (2D) or volume (3D) based on the input field +starting from these markers until reaching the threshold. If two or more +cloud objects are directly connected, the border runs along the +watershed line between the two regions. This procedure creates a mask of +the same shape as the input data, with zeros at all grid points where +there is no cloud or updraft and the integer number of the associated +feature at all grid points belonging to that specific cloud/updraft. +this mask can be conveniently and efficiently used to select the volume +of each cloud object at a specific time step for further analysis or +visialization. [4]_ + +References +---------- +.. [4] Heikenfeld, M., Marinescu, P. J., Christensen, M., Watson-Parris, + D., Senf, F., van den Heever, S. C., and Stier, P.: tobac v1.0: + towards a flexible framework for tracking and analysis of clouds in + diverse datasets, Geosci. Model Dev. Discuss., + https://doi.org/10.5194/gmd-2019-105 , in review, 2019, 7ff. +""" + import logging @@ -11,6 +44,14 @@ def segmentation_3D( method="watershed", max_distance=None, ): + """Wraper for the segmentation()-function. + + Notes + ---------- + + Obsolete? + """ + return segmentation( features, field, @@ -33,6 +74,13 @@ def segmentation_2D( method="watershed", max_distance=None, ): + """Wraper for the segmentation()-function. + + Notes + ---------- + + Obsolete? + """ return segmentation( features, field, @@ -56,31 +104,73 @@ def segmentation_timestep( max_distance=None, vertical_coord="auto", ): + """Perform watershedding for an individual time step of the data. Works + for both 2D and 3D data + + Parameters + ---------- + field_in : iris.cube.Cube + Input field to perform the watershedding on (2D or 3D for one + specific point in time). + + features_in : pandas.DataFrame + Features for one specific point in time. + + dxy : float + Grid spacing of the input data in metres + + threshold : float, optional + Threshold for the watershedding field to be used for the mask. + Default is 3e-3. + + target : {'maximum', 'minimum'}, optional + Flag to determine if tracking is targetting minima or maxima in + the data to determine from which direction to approach the threshold + value. Default is 'maximum'. + + level : slice of iris.cube.Cube, optional + Levels at which to seed the cells for the watershedding + algorithm. Default is None. + + method : {'watershed'}, optional + Flag determining the algorithm to use (currently watershedding + implemented). 'random_walk' could be uncommented. + + max_distance : float, optional + Maximum distance from a marker allowed to be classified as + belonging to that cell. Default is None. + + vertical_coord : str, optional + Vertical coordinate in 3D input data. If 'auto', input is checked for + one of {'z', 'model_level_number', 'altitude','geopotential_height'} + as a likely coordinate name + + Returns + ------- + segmentation_out : iris.cube.Cube + Cloud mask, 0 outside and integer numbers according to track + inside the clouds. + + features_out : pandas.DataFrame + Feature dataframe including the number of cells (2D or 3D) in + the segmented area/volume of the feature at the timestep. + + Raises + ------ + ValueError + If target is neither 'maximum' nor 'minimum'. + + If vertical_coord is not in {'auto', 'z', 'model_level_number', + 'altitude', geopotential_height'}. + + If there is more than one coordinate name. + + If the spatial dimension is neither 2 nor 3. + + If method is not 'watershed'. + """ - Function performing watershedding for an individual timestep of the data - - Parameters: - features: pandas.DataFrame - features for one specific point in time - field: iris.cube.Cube - input field to perform the watershedding on (2D or 3D for one specific point in time) - threshold: float - threshold for the watershedding field to be used for the mas - target: string - switch to determine if algorithm looks strating from maxima or minima in input field (maximum: starting from maxima (default), minimum: starting from minima) - level slice - vertical levels at which to seed the cells for the watershedding algorithm - method: string - flag determining the algorithm to use (currently watershedding implemented) - max_distance: float - maximum distance from a marker allowed to be classified as belonging to that cell - - Output: - segmentation_out: iris.cube.Cube - cloud mask, 0 outside and integer numbers according to track inside the clouds - features_out: pandas.DataFrame - feature dataframe including the number of cells (2D or 3D) in the segmented area/volume of the feature at the timestep - """ + # The location of watershed within skimage submodules changes with v0.19, but I've kept both for backward compatibility for now try: from skimage.segmentation import watershed @@ -210,31 +300,64 @@ def segmentation( max_distance=None, vertical_coord="auto", ): - """ - Function using watershedding or random walker to determine cloud volumes associated with tracked updrafts - - Parameters: - features: pandas.DataFrame - output from trackpy/maketrack - field: iris.cube.Cube - containing the field to perform the watershedding on - threshold: float - threshold for the watershedding field to be used for the mask - - target: string - Switch to determine if algorithm looks strating from maxima or minima in input field (maximum: starting from maxima (default), minimum: starting from minima) - - level slice - levels at which to seed the cells for the watershedding algorithm - method: str ('method') - flag determining the algorithm to use (currently watershedding implemented) - - max_distance: float - Maximum distance from a marker allowed to be classified as belonging to that cell - - Output: - segmentation_out: iris.cube.Cube - Cloud mask, 0 outside and integer numbers according to track inside the cloud + """Use watershedding or random walker technique to determine region above + a threshold value around initial seeding position for all time steps of + the input data. Works both in 2D (based on single seeding point) and 3D and + returns a mask with zeros everywhere around the identified regions and the + feature id inside the regions. + + Calls segmentation_timestep at each individal timestep of the input data. + + Parameters + ---------- + features : pandas.DataFrame + Output from trackpy/maketrack. + + field : iris.cube.Cube + Containing the field to perform the watershedding on. + + dxy : float + Grid spacing of the input data. + + threshold : float, optional + Threshold for the watershedding field to be used for the mask. + Default is 3e-3. + + target : {'maximum', 'minimum'}, optional + Flag to determine if tracking is targetting minima or maxima in + the data. Default is 'maximum'. + + level : slice of iris.cube.Cube, optional + Levels at which to seed the cells for the watershedding + algorithm. Default is None. + + method : {'watershed'}, optional + Flag determining the algorithm to use (currently watershedding + implemented). 'random_walk' could be uncommented. + + max_distance : float, optional + Maximum distance from a marker allowed to be classified as + belonging to that cell. Default is None. + + vertical_coord : {'auto', 'z', 'model_level_number', 'altitude', + 'geopotential_height'}, optional + Name of the vertical coordinate for use in 3D segmentation case + + Returns + ------- + segmentation_out : iris.cube.Cube + Cloud mask, 0 outside and integer numbers according to track + inside the clouds. + + features_out : pandas.DataFrame + Feature dataframe including the number of cells (2D or 3D) in + the segmented area/volume of the feature at the timestep. + + Raises + ------ + ValueError + If field_in.ndim is neither 3 nor 4 and 'time' is not included + in coords. """ import pandas as pd from iris.cube import CubeList @@ -286,10 +409,24 @@ def segmentation( def watershedding_3D(track, field_in, **kwargs): + """Wraper for the segmentation()-function. + + Notes + ---------- + + Obsolete? + """ kwargs.pop("method", None) return segmentation_3D(track, field_in, method="watershed", **kwargs) def watershedding_2D(track, field_in, **kwargs): + """Wraper for the segmentation()-function. + + Notes + ---------- + + Obsolete? + """ kwargs.pop("method", None) return segmentation_2D(track, field_in, method="watershed", **kwargs) From 629b31a521220c98f5379b285805e96d868722f2 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 9 Jun 2022 08:45:43 +0200 Subject: [PATCH 016/187] testing.py docstrings revised --- tobac/testing.py | 241 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 176 insertions(+), 65 deletions(-) diff --git a/tobac/testing.py b/tobac/testing.py index 36338a44..413e8dfc 100644 --- a/tobac/testing.py +++ b/tobac/testing.py @@ -1,3 +1,7 @@ +"""Containing methods to make simple sample data for testing. + +""" + import datetime import numpy as np from xarray import DataArray @@ -5,17 +9,28 @@ def make_simple_sample_data_2D(data_type="iris"): - """ - function creating a simple dataset to use in tests for tobac. - The grid has a grid spacing of 1km in both horizontal directions and 100 grid cells in x direction and 500 in y direction. - Time resolution is 1 minute and the total length of the dataset is 100 minutes around a abritraty date (2000-01-01 12:00). - The longitude and latitude coordinates are added as 2D aux coordinates and arbitrary, but in realisitic range. - The data contains a single blob travelling on a linear trajectory through the dataset for part of the time. + """Create a simple dataset to use in tests. - :param data_type: 'iris' or 'xarray' to chose the type of dataset to produce - :return: sample dataset as an Iris.Cube.cube or xarray.DataArray + The grid has a grid spacing of 1km in both horizontal directions + and 100 grid cells in x direction and 500 in y direction. + Time resolution is 1 minute and the total length of the dataset is + 100 minutes around a abritraty date (2000-01-01 12:00). + The longitude and latitude coordinates are added as 2D aux + coordinates and arbitrary, but in realisitic range. + The data contains a single blob travelling on a linear trajectory + through the dataset for part of the time. + + Parameters + ---------- + data_type : {'iris', 'xarray'}, optional + Choose type of the dataset that will be produced. + Default is 'iris' + Returns + ------- + sample_data : iris.cube.Cube or xarray.DataArray """ + from iris.cube import Cube from iris.coords import DimCoord, AuxCoord @@ -79,20 +94,30 @@ def make_simple_sample_data_2D(data_type="iris"): def make_sample_data_2D_3blobs(data_type="iris"): - from iris.cube import Cube - from iris.coords import DimCoord, AuxCoord - - """ - function creating a simple dataset to use in tests for tobac. - The grid has a grid spacing of 1km in both horizontal directions and 100 grid cells in x direction and 200 in y direction. - Time resolution is 1 minute and the total length of the dataset is 100 minutes around a abritraty date (2000-01-01 12:00). - The longitude and latitude coordinates are added as 2D aux coordinates and arbitrary, but in realisitic range. - The data contains a three individual blobs travelling on a linear trajectory through the dataset for part of the time. + """Create a simple dataset to use in tests. + + The grid has a grid spacing of 1km in both horizontal directions + and 100 grid cells in x direction and 200 in y direction. + Time resolution is 1 minute and the total length of the dataset is + 100 minutes around a abritraty date (2000-01-01 12:00). + The longitude and latitude coordinates are added as 2D aux + coordinates and arbitrary, but in realisitic range. + The data contains three individual blobs travelling on a linear + trajectory through the dataset for part of the time. - :param data_type: 'iris' or 'xarray' to chose the type of dataset to produce - :return: sample dataset as an Iris.Cube.cube or xarray.DataArray + Parameters + ---------- + data_type : {'iris', 'xarray'}, optional + Choose type of the dataset that will be produced. + Default is 'iris' + Returns + ------- + sample_data : iris.cube.Cube or xarray.DataArray """ + + from iris.cube import Cube + from iris.coords import DimCoord, AuxCoord t_0 = datetime.datetime(2000, 1, 1, 12, 0, 0) @@ -183,14 +208,24 @@ def make_sample_data_2D_3blobs(data_type="iris"): def make_sample_data_2D_3blobs_inv(data_type="iris"): - """ - function creating a version of the dataset created in the function make_sample_cube_2D, but with switched coordinate order for the horizontal coordinates - for tests to ensure that this does not affect the results + """Create a version of the dataset with switched coordinates. - :param data_type: 'iris' or 'xarray' to chose the type of dataset to produce - :return: sample dataset as an Iris.Cube.cube or xarray.DataArray + Create a version of the dataset created in the function + make_sample_cube_2D, but with switched coordinate order for the + horizontal coordinates for tests to ensure that this does not + affect the results. + + Parameters + ---------- + data_type : {'iris', 'xarray'}, optional + Choose type of the dataset that will be produced. + Default is 'iris' + Returns + ------- + sample_data : iris.cube.Cube or xarray.DataArray """ + from iris.cube import Cube from iris.coords import DimCoord, AuxCoord @@ -285,20 +320,34 @@ def make_sample_data_2D_3blobs_inv(data_type="iris"): def make_sample_data_3D_3blobs(data_type="iris", invert_xy=False): - from iris.cube import Cube - from iris.coords import DimCoord, AuxCoord + """Create a simple dataset to use in tests. - """ - function creating a simple dataset to use in tests for tobac. - The grid has a grid spacing of 1km in both horizontal directions and 100 grid cells in x direction and 200 in y direction. - Time resolution is 1 minute and the total length of the dataset is 100 minutes around a abritraty date (2000-01-01 12:00). - The longitude and latitude coordinates are added as 2D aux coordinates and arbitrary, but in realisitic range. - The data contains a three individual blobs travelling on a linear trajectory through the dataset for part of the time. - - :param data_type: 'iris' or 'xarray' to chose the type of dataset to produce - :return: sample dataset as an Iris.Cube.cube or xarray.DataArray + The grid has a grid spacing of 1km in both horizontal directions + and 100 grid cells in x direction and 200 in y direction. + Time resolution is 1 minute and the total length of the dataset is + 100 minutes around a abritraty date (2000-01-01 12:00). + The longitude and latitude coordinates are added as 2D aux + coordinates and arbitrary, but in realisitic range. + The data contains three individual blobs travelling on a linear + trajectory through the dataset for part of the time. + + Parameters + ---------- + data_type : {'iris', 'xarray'}, optional + Choose type of the dataset that will be produced. + Default is 'iris' + + invert_xy : bool, optional + Flag to determine wether to switch x and y coordinates + Default is False + Returns + ------- + sample_data : iris.cube.Cube or xarray.DataArray """ + + from iris.cube import Cube + from iris.coords import DimCoord, AuxCoord t_0 = datetime.datetime(2000, 1, 1, 12, 0, 0) @@ -438,22 +487,33 @@ def make_dataset_from_arr( ---------- in_arr: array-like The input array to convert to iris/xarray - data_type: str('xarray' or 'iris') + + data_type: str('xarray' or 'iris'), optional Type of the dataset to return - time_dim_num: int or None + Default is 'xarray' + + time_dim_num: int or None, optional What axis is the time dimension on, None for a single timestep - z_dim_num: int or None + Default is None + + z_dim_num: int or None, optional What axis is the z dimension on, None for a 2D array - y_dim_num: int + Default is None + + y_dim_num: int, optional What axis is the y dimension on, typically 0 for a 2D array - x_dim_num: int + Default is 0 + + x_dim_num: int, optional What axis is the x dimension on, typically 1 for a 2D array + Deafult is 1 Returns ------- Iris or xarray dataset with everything we need for feature detection/tracking. """ + import xarray as xr import iris @@ -490,8 +550,6 @@ def make_feature_blob( shape="rectangle", amplitude=1, ): - import xarray as xr - """Function to make a defined "blob" in location (zloc, yloc, xloc) with user-specified shape and amplitude. Note that this function will round the size and locations to the nearest point within the array. @@ -500,30 +558,46 @@ def make_feature_blob( ---------- in_arr: array-like input array to add the "blob" to + h1_loc: float Center hdim_1 location of the blob, required + h2_loc: float Center hdim_2 location of the blob, required - v_loc: float + + v_loc: float, optional Center vdim location of the blob, optional. If this is None, we assume that the dataset is 2D. - h1_size: float + Default is None + + h1_size: float, optional Size of the bubble in array coordinates in hdim_1 - h2_size: float + Default is 1 + + h2_size: float, optional Size of the bubble in array coordinates in hdim_2 - v_size: float + Default is 1 + + v_size: float, optional Size of the bubble in array coordinates in vdim - shape: str('rectangle') + Default is 1 + + shape: str('rectangle'), optional The shape of the blob that is added. For now, this is just rectangle 'rectangle' adds a rectangular/rectangular prism bubble with constant amplitude `amplitude`. - amplitude: float + Default is "rectangle" + + amplitude: float, optional Maximum amplitude of the blob + Default is 1 Returns ------- array-like An array with the same type as `in_arr` that has the blob added. """ + + import xarray as xr # Check if z location is there and set our 3D-ness based on this. if v_loc is None: @@ -576,27 +650,37 @@ def set_arr_2D_3D( ---------- in_arr: array-like Array of values to set + value: int, float, or array-like of size (end_v-start_v, end_h1-start_h1, end_h2-start_h2) The value to assign to in_arr. This will work to assign an array, but the array must have the same dimensions as the size specified in the function. + start_h1: int Start index to set for hdim_1 + end_h1: int End index to set for hdim_1 (exclusive, so it acts like [start_h1:end_h1]) + start_h2: int Start index to set for hdim_2 + end_h2: int End index to set for hdim_2 - start_v: int - Start index to set for vdim (optional) - end_v: int - End index to set for vdim (optional) + + start_v: int, optional + Start index to set for vdim + Default is None + + end_v: int, optional + End index to set for vdim + Default is None Returns ------- array-like in_arr with the new values set. """ + if start_v is not None and end_v is not None: in_arr[start_v:end_v, start_h1:end_h1, start_h2:end_h2] = value else: @@ -628,37 +712,64 @@ def generate_single_feature( ---------- start_h1: float Starting point of the feature in hdim_1 space + start_h2: float Starting point of the feature in hdim_2 space - start_v: float + + start_v: float, optional Starting point of the feature in vdim space (if 3D). For 2D, set to None. - spd_h1: float + Default is None + + spd_h1: float, optional Speed (per frame) of the feature in hdim_1 - spd_h2: float + Default is 1 + + spd_h2: float, optional Speed (per frame) of the feature in hdim_2 - spd_v: float + Default is 1 + + spd_v: float, optional Speed (per frame) of the feature in vdim - min_h1: int + Default is 1 + + min_h1: int, optional Minimum value of hdim_1 allowed. If PBC_flag is not 'none', then this will be used to know when to wrap around periodic boundaries. If PBC_flag is 'none', features will disappear if they are above/below these bounds. - max_h1: int + Default is 0 + + max_h1: int, optional Similar to min_h1, but the max value of hdim_1 allowed. - min_h2: int + Default is 1000 + + min_h2: int, optional Similar to min_h1, but the minimum value of hdim_2 allowed. - max_h2: int + Default is 0 + + max_h2: int, optional Similar to min_h1, but the maximum value of hdim_2 allowed. - num_frames: int + Default is 1000 + + num_frames: int, optional Number of frames to generate - dt: datetime.timedelta + Default is 1 + + dt: datetime.timedelta, optional Difference in time between each frame - start_date: datetime.datetime + Default is datetime.timedelta(minutes=5) + + start_date: datetime.datetime, optional Start datetime - frame_start: int + Default is datetime.datetime(2022, 1, 1, 0) + + frame_start: int, optional Number to start the frame at - feature_num: int + Default is 1 + + feature_num: int, optional What number to start the feature at + Default is 1 """ out_list_of_dicts = list() From 66ac70bf2e0df7fd2e27f863560dbfa8668b959a Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 9 Jun 2022 08:48:30 +0200 Subject: [PATCH 017/187] tracking.py docstrings revised --- tobac/tracking.py | 206 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 162 insertions(+), 44 deletions(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index 227eed79..6daea5db 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -1,3 +1,23 @@ +"""Provide tracking methods. + +The individual features and associated area/volumes identified in +each timestep have to be linked into cloud trajectories to analyse +the time evolution of cloud properties for a better understanding of +the underlying pyhsical processes. [5]_ +The implementations are structured in a way that allows for the future +addition of more complex tracking methods recording a more complex +network of relationships between cloud objects at different points in +time. [5]_ + +References +---------- +.. [5] Heikenfeld, M., Marinescu, P. J., Christensen, M., Watson-Parris, + D., Senf, F., van den Heever, S. C., and Stier, P.: tobac v1.0: + towards a flexible framework for tracking and analysis of clouds in + diverse datasets, Geosci. Model Dev. Discuss., + https://doi.org/10.5194/gmd-2019-105 , in review, 2019, 9f. +""" + import logging import numpy as np import pandas as pd @@ -24,31 +44,119 @@ def linking_trackpy( cell_number_start=1, cell_number_unassigned=-1, ): - """ - Function to perform the linking of features in trajectories - - Parameters: - features: pandas.DataFrame - Detected features to be linked - v_max: float - speed at which features are allowed to move - dt: float - time resolution of tracked features - dxy: float - grid spacing of input data - memory int - number of output timesteps features allowed to vanish for to be still considered tracked - subnetwork_size int - maximim size of subnetwork for linking - method_detection: str('trackpy' or 'threshold') - flag choosing method used for feature detection - method_linking: str('predict' or 'random') - flag choosing method used for trajectory linking + + """Perform Linking of features in trajectories. + + The linking determines which of the features detected in a specific + timestep is identical to an existing feature in the previous + timestep. For each existing feature, the movement within a time step + is extrapolated based on the velocities in a number previous time + steps. The algorithm then breaks the search process down to a few + candidate features by restricting the search to a circular search + region centered around the predicted position of the feature in the + next time step. For newly initialized trajectories, where no + velocity from previous timesteps is available, the algorithm resort + to the average velocity of the nearest tracked objects. v_max and + d_min are given as physical quantities and then converted into + pixel-based values used in trackpy. This allows for cloud tracking + that is controlled by physically-based parameters that are + independent of the temporal and spatial resolution of the input + data. The algorithm creates a continuous track for the cloud that + most directly follows the direction of travel of the preceding or + following cell path. [5]_ + + Parameters + ---------- + features : xarray.Dataset + Detected features to be linked. + + field_in : xarray.DataArray + Input field to perform the watershedding on (2D or 3D for one + specific point in time). + + dt : float + Time resolution of tracked features. + + dxy : float + Grid spacing of the input data. + + d_max : float, optional + Maximum search range + Default is None. + + d_min : float, optional + Variations in the shape of the regions used to determine the + positions of the features can lead to quasi-instantaneous shifts + of the position of the feature by one or two grid cells even for + a very high temporal resolution of the input data, potentially + jeopardising the tracking procedure. To prevent this, tobac uses + an additional minimum radius of the search range. [5]_ + Default is None. + + subnetwork_size : int, optional + Maximum size of subnetwork for linking. Default is None. + + v_max : float, optional + Speed at which features are allowed to move. Default is None. + + memory : int, optional + Number of output timesteps features allowed to vanish for to + be still considered tracked. Default is 0. + .. warning :: This parameter should be used with caution, as it + can lead to erroneous trajectory linking, + espacially for data with low time resolution. [5]_ + + stubs : int, optional + Minimum number of timesteps of a tracked cell to be reported + Default is 1 + + time_cell_min : float, optional + Minimum length in time of tracked cell to be reported in minutes + Default is None. + + order : int, optional + Order of polynomial used to extrapolate trajectory into gaps and + ond start and end point. + Default is 1. + + extrapolate : int, optional + Number or timesteps to extrapolate trajectories. + Default is 0. + + method_linking : {'random', 'predict'}, optional + Flag choosing method used for trajectory linking. + Default is 'random'. + + adaptive_step : float, optional + Reduce search range by multiplying it by this factor. + + adaptive_stop : float, optional + If not None, when encountering an oversize subnet, retry by progressively + reducing search_range until the subnet is solvable. If search_range + becomes <= adaptive_stop, give up and raise a SubnetOversizeException. + Default is None + + cell_number_start : int, optional + Cell number for first tracked cell. + Default is 1 + cell_number_unassigned: int - Number to set the unassigned/non-tracked cells to. By default, this is -1. - Note that if you set this to `np.nan`, the data type of 'cell' will - change to float. + Number to set the unassigned/non-tracked cells to. Note that if you set this + to `np.nan`, the data type of 'cell' will change to float. + Default is -1 + + Returns + ------- + trajectories_final : xarray.Dataset + This enables filtering the resulting trajectories, e.g. to + reject trajectories that are only partially captured at the + boundaries of the input field both in space and time. [5]_ + Raises + ------ + ValueError + If method_linking is neither 'random' nor 'predict'. """ + # from trackpy import link_df import trackpy as tp from copy import deepcopy @@ -197,24 +305,34 @@ def linking_trackpy( def fill_gaps( t, order=1, extrapolate=0, frame_max=None, hdim_1_max=None, hdim_2_max=None ): - """add cell time as time since the initiation of each cell - Input: - t: pandas dataframe - trajectories from trackpy - order: int - Order of polynomial used to extrapolate trajectory into gaps and beyond start and end point - extrapolate int - number of timesteps to extrapolate trajectories by - frame_max: int - size of input data along time axis - hdim_1_max: int - size of input data along first horizontal axis - hdim_2_max: int - size of input data along second horizontal axis - Output: - t: pandas dataframe - trajectories from trackpy with with filled gaps and potentially extrapolated + """Add cell time as time since the initiation of each cell. + + Parameters + ---------- + t : pandas.DataFrame + Trajectories from trackpy. + + order : int, optional + Order of polynomial used to extrapolate trajectory into + gaps and beyond start and end point. Default is 1. + + extrapolate : int, optional + Number or timesteps to extrapolate trajectories. Default is 0. + + frame_max : int, optional + Size of input data along time axis. Default is None. + + hdim_1_max, hdim2_max : int, optional + Size of input data along first and second horizontal axis. + Default is None. + + Returns + ------- + t : pandas.DataFrame + Trajectories from trackpy with with filled gaps and potentially + extrapolated. """ + from scipy.interpolate import InterpolatedUnivariateSpline logging.debug("start filling gaps") @@ -271,13 +389,13 @@ def add_cell_time(t): Parameters ---------- - t: pandas DataFrame - trajectories with added coordinates + t : pandas DataFrame + trajectories with added coordinates Returns ------- - t: pandas dataframe - trajectories with added cell time + t : pandas dataframe + trajectories with added cell time """ # logging.debug('start adding time relative to cell initiation') From f2cc85fda8aefa1e1f9b42dfa9a34416485d4716 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 9 Jun 2022 08:50:27 +0200 Subject: [PATCH 018/187] centerofgravity.py docstrings revised --- tobac/centerofgravity.py | 124 ++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/tobac/centerofgravity.py b/tobac/centerofgravity.py index d7d5675d..895d1dad 100644 --- a/tobac/centerofgravity.py +++ b/tobac/centerofgravity.py @@ -1,19 +1,33 @@ +"""Identify center of gravity and mass for analysis. +""" + import logging def calculate_cog(tracks, mass, mask): - """caluclate centre of gravity and mass forech individual tracked cell in the simulation - Input: - tracks: pandas.DataFrame - DataFrame containing trajectories of cell centres - mass: iris.cube.Cube - cube of quantity (need coordinates 'time', 'geopotential_height','projection_x_coordinate' and 'projection_y_coordinate') - mask: iris.cube.Cube - cube containing mask (int > where belonging to cloud volume, 0 everywhere else ) - Output: - tracks_out pandas.DataFrame - Dataframe containing t,x,y,z positions of centre of gravity and total cloud mass each tracked cells at each timestep + """Calculate center of gravity and mass for each tracked cell. + + Parameters + ---------- + tracks : pandas.DataFrame + DataFrame containing trajectories of cell centers. + + mass : iris.cube.Cube + Cube of quantity (need coordinates 'time', + 'geopotential_height','projection_x_coordinate' and + 'projection_y_coordinate'). + + mask : iris.cube.Cube + Cube containing mask (int > where belonging to cloud volume, + 0 everywhere else). + + Returns + ------- + tracks_out : pandas.DataFrame + Dataframe containing t, x, y, z positions of center of gravity + and total cloud mass each tracked cells at each timestep. """ + from .utils import mask_cube_cell from iris import Constraint @@ -39,17 +53,26 @@ def calculate_cog(tracks, mass, mask): def calculate_cog_untracked(mass, mask): - """caluclate centre of gravity and mass for untracked parts of domain - Input: - mass: iris.cube.Cube - cube of quantity (need coordinates 'time', 'geopotential_height','projection_x_coordinate' and 'projection_y_coordinate') - - mask: iris.cube.Cube - cube containing mask (int > where belonging to cloud volume, 0 everywhere else ) - Output: - tracks_out pandas.DataFrame - Dataframe containing t,x,y,z positions of centre of gravity and total cloud mass for untracked part of dimain + """Calculate center of gravity and mass for untracked domain parts. + + Parameters + ---------- + mass : iris.cube.Cube + Cube of quantity (need coordinates 'time', + 'geopotential_height','projection_x_coordinate' and + 'projection_y_coordinate'). + + mask : iris.cube.Cube + Cube containing mask (int > where belonging to cloud volume, + 0 everywhere else). + + Returns + ------- + tracks_out : pandas.DataFrame + Dataframe containing t, x, y, z positions of center of gravity + and total cloud mass for untracked part of domain. """ + from pandas import DataFrame from .utils import mask_cube_untracked from iris import Constraint @@ -81,14 +104,22 @@ def calculate_cog_untracked(mass, mask): def calculate_cog_domain(mass): - """caluclate centre of gravity and mass for entire domain - Input: - mass: iris.cube.Cube - cube of quantity (need coordinates 'time', 'geopotential_height','projection_x_coordinate' and 'projection_y_coordinate') - Output: - tracks_out pandas.DataFrame - Dataframe containing t,x,y,z positions of centre of gravity and total cloud mass + """Calculate center of gravity and mass for entire domain. + + Parameters + ---------- + mass : iris.cube.Cube + Cube of quantity (need coordinates 'time', + 'geopotential_height','projection_x_coordinate' and + 'projection_y_coordinate'). + + Returns + ------- + tracks_out : pandas.DataFrame + Dataframe containing t, x, y, z positions of center of gravity + and total cloud mass. """ + from pandas import DataFrame from iris import Constraint @@ -115,21 +146,30 @@ def calculate_cog_domain(mass): def center_of_gravity(cube_in): - """caluclate centre of gravity and sum of quantity - Input: - cube_in: iris.cube.Cube - cube (potentially masked) of quantity (need coordinates 'geopotential_height','projection_x_coordinate' and 'projection_y_coordinate') - Output: - x: float - x position of centre of gravity - y: float - y position of centre of gravity - z: float - z position of centre of gravity - variable_sum: float - sum of quantity of over unmasked part of the cube - + """Calculate center of gravity and sum of quantity. + + Parameters + ---------- + cube_in : iris.cube.Cube + Cube (potentially masked) of quantity (need coordinates + 'geopotential_height','projection_x_coordinate' and + 'projection_y_coordinate'). + + Returns + ------- + x : float + X position of center of gravity. + + y : float + Y position of center of gravity. + + z : float + Z position of center of gravity. + + variable_sum : float + Sum of quantity of over unmasked part of the cube. """ + from iris.analysis import SUM import numpy as np From 6bbadc6b9eaa4a72adfb0154b94dd33cfb308b82 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 9 Jun 2022 08:54:05 +0200 Subject: [PATCH 019/187] feature_detection.py docsrings revised --- tobac/feature_detection.py | 397 ++++++++++++++++++++++++------------- 1 file changed, 263 insertions(+), 134 deletions(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index dea74933..415b83bf 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -1,3 +1,23 @@ +"""Provide feature detection. + +This module can work with any two-dimensional field +either present or derived from the input data. To +identify the features, contiguous regions above or +below a threshold arendetermined and labelled individually. +To describe the specific location of the feature at a +specific point in time, different spatial properties +are used to describe the identified region. [2]_ + +References +---------- +.. [2] Heikenfeld, M., Marinescu, P. J., Christensen, M., + Watson-Parris, D., Senf, F., van den Heever, S. C., + and Stier, P.: tobac v1.0: towards a flexible framework + for tracking and analysis of clouds in diverse datasets, + Geosci. Model Dev. Discuss., + https://doi.org/10.5194/gmd-2019-105 , in review, 2019, 6f. +""" + import logging import numpy as np import pandas as pd @@ -15,27 +35,33 @@ def feature_position( position_threshold="center", target=None, ): - """Function to determine feature position - + """Determine feature position with regard to the horizontal + dimensions in pixels from the identified region above + threshold values + Parameters ---------- hdim1_indices : list - list of indices along hdim1 (typically ```y```) + indices of pixels in region along first horizontal + dimension hdim2_indices : list - List of indices of feature along hdim2 (typically ```x```) + indices of pixels in region along second horizontal + dimension region_small : 2D array-like - A true/false array containing True where the threshold - is met and false where the threshold isn't met. This array should - be the the size specified by region_bbox, and can be a subset of the - overall input array (i.e., ```track_data```). + A true/false array containing True where the threshold + is met and false where the threshold isn't met. This + array should be the the size specified by region_bbox, + and can be a subset of the overall input array + (i.e., ```track_data```). region_bbox : list or tuple with length of 4 - The coordinates that region_small occupies within the total track_data - array. This is in the order that the coordinates come from the - ```get_label_props_in_dict``` function. For 2D data, this should be: - (hdim1 start, hdim 2 start, hdim 1 end, hdim 2 end). + The coordinates that region_small occupies within the + total track_data array. This is in the order that the + coordinates come from the ```get_label_props_in_dict``` + function. For 2D data, this should be: (hdim1 start, + hdim 2 start, hdim 1 end, hdim 2 end). track_data : 2D array-like 2D array containing the data @@ -43,16 +69,20 @@ def feature_position( threshold_i : float The threshold value that we are testing against - position_threshold : {'center', 'extreme', 'weighted_diff', 'weighted abs'} + position_threshold : {'center', 'extreme', 'weighted_diff', ' + weighted abs'} How to select the single point position from our data. - 'center' picks the geometrical centre of the region, and is typically not recommended. - 'extreme' picks the maximum or minimum value inside the region (max/min set by ```target```) - 'weighted_diff' picks the centre of the region weighted by the distance from the threshold value - 'weighted_abs' picks the centre of the region weighted by the absolute values of the field + 'center' picks the geometrical centre of the region, + and is typically not recommended. 'extreme' picks the + maximum or minimum value inside the region (max/min set by + ```target```) 'weighted_diff' picks the centre of the + region weighted by the distance from the threshold value + 'weighted_abs' picks the centre of the region weighted by + the absolute values of the field target : {'maximum', 'minimum'} - Used only when position_threshold is set to 'extreme', this sets whether - it is looking for maxima or minima. + Used only when position_threshold is set to 'extreme', + this sets whether it is looking for maxima or minima. Returns ------- @@ -104,36 +134,58 @@ def feature_position( def test_overlap(region_inner, region_outer): + """Test for overlap between two regions + (probably scope for further speedup here) + + Parameters + ---------- + region_1 : list + list of 2-element tuples defining the indices of + all cell in the region + + region_2 : list + list of 2-element tuples defining the indices of + all cell in the region + + Returns + ---------- + overlap : bool + True if there are any shared points between the two + regions """ - function to test for overlap between two regions (probably scope for further speedup here) - Input: - region_1: list - list of 2-element tuples defining the indices of all cell in the region - region_2: list - list of 2-element tuples defining the indices of all cell in the region - - Output: - overlap: bool - True if there are any shared points between the two regions - """ + overlap = frozenset(region_outer).isdisjoint(region_inner) return not overlap def remove_parents(features_thresholds, regions_i, regions_old): + """Remove parents of newly detected feature regions. + + Remove features where its regions surround newly + detected feature regions. + + Parameters + ---------- + features_thresholds : pandas.DataFrame + Dataframe containing detected features. + + regions_i : dict + Dictionary containing the regions above/below + threshold for the newly detected feature + (feature ids as keys). + + regions_old : dict + Dictionary containing the regions above/below + threshold from previous threshold + (feature ids as keys). + + Returns + ------- + features_thresholds : pandas.DataFrame + Dataframe containing detected features excluding those + that are superseded by newly detected ones. """ - function to remove features whose regions surround newly detected feature regions - Input: - features_thresholds: pandas.DataFrame - Dataframe containing detected features - regions_i: dict - dictionary containing the regions above/below threshold for the newly detected feature (feature ids as keys) - regions_old: dict - dictionary containing the regions above/below threshold from previous threshold (feature ids as keys) - Output: - features_thresholds pandas.DataFrame - Dataframe containing detected features excluding those that are superseded by newly detected ones - """ + try: all_curr_pts = np.concatenate([vals for idx, vals in regions_i.items()]) all_old_pts = np.concatenate([vals for idx, vals in regions_old.items()]) @@ -173,35 +225,55 @@ def feature_detection_threshold( min_distance=0, idx_start=0, ): + """Find features based on individual threshold value. + + Parameters + ---------- + data_i : iris.cube.Cube + 2D field to perform the feature detection (single timestep) on. + + i_time : int + Number of the current timestep. + + threshold : float, optional + Threshold value used to select target regions to track. Default + is None. + + target : {'maximum', 'minimum'}, optional + Flag to determine if tracking is targetting minima or maxima + in the data. Default is 'maximum'. + + position_threshold : {'center', 'extreme', 'weighted_diff', + 'weighted_abs'}, optional + Flag choosing method used for the position of the tracked + feature. Default is 'center'. + + sigma_threshold: float, optional + Standard deviation for intial filtering step. Default is 0.5. + + n_erosion_threshold: int, optional + Number of pixel by which to erode the identified features. + Default is 0. + + n_min_threshold : int, optional + Minimum number of identified features. Default is 0. + + min_distance : float, optional + Minimum distance between detected features. Default is 0. + + idx_start : int, optional + Feature id to start with. Default is 0. + + Returns + ------- + features_threshold : pandas DataFrame + Detected features for individual threshold. + + regions : dict + Dictionary containing the regions above/below threshold used + for each feature (feature ids as keys). """ - function to find features based on individual threshold value: - Input: - data_i: iris.cube.Cube - 2D field to perform the feature detection (single timestep) - i_time: int - number of the current timestep - threshold: float - threshold value used to select target regions to track - target: str ('minimum' or 'maximum') - flag to determine if tracking is targetting minima or maxima in the data - position_threshold: str('extreme', 'weighted_diff', 'weighted_abs' or 'center') - flag choosing method used for the position of the tracked feature - sigma_threshold: float - standard deviation for intial filtering step - n_erosion_threshold: int - number of pixel by which to erode the identified features - n_min_threshold: int - minimum number of identified features - min_distance: float - minimum distance between detected features (m) - idx_start: int - feature id to start with - Output: - features_threshold: pandas DataFrame - detected features for individual threshold - regions: dict - dictionary containing the regions above/below threshold used for each feature (feature ids as keys) - """ + from skimage.measure import label from skimage.morphology import binary_erosion from copy import deepcopy @@ -337,35 +409,54 @@ def feature_detection_multithreshold_timestep( min_distance=0, feature_number_start=1, ): - """ - function to find features in each timestep based on iteratively finding regions above/below a set of thresholds - Input: - data_i: iris.cube.Cube - 2D field to perform the feature detection (single timestep) - i_time: int - number of the current timestep - - threshold: list of floats - threshold values used to select target regions to track - dxy: float - grid spacing of the input data (m) - target: str ('minimum' or 'maximum') - flag to determine if tracking is targetting minima or maxima in the data - position_threshold: str('extreme', 'weighted_diff', 'weighted_abs' or 'center') - flag choosing method used for the position of the tracked feature - sigma_threshold: float - standard deviation for intial filtering step - n_erosion_threshold: int - number of pixel by which to erode the identified features - n_min_threshold: int - minimum number of identified features - min_distance: float - minimum distance between detected features (m) - feature_number_start: int - feature number to start with - Output: - features_threshold: pandas DataFrame - detected features for individual timestep + """Find features in each timestep. + + Based on iteratively finding regions above/below a set of + thresholds. Smoothing the input data with the Gaussian filter makes + output more reliable. [2]_ + + Parameters + ---------- + + data_i : iris.cube.Cube + 2D field to perform the feature detection (single timestep) on. + + threshold : float, optional + Threshold value used to select target regions to track. Default + is None. + + min_num : int, optional + Default is 0. + + target : {'maximum', 'minimum'}, optinal + Flag to determine if tracking is targetting minima or maxima + in the data. Default is 'maximum'. + + position_threshold : {'center', 'extreme', 'weighted_diff', + 'weighted_abs'}, optional + Flag choosing method used for the position of the tracked + feature. Default is 'center'. + + sigma_threshold: float, optional + Standard deviation for intial filtering step. Default is 0.5. + + n_erosion_threshold: int, optional + Number of pixel by which to erode the identified features. + Default is 0. + + n_min_threshold : int, optional + Minimum number of identified features. Default is 0. + + min_distance : float, optional + Minimum distance between detected features. Default is 0. + + feature_number_start : int, optional + Feature id to start with. Default is 1. + + Returns + ------- + features_threshold : pandas DataFrame + Detected features for individual timestep. """ # Handle scipy depreciation gracefully try: @@ -437,31 +528,61 @@ def feature_detection_multithreshold( min_distance=0, feature_number_start=1, ): - """Function to perform feature detection based on contiguous regions above/below a threshold - Input: - field_in: iris.cube.Cube - 2D field to perform the tracking on (needs to have coordinate 'time' along one of its dimensions) - - thresholds: list of floats - threshold values used to select target regions to track - dxy: float - grid spacing of the input data (m) - target: str ('minimum' or 'maximum') - flag to determine if tracking is targetting minima or maxima in the data - position_threshold: str('extreme', 'weighted_diff', 'weighted_abs' or 'center') - flag choosing method used for the position of the tracked feature - sigma_threshold: float - standard deviation for intial filtering step - n_erosion_threshold: int - number of pixel by which to erode the identified features - n_min_threshold: int - minimum number of identified features - min_distance: float - minimum distance between detected features (m) - Output: - features: pandas DataFrame - detected features + """Perform feature detection based on contiguous regions. + + The regions are above/below a threshold. + + Parameters + ---------- + field_in : iris.cube.Cube + 2D field to perform the tracking on (needs to have coordinate + 'time' along one of its dimensions), + + dxy : float + Grid spacing of the input data (in meter). + + thresholds : list of floats, optional + Threshold values used to select target regions to track. + Default is None. + + target : {'maximum', 'minimum'}, optional + Flag to determine if tracking is targetting minima or maxima in + the data. Default is 'maximum'. + + position_threshold : {'center', 'extreme', 'weighted_diff', + 'weighted_abs'}, optional + Flag choosing method used for the position of the tracked + feature. Default is 'center'. + + coord_interp_kind : str, optional + The kind of interpolation for coordinates. Default is 'linear'. + For 1d interp, {'linear', 'nearest', 'nearest-up', 'zero', + 'slinear', 'quadratic', 'cubic', + 'previous', 'next'}. + For 2d interp, {'linear', 'cubic', 'quintic'}. + + sigma_threshold: float, optional + Standard deviation for intial filtering step. Default is 0.5. + + n_erosion_threshold: int, optional + Number of pixel by which to erode the identified features. + Default is 0. + + n_min_threshold : int, optional + Minimum number of identified features. Default is 0. + + min_distance : float, optional + Minimum distance between detected features. Default is 0. + + feature_number_start : int, optional + Feature id to start with. Default is 1. + + Returns + ------- + features : pandas.DataFrame + Detected features. """ + from .utils import add_coordinates if min_num != 0: @@ -528,18 +649,26 @@ def feature_detection_multithreshold( def filter_min_distance(features, dxy, min_distance): - """Function to perform feature detection based on contiguous regions above/below a threshold - Input: - features: pandas DataFrame - features - dxy: float - horzontal grid spacing (m) - min_distance: float - minimum distance between detected features (m) - Output: - features: pandas DataFrame - features + """Perform feature detection based on contiguous regions. + + Regions are above/below a threshold. + + Parameters + ---------- + features : pandas.DataFrame + + dxy : float + Grid spacing of the input data. + + min_distance : float, optional + Minimum distance between detected features. + + Returns + ------- + features : pandas.DataFrame + Detected features. """ + from itertools import combinations remove_list_distance = [] From beefabf7f9d88bcc24f0920c991a4f935547762f Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 9 Jun 2022 08:56:57 +0200 Subject: [PATCH 020/187] reformatting with black --- tobac/analysis.py | 652 ++++++++++++++++++------------------- tobac/centerofgravity.py | 36 +- tobac/feature_detection.py | 166 +++++----- tobac/segmentation.py | 24 +- tobac/testing.py | 92 +++--- tobac/tracking.py | 48 +-- 6 files changed, 509 insertions(+), 509 deletions(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index 3f325781..b01400b5 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -51,41 +51,41 @@ def cell_statistics_all( Parameters ---------- input_cubes : iris.cube.Cube - + track : dask.dataframe.DataFrame - + mask : iris.cube.Cube Cube containing mask (int id for tracked volumes 0 everywhere else). - - aggregators : list - list of iris.analysis.Aggregator instances - + + aggregators : list + list of iris.analysis.Aggregator instances + output_path : str, optional Default is './'. - + cell_selection : optional Default is None. - + output_name : str, optional Default is 'Profiles'. - + width : int, optional Default is 10000. - + z_coord : str, optional Name of the vertical coordinate in the cube. Default is 'model_level_number'. - + dimensions : list of str, optional Default is ['x', 'y']. - + **kwargs - + Returns ------- None - + Notes ----- Not sure what this function does @@ -125,46 +125,46 @@ def cell_statistics( Parameters ---------- input_cubes : iris.cube.Cube - + track : dask.dataframe.DataFrame - + mask : iris.cube.Cube Cube containing mask (int id for tracked volumes 0 everywhere else). - - aggregators list - list of iris.analysis.Aggregator instances - + + aggregators list + list of iris.analysis.Aggregator instances + cell : int Integer id of cell to create masked cube for output. - + output_path : str, optional Default is './'. - + output_name : str, optional Default is 'Profiles'. - + width : int, optional Default is 10000. - + z_coord : str, optional Name of the vertical coordinate in the cube. Default is 'model_level_number'. - + dimensions : list of str, optional Default is ['x', 'y']. - + **kwargs - + Returns ------- None - + Notes ----- Not sure what this function does """ - + from iris.cube import Cube, CubeList from iris.coords import AuxCoord from iris import Constraint, save @@ -300,31 +300,31 @@ def cog_cell( ---------- cell : int Integer id of cell to create masked cube for output. - + Tracks : optional Default is None. - + M_total : subset of cube, optional Default is None. - + M_liquid : subset of cube, optional Default is None. - + M_frozen : subset of cube, optional Default is None. - + savedir : str Default is None. - + Returns ------- None - + Notes ----- Not sure what this function does """ - + from iris import Constraint logging.debug("Start calculating COG for " + str(cell)) @@ -372,45 +372,45 @@ def lifetime_histogram( Track, bin_edges=np.arange(0, 200, 20), density=False, return_values=False ): """Compute the lifetime histogram of linked features. - + Parameters ---------- Track : pandas.DataFrame - Dataframe of linked features, containing the columns 'cell' - and 'time_cell'. - + Dataframe of linked features, containing the columns 'cell' + and 'time_cell'. + bin_edges : int or ndarray, optional - If bin_edges is an int, it defines the number of equal-width - bins in the given range. If bins is a ndarray, it defines a - monotonically increasing array of bin edges, including the + If bin_edges is an int, it defines the number of equal-width + bins in the given range. If bins is a ndarray, it defines a + monotonically increasing array of bin edges, including the rightmost edge. Default is np.arange(0, 200, 20). - + density : bool, optional - If False, the result will contain the number of samples in - each bin. If True, the result is the value of the probability - density function at the bin, normalized such that the integral - over the range is 1. Default is False. - + If False, the result will contain the number of samples in + each bin. If True, the result is the value of the probability + density function at the bin, normalized such that the integral + over the range is 1. Default is False. + return_values : bool, optional - Bool determining wether the lifetimes of the features are - returned from this function. Default is False. - + Bool determining wether the lifetimes of the features are + returned from this function. Default is False. + Returns ------- hist : ndarray - The values of the histogram. - + The values of the histogram. + bin_edges : ndarray - The edges of the histogram. - + The edges of the histogram. + bin_centers : ndarray - The centers of the histogram intervalls. - + The centers of the histogram intervalls. + minutes, optional : ndarray - Numpy.array of the lifetime of each feature in minutes. - + Numpy.array of the lifetime of each feature in minutes. + """ - + Track_cell = Track.groupby("cell") minutes = (Track_cell["time_cell"].max() / pd.Timedelta(minutes=1)).values hist, bin_edges = np.histogram(minutes, bin_edges, density=density) @@ -423,26 +423,26 @@ def lifetime_histogram( def haversine(lat1, lon1, lat2, lon2): """Computes the Haversine distance in kilometers. - + Calculates the Haversine distance between two points (based on implementation CIS https://github.com/cedadev/cis). - + Parameters ---------- lat1, lon1 : array of latitude, longitude First point or points as array in degrees. - + lat2, lon2 : array of latitude, longitude Second point or points as array in degrees. - + Returns ------- arclen * RADIUS_EARTH : array - Array of Distance(s) between the two points(-arrays) in + Array of Distance(s) between the two points(-arrays) in kilometers. - + """ - + RADIUS_EARTH = 6378.0 lat1 = np.radians(lat1) lat2 = np.radians(lat2) @@ -459,31 +459,31 @@ def haversine(lat1, lon1, lat2, lon2): def calculate_distance(feature_1, feature_2, method_distance=None): - """Compute the distance between two features. It is based on + """Compute the distance between two features. It is based on either lat/lon coordinates or x/y coordinates. - + Parameters ---------- feature_1, feature_2 : pandas.DataFrame or pandas.Series - Dataframes containing multiple features or pandas.Series - of one feature. Need to contain either projection_x_coordinate - and projection_y_coordinate or latitude and longitude - coordinates. - + Dataframes containing multiple features or pandas.Series + of one feature. Need to contain either projection_x_coordinate + and projection_y_coordinate or latitude and longitude + coordinates. + method_distance : {None, 'xy', 'latlon'}, optional - Method of distance calculation. 'xy' uses the length of the - vector between the two features, 'latlon' uses the haversine - distance. None checks wether the required coordinates are - present and starts with 'xy'. Default is None. - + Method of distance calculation. 'xy' uses the length of the + vector between the two features, 'latlon' uses the haversine + distance. None checks wether the required coordinates are + present and starts with 'xy'. Default is None. + Returns ------- distance : float or pandas.Series Float with the distance between the two features in meters if - the input are two pandas.Series containing one feature, - pandas.Series of the distancesif one of the inputs contains + the input are two pandas.Series containing one feature, + pandas.Series of the distancesif one of the inputs contains multiple features. - + """ if method_distance is None: if ( @@ -532,33 +532,33 @@ def calculate_distance(feature_1, feature_2, method_distance=None): def calculate_velocity_individual(feature_old, feature_new, method_distance=None): """Calculate the mean velocity of a feature between two timeframes. - + Parameters ---------- feature_old : pandas.Series - pandas.Series of a feature at a certain timeframe. Needs to - contain a 'time' column and either projection_x_coordinate - and projection_y_coordinate or latitude and longitude coordinates. - + pandas.Series of a feature at a certain timeframe. Needs to + contain a 'time' column and either projection_x_coordinate + and projection_y_coordinate or latitude and longitude coordinates. + feature_new : pandas.Series - pandas.Series of the same feature at a later timeframe. Needs - to contain a 'time' column and either projection_x_coordinate - and projection_y_coordinate or latitude and longitude coordinates. - + pandas.Series of the same feature at a later timeframe. Needs + to contain a 'time' column and either projection_x_coordinate + and projection_y_coordinate or latitude and longitude coordinates. + method_distance : {None, 'xy', 'latlon'}, optional - Method of distance calculation, used to calculate the velocit. - 'xy' uses the length of the vector between the two features, - 'latlon' uses the haversine distance. None checks wether the - required coordinates are present and starts with 'xy'. + Method of distance calculation, used to calculate the velocit. + 'xy' uses the length of the vector between the two features, + 'latlon' uses the haversine distance. None checks wether the + required coordinates are present and starts with 'xy'. Default is None. - + Returns ------- velocity : float - Value of the approximate velocity. - + Value of the approximate velocity. + """ - + distance = calculate_distance( feature_old, feature_new, method_distance=method_distance ) @@ -569,33 +569,33 @@ def calculate_velocity_individual(feature_old, feature_new, method_distance=None def calculate_velocity(track, method_distance=None): """Calculate the velocities of a set of linked features. - + Parameters ---------- track : pandas.DataFrame - Dataframe of linked features, containing the columns 'cell', - 'time' and either 'projection_x_coordinate' and - 'projection_y_coordinate' or 'latitude' and 'longitude'. - + Dataframe of linked features, containing the columns 'cell', + 'time' and either 'projection_x_coordinate' and + 'projection_y_coordinate' or 'latitude' and 'longitude'. + method_distance : {None, 'xy', 'latlon'}, optional - Method of distance calculation, used to calculate the + Method of distance calculation, used to calculate the velocity. 'xy' uses the length of the vector between the - two features, 'latlon' uses the haversine distance. None - checks wether the required coordinates are present and + two features, 'latlon' uses the haversine distance. None + checks wether the required coordinates are present and starts with 'xy'. Default is None. - + Returns ------- track : pandas.DataFrame - DataFrame from the input, with an additional column 'v', - contain the value of the velocity for every feature at - every possible timestep - + DataFrame from the input, with an additional column 'v', + contain the value of the velocity for every feature at + every possible timestep + Notes ----- needs short summary, description and type of track """ - + for cell_i, track_i in track.groupby("cell"): index = track_i.index.values for i, index_i in enumerate(index[:-1]): @@ -615,52 +615,52 @@ def velocity_histogram( method_distance=None, return_values=False, ): - """Create an velocity histogram of the features. If the DataFrame + """Create an velocity histogram of the features. If the DataFrame does not contain a velocity column, the velocities are calculated. - + Parameters ---------- track: pandas.DataFrame - DataFrame of the linked features, containing the columns 'cell', - 'time' and either 'projection_x_coordinate' and - 'projection_y_coordinate' or 'latitude' and 'longitude'. - + DataFrame of the linked features, containing the columns 'cell', + 'time' and either 'projection_x_coordinate' and + 'projection_y_coordinate' or 'latitude' and 'longitude'. + bin_edges : int or ndarray, optional - If bin_edges is an int, it defines the number of equal-width - bins in the given range. If bins is a ndarray, it defines a - monotonically increasing array of bin edges, including the + If bin_edges is an int, it defines the number of equal-width + bins in the given range. If bins is a ndarray, it defines a + monotonically increasing array of bin edges, including the rightmost edge. Default is np.arange(0, 30000, 500). - + density : bool, optional - If False, the result will contain the number of samples in - each bin. If True, the result is the value of the probability - density function at the bin, normalized such that the integral - over the range is 1. Default is False. - + If False, the result will contain the number of samples in + each bin. If True, the result is the value of the probability + density function at the bin, normalized such that the integral + over the range is 1. Default is False. + methods_distance : {None, 'xy', 'latlon'}, optional - Method of distance calculation, used to calculate the velocity. - 'xy' uses the length of the vector between the two features, - 'latlon' uses the haversine distance. None checks wether the - required coordinates are present and starts with 'xy'. + Method of distance calculation, used to calculate the velocity. + 'xy' uses the length of the vector between the two features, + 'latlon' uses the haversine distance. None checks wether the + required coordinates are present and starts with 'xy'. Default is None. - + return_values : bool, optional - Bool determining wether the velocities of the features are - returned from this function. Default is False. - + Bool determining wether the velocities of the features are + returned from this function. Default is False. + Returns ------- hist : ndarray - The values of the histogram. - + The values of the histogram. + bin_edges : ndarray - The edges of the histogram. - + The edges of the histogram. + velocities , optional : ndarray - Numpy array with the velocities of each feature. - + Numpy array with the velocities of each feature. + """ - + if "v" not in track.columns: logging.info("calculate velocities") track = calculate_velocity(track) @@ -677,28 +677,28 @@ def velocity_histogram( def calculate_nearestneighbordistance(features, method_distance=None): """Calculate the distance between a feature and the nearest other feature in the same timeframe. - + Parameters ---------- features : pandas.DataFrame - DataFrame of the features whose nearest neighbor distance is to - be calculated. Needs to contain either projection_x_coordinate - and projection_y_coordinate or latitude and longitude coordinates. - + DataFrame of the features whose nearest neighbor distance is to + be calculated. Needs to contain either projection_x_coordinate + and projection_y_coordinate or latitude and longitude coordinates. + method_distance : {None, 'xy', 'latlon'}, optional - Method of distance calculation. 'xy' uses the length of the vector - between the two features, 'latlon' uses the haversine distance. - None checks wether the required coordinates are present and starts - with 'xy'. Default is None. - + Method of distance calculation. 'xy' uses the length of the vector + between the two features, 'latlon' uses the haversine distance. + None checks wether the required coordinates are present and starts + with 'xy'. Default is None. + Returns ------- features : pandas.DataFrame - DataFrame of the features with a new column 'min_distance', - containing the calculated minimal distance to other features. - + DataFrame of the features with a new column 'min_distance', + containing the calculated minimal distance to other features. + """ - + from itertools import combinations features["min_distance"] = np.nan @@ -738,49 +738,49 @@ def nearestneighbordistance_histogram( method_distance=None, return_values=False, ): - """Create an nearest neighbor distance histogram of the features. - If the DataFrame does not contain a 'min_distance' column, the + """Create an nearest neighbor distance histogram of the features. + If the DataFrame does not contain a 'min_distance' column, the distances are calculated. - + ---------- features - + bin_edges : int or ndarray, optional - If bin_edges is an int, it defines the number of equal-width - bins in the given range. If bins is a ndarray, it defines a - monotonically increasing array of bin edges, including the + If bin_edges is an int, it defines the number of equal-width + bins in the given range. If bins is a ndarray, it defines a + monotonically increasing array of bin edges, including the rightmost edge. Default is np.arange(0, 30000, 500). - + density : bool, optional - If False, the result will contain the number of samples in - each bin. If True, the result is the value of the probability - density function at the bin, normalized such that the integral - over the range is 1. Default is False. - + If False, the result will contain the number of samples in + each bin. If True, the result is the value of the probability + density function at the bin, normalized such that the integral + over the range is 1. Default is False. + method_distance : {None, 'xy', 'latlon'}, optional - Method of distance calculation. 'xy' uses the length of the - vector between the two features, 'latlon' uses the haversine - distance. None checks wether the required coordinates are - present and starts with 'xy'. Default is None. - + Method of distance calculation. 'xy' uses the length of the + vector between the two features, 'latlon' uses the haversine + distance. None checks wether the required coordinates are + present and starts with 'xy'. Default is None. + return_values : bool, optional - Bool determining wether the nearest neighbor distance of the - features are returned from this function. Default is False. - + Bool determining wether the nearest neighbor distance of the + features are returned from this function. Default is False. + Returns ------- hist : ndarray - The values of the histogram. - + The values of the histogram. + bin_edges : ndarray - The edges of the histogram. - + The edges of the histogram. + distances, optional : ndarray - A numpy array with the nearest neighbor distances of each - feature. - + A numpy array with the nearest neighbor distances of each + feature. + """ - + if "min_distance" not in features.columns: logging.debug("calculate nearest neighbor distances") features = calculate_nearestneighbordistance( @@ -798,25 +798,25 @@ def nearestneighbordistance_histogram( # Treatment of 2D lat/lon coordinates to be added: def calculate_areas_2Dlatlon(_2Dlat_coord, _2Dlon_coord): - """Calculate an array of cell areas when given two 2D arrays + """Calculate an array of cell areas when given two 2D arrays of latitude and longitude values NOTE: This currently assuems that the lat/lon grid is orthogonal, which is not strictly true! It's close enough for most cases, but - should be updated in future to use the cross product of the + should be updated in future to use the cross product of the distances to the neighbouring cells. This will require the use - of a more advanced calculation. I would advise using pyproj - at some point in the future to solve this issue and replace + of a more advanced calculation. I would advise using pyproj + at some point in the future to solve this issue and replace haversine distance. Parameters ---------- _2Dlat_coord : AuxCoord - Iris auxilliary coordinate containing a 2d grid of latitudes + Iris auxilliary coordinate containing a 2d grid of latitudes for each point. _2Dlon_coord : AuxCoord - Iris auxilliary coordinate containing a 2d grid of longitudes + Iris auxilliary coordinate containing a 2d grid of longitudes for each point. Returns @@ -825,7 +825,7 @@ def calculate_areas_2Dlatlon(_2Dlat_coord, _2Dlon_coord): A numpy array approximating the area of each cell. """ - + hdist1 = ( haversine( _2Dlat_coord.points[:-1], @@ -863,47 +863,47 @@ def calculate_areas_2Dlatlon(_2Dlat_coord, _2Dlon_coord): def calculate_area(features, mask, method_area=None): """Calculate the area of the segments for each feature. - + Parameters ---------- features : pandas.DataFrame - DataFrame of the features whose area is to be calculated. - + DataFrame of the features whose area is to be calculated. + mask : iris.cube.Cube - Cube containing mask (int for tracked volumes 0 everywhere - else). Needs to contain either projection_x_coordinate and - projection_y_coordinate or latitude and longitude - coordinates. - + Cube containing mask (int for tracked volumes 0 everywhere + else). Needs to contain either projection_x_coordinate and + projection_y_coordinate or latitude and longitude + coordinates. + method_area : {None, 'xy', 'latlon'}, optional - Flag determining how the area is calculated. 'xy' uses the - areas of the individual pixels, 'latlon' uses the - area_weights method of iris.analysis.cartography, None - checks wether the required coordinates are present and - starts with 'xy'. Default is None. - + Flag determining how the area is calculated. 'xy' uses the + areas of the individual pixels, 'latlon' uses the + area_weights method of iris.analysis.cartography, None + checks wether the required coordinates are present and + starts with 'xy'. Default is None. + Returns ------- features : pandas.DataFrame - DataFrame of the features with a new column 'area', - containing the calculated areas. - + DataFrame of the features with a new column 'area', + containing the calculated areas. + Raises ------ ValueError If neither latitude/longitude nor - projection_x_coordinate/projection_y_coordinate are + projection_x_coordinate/projection_y_coordinate are present in mask_coords. - + If latitude/longitude coordinates are 2D. - + If latitude/longitude shapes are not supported. - - If method is undefined, i.e. method is neither None, + + If method is undefined, i.e. method is neither None, 'xy' nor 'latlon'. - + """ - + from tobac.utils import mask_features_surface, mask_features from iris import Constraint from iris.analysis.cartography import area_weights @@ -971,61 +971,61 @@ def area_histogram( return_values=False, representative_area=False, ): - """Create an area histogram of the features. If the DataFrame + """Create an area histogram of the features. If the DataFrame does not contain an area column, the areas are calculated. - + Parameters ---------- features : pandas.DataFrame - DataFrame of the features. - + DataFrame of the features. + mask : iris.cube.Cube - Cube containing mask (int for tracked volumes 0 - everywhere else). Needs to contain either - projection_x_coordinate and projection_y_coordinate or - latitude and longitude coordinates. The output of a - segmentation should be used here. - + Cube containing mask (int for tracked volumes 0 + everywhere else). Needs to contain either + projection_x_coordinate and projection_y_coordinate or + latitude and longitude coordinates. The output of a + segmentation should be used here. + bin_edges : int or ndarray, optional - If bin_edges is an int, it defines the number of - equal-width bins in the given range. If bins is a ndarray, + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is a ndarray, it defines a monotonically increasing array of bin edges, - including the rightmost edge. + including the rightmost edge. Default is np.arange(0, 30000, 500). - + density : bool, optional - If False, the result will contain the number of samples - in each bin. If True, the result is the value of the - probability density function at the bin, normalized such - that the integral over the range is 1. Default is False. - + If False, the result will contain the number of samples + in each bin. If True, the result is the value of the + probability density function at the bin, normalized such + that the integral over the range is 1. Default is False. + return_values : bool, optional - Bool determining wether the areas of the features are - returned from this function. Default is False. - + Bool determining wether the areas of the features are + returned from this function. Default is False. + representive_area: bool, optional - If False, no weights will associated to the values. + If False, no weights will associated to the values. If True, the weights for each area will be the areas itself, i.e. each bin count will have the value of - the sum of all areas within the edges of the bin. + the sum of all areas within the edges of the bin. Default is False. - + Returns ------- hist : ndarray - The values of the histogram. - + The values of the histogram. + bin_edges : ndarray - The edges of the histogram. - + The edges of the histogram. + bin_centers : ndarray - The centers of the histogram intervalls. - + The centers of the histogram intervalls. + areas : ndarray, optional A numpy array approximating the area of each feature. - + """ - + if "area" not in features.columns: logging.info("calculate area") features = calculate_area(features, mask, method_area) @@ -1048,56 +1048,56 @@ def area_histogram( def histogram_cellwise( Track, variable=None, bin_edges=None, quantity="max", density=False ): - """Create a histogram of the maximum, minimum or mean of - a variable for the cells of a track. Essentially a wrapper + """Create a histogram of the maximum, minimum or mean of + a variable for the cells of a track. Essentially a wrapper of the numpy.histogram() method. - + Parameters ---------- Track : pandas.DataFrame The track containing the variable to create the histogram from. - + variable : string, optional - Column of the DataFrame with the variable on which the - histogram is to be based on. Default is None. - + Column of the DataFrame with the variable on which the + histogram is to be based on. Default is None. + bin_edges : int or ndarray, optional - If bin_edges is an int, it defines the number of - equal-width bins in the given range. If bins is a ndarray, - it defines a monotonically increasing array of bin edges, - including the rightmost edge. - + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is a ndarray, + it defines a monotonically increasing array of bin edges, + including the rightmost edge. + quantity : {'max', 'min', 'mean'}, optional - Flag determining wether to use maximum, minimum or mean - of a variable from all timeframes the cell covers. - Default is 'max'. - + Flag determining wether to use maximum, minimum or mean + of a variable from all timeframes the cell covers. + Default is 'max'. + density : bool, optional - If False, the result will contain the number of samples - in each bin. If True, the result is the value of the - probability density function at the bin, normalized such - that the integral over the range is 1. - Default is False. - + If False, the result will contain the number of samples + in each bin. If True, the result is the value of the + probability density function at the bin, normalized such + that the integral over the range is 1. + Default is False. + Returns ------- hist : ndarray - The values of the histogram - + The values of the histogram + bin_edges : ndarray - The edges of the histogram - + The edges of the histogram + bin_centers : ndarray - The centers of the histogram intervalls - + The centers of the histogram intervalls + Raises ------ ValueError If quantity is not 'max', 'min' or 'mean'. - + """ - + Track_cell = Track.groupby("cell") if quantity == "max": variable_cell = Track_cell[variable].max().values @@ -1114,46 +1114,46 @@ def histogram_cellwise( def histogram_featurewise(Track, variable=None, bin_edges=None, density=False): - """Create a histogram of a variable from the features of a - track. Essentially a wrapper of the numpy.histogram() + """Create a histogram of a variable from the features of a + track. Essentially a wrapper of the numpy.histogram() method. - + Parameters ---------- Track : pandas.DataFrame - The track containing the variable to create the + The track containing the variable to create the histogram from. - + variable : string, optional - Column of the DataFrame with the variable on which the - histogram is to be based on. Default is None. - + Column of the DataFrame with the variable on which the + histogram is to be based on. Default is None. + bin_edges : int or ndarray, optional - If bin_edges is an int, it defines the number of - equal-width bins in the given range. If bins is - a sequence, it defines a monotonically increasing - array of bin edges, including the rightmost edge. - + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is + a sequence, it defines a monotonically increasing + array of bin edges, including the rightmost edge. + density : bool, optional - If False, the result will contain the number of - samples in each bin. If True, the result is the - value of the probability density function at the - bin, normalized such that the integral over the - range is 1. Default is False. - + If False, the result will contain the number of + samples in each bin. If True, the result is the + value of the probability density function at the + bin, normalized such that the integral over the + range is 1. Default is False. + Returns ------- hist : ndarray - The values of the histogram - + The values of the histogram + bin_edges : ndarray - The edges of the histogram - + The edges of the histogram + bin_centers : ndarray - The centers of the histogram intervalls - + The centers of the histogram intervalls + """ - + hist, bin_edges = np.histogram(Track[variable].values, bin_edges, density=density) bin_centers = bin_edges[:-1] + 0.5 * np.diff(bin_edges) @@ -1163,36 +1163,36 @@ def histogram_featurewise(Track, variable=None, bin_edges=None, density=False): def calculate_overlap( track_1, track_2, min_sum_inv_distance=None, min_mean_inv_distance=None ): - """Count the number of time frames in which the - individual cells of two tracks are present together + """Count the number of time frames in which the + individual cells of two tracks are present together and calculate their mean and summed inverse distance. Parameters ---------- track_1, track_2 : pandas.DataFrame - The tracks conaining the cells to analyze. - + The tracks conaining the cells to analyze. + min_sum_inv_distance : float, optional - Minimum of the inverse net distance for two - cells to be counted as overlapping. + Minimum of the inverse net distance for two + cells to be counted as overlapping. Default is None. - + min_mean_inv_distance : float, optional - Minimum of the inverse mean distance for two cells - to be counted as overlapping. Default is None. - + Minimum of the inverse mean distance for two cells + to be counted as overlapping. Default is None. + Returns ------- overlap : pandas.DataFrame - DataFrame containing the columns cell_1 and cell_2 - with the index of the cells from the tracks, - n_overlap with the number of frames both cells are - present in, mean_inv_distance with the mean inverse - distance and sum_inv_distance with the summed - inverse distance of the cells. - + DataFrame containing the columns cell_1 and cell_2 + with the index of the cells from the tracks, + n_overlap with the number of frames both cells are + present in, mean_inv_distance with the mean inverse + distance and sum_inv_distance with the summed + inverse distance of the cells. + """ - + cells_1 = track_1["cell"].unique() # n_cells_1_tot=len(cells_1) cells_2 = track_2["cell"].unique() diff --git a/tobac/centerofgravity.py b/tobac/centerofgravity.py index 895d1dad..8a5cd84d 100644 --- a/tobac/centerofgravity.py +++ b/tobac/centerofgravity.py @@ -6,28 +6,28 @@ def calculate_cog(tracks, mass, mask): """Calculate center of gravity and mass for each tracked cell. - + Parameters ---------- tracks : pandas.DataFrame DataFrame containing trajectories of cell centers. - + mass : iris.cube.Cube Cube of quantity (need coordinates 'time', 'geopotential_height','projection_x_coordinate' and 'projection_y_coordinate'). - + mask : iris.cube.Cube Cube containing mask (int > where belonging to cloud volume, 0 everywhere else). - + Returns ------- tracks_out : pandas.DataFrame Dataframe containing t, x, y, z positions of center of gravity and total cloud mass each tracked cells at each timestep. """ - + from .utils import mask_cube_cell from iris import Constraint @@ -54,25 +54,25 @@ def calculate_cog(tracks, mass, mask): def calculate_cog_untracked(mass, mask): """Calculate center of gravity and mass for untracked domain parts. - + Parameters ---------- mass : iris.cube.Cube Cube of quantity (need coordinates 'time', 'geopotential_height','projection_x_coordinate' and 'projection_y_coordinate'). - + mask : iris.cube.Cube Cube containing mask (int > where belonging to cloud volume, 0 everywhere else). - + Returns ------- tracks_out : pandas.DataFrame Dataframe containing t, x, y, z positions of center of gravity and total cloud mass for untracked part of domain. """ - + from pandas import DataFrame from .utils import mask_cube_untracked from iris import Constraint @@ -105,21 +105,21 @@ def calculate_cog_untracked(mass, mask): def calculate_cog_domain(mass): """Calculate center of gravity and mass for entire domain. - + Parameters ---------- mass : iris.cube.Cube Cube of quantity (need coordinates 'time', 'geopotential_height','projection_x_coordinate' and 'projection_y_coordinate'). - + Returns ------- tracks_out : pandas.DataFrame Dataframe containing t, x, y, z positions of center of gravity and total cloud mass. """ - + from pandas import DataFrame from iris import Constraint @@ -147,29 +147,29 @@ def calculate_cog_domain(mass): def center_of_gravity(cube_in): """Calculate center of gravity and sum of quantity. - + Parameters ---------- cube_in : iris.cube.Cube Cube (potentially masked) of quantity (need coordinates 'geopotential_height','projection_x_coordinate' and 'projection_y_coordinate'). - + Returns ------- x : float X position of center of gravity. - + y : float Y position of center of gravity. - + z : float Z position of center of gravity. - + variable_sum : float Sum of quantity of over unmasked part of the cube. """ - + from iris.analysis import SUM import numpy as np diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index 415b83bf..be57d5ec 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -36,31 +36,31 @@ def feature_position( target=None, ): """Determine feature position with regard to the horizontal - dimensions in pixels from the identified region above + dimensions in pixels from the identified region above threshold values - + Parameters ---------- hdim1_indices : list - indices of pixels in region along first horizontal + indices of pixels in region along first horizontal dimension hdim2_indices : list - indices of pixels in region along second horizontal + indices of pixels in region along second horizontal dimension region_small : 2D array-like - A true/false array containing True where the threshold - is met and false where the threshold isn't met. This - array should be the the size specified by region_bbox, - and can be a subset of the overall input array + A true/false array containing True where the threshold + is met and false where the threshold isn't met. This + array should be the the size specified by region_bbox, + and can be a subset of the overall input array (i.e., ```track_data```). region_bbox : list or tuple with length of 4 - The coordinates that region_small occupies within the - total track_data array. This is in the order that the - coordinates come from the ```get_label_props_in_dict``` - function. For 2D data, this should be: (hdim1 start, + The coordinates that region_small occupies within the + total track_data array. This is in the order that the + coordinates come from the ```get_label_props_in_dict``` + function. For 2D data, this should be: (hdim1 start, hdim 2 start, hdim 1 end, hdim 2 end). track_data : 2D array-like @@ -70,18 +70,18 @@ def feature_position( The threshold value that we are testing against position_threshold : {'center', 'extreme', 'weighted_diff', ' - weighted abs'} + weighted abs'} How to select the single point position from our data. - 'center' picks the geometrical centre of the region, - and is typically not recommended. 'extreme' picks the + 'center' picks the geometrical centre of the region, + and is typically not recommended. 'extreme' picks the maximum or minimum value inside the region (max/min set by - ```target```) 'weighted_diff' picks the centre of the + ```target```) 'weighted_diff' picks the centre of the region weighted by the distance from the threshold value - 'weighted_abs' picks the centre of the region weighted by + 'weighted_abs' picks the centre of the region weighted by the absolute values of the field target : {'maximum', 'minimum'} - Used only when position_threshold is set to 'extreme', + Used only when position_threshold is set to 'extreme', this sets whether it is looking for maxima or minima. Returns @@ -134,58 +134,58 @@ def feature_position( def test_overlap(region_inner, region_outer): - """Test for overlap between two regions + """Test for overlap between two regions (probably scope for further speedup here) - + Parameters ---------- region_1 : list - list of 2-element tuples defining the indices of + list of 2-element tuples defining the indices of all cell in the region - + region_2 : list - list of 2-element tuples defining the indices of + list of 2-element tuples defining the indices of all cell in the region Returns ---------- overlap : bool - True if there are any shared points between the two + True if there are any shared points between the two regions """ - + overlap = frozenset(region_outer).isdisjoint(region_inner) return not overlap def remove_parents(features_thresholds, regions_i, regions_old): """Remove parents of newly detected feature regions. - - Remove features where its regions surround newly + + Remove features where its regions surround newly detected feature regions. - + Parameters ---------- features_thresholds : pandas.DataFrame Dataframe containing detected features. - + regions_i : dict - Dictionary containing the regions above/below - threshold for the newly detected feature + Dictionary containing the regions above/below + threshold for the newly detected feature (feature ids as keys). - + regions_old : dict - Dictionary containing the regions above/below - threshold from previous threshold + Dictionary containing the regions above/below + threshold from previous threshold (feature ids as keys). - + Returns ------- features_thresholds : pandas.DataFrame - Dataframe containing detected features excluding those + Dataframe containing detected features excluding those that are superseded by newly detected ones. """ - + try: all_curr_pts = np.concatenate([vals for idx, vals in regions_i.items()]) all_old_pts = np.concatenate([vals for idx, vals in regions_old.items()]) @@ -226,54 +226,54 @@ def feature_detection_threshold( idx_start=0, ): """Find features based on individual threshold value. - + Parameters ---------- data_i : iris.cube.Cube 2D field to perform the feature detection (single timestep) on. - + i_time : int Number of the current timestep. - + threshold : float, optional Threshold value used to select target regions to track. Default is None. - + target : {'maximum', 'minimum'}, optional Flag to determine if tracking is targetting minima or maxima in the data. Default is 'maximum'. - + position_threshold : {'center', 'extreme', 'weighted_diff', 'weighted_abs'}, optional Flag choosing method used for the position of the tracked feature. Default is 'center'. - + sigma_threshold: float, optional Standard deviation for intial filtering step. Default is 0.5. - + n_erosion_threshold: int, optional Number of pixel by which to erode the identified features. Default is 0. - + n_min_threshold : int, optional Minimum number of identified features. Default is 0. - + min_distance : float, optional Minimum distance between detected features. Default is 0. - + idx_start : int, optional Feature id to start with. Default is 0. - + Returns ------- features_threshold : pandas DataFrame Detected features for individual threshold. - + regions : dict Dictionary containing the regions above/below threshold used for each feature (feature ids as keys). """ - + from skimage.measure import label from skimage.morphology import binary_erosion from copy import deepcopy @@ -410,46 +410,46 @@ def feature_detection_multithreshold_timestep( feature_number_start=1, ): """Find features in each timestep. - + Based on iteratively finding regions above/below a set of thresholds. Smoothing the input data with the Gaussian filter makes output more reliable. [2]_ - + Parameters ---------- - + data_i : iris.cube.Cube 2D field to perform the feature detection (single timestep) on. - + threshold : float, optional Threshold value used to select target regions to track. Default is None. - + min_num : int, optional Default is 0. - + target : {'maximum', 'minimum'}, optinal Flag to determine if tracking is targetting minima or maxima in the data. Default is 'maximum'. - + position_threshold : {'center', 'extreme', 'weighted_diff', 'weighted_abs'}, optional Flag choosing method used for the position of the tracked feature. Default is 'center'. - + sigma_threshold: float, optional Standard deviation for intial filtering step. Default is 0.5. - + n_erosion_threshold: int, optional Number of pixel by which to erode the identified features. Default is 0. - + n_min_threshold : int, optional Minimum number of identified features. Default is 0. - + min_distance : float, optional Minimum distance between detected features. Default is 0. - + feature_number_start : int, optional Feature id to start with. Default is 1. @@ -529,60 +529,60 @@ def feature_detection_multithreshold( feature_number_start=1, ): """Perform feature detection based on contiguous regions. - + The regions are above/below a threshold. - + Parameters ---------- field_in : iris.cube.Cube 2D field to perform the tracking on (needs to have coordinate 'time' along one of its dimensions), - + dxy : float Grid spacing of the input data (in meter). - + thresholds : list of floats, optional Threshold values used to select target regions to track. Default is None. - + target : {'maximum', 'minimum'}, optional - Flag to determine if tracking is targetting minima or maxima in + Flag to determine if tracking is targetting minima or maxima in the data. Default is 'maximum'. - + position_threshold : {'center', 'extreme', 'weighted_diff', 'weighted_abs'}, optional Flag choosing method used for the position of the tracked feature. Default is 'center'. - + coord_interp_kind : str, optional The kind of interpolation for coordinates. Default is 'linear'. For 1d interp, {'linear', 'nearest', 'nearest-up', 'zero', 'slinear', 'quadratic', 'cubic', 'previous', 'next'}. For 2d interp, {'linear', 'cubic', 'quintic'}. - + sigma_threshold: float, optional Standard deviation for intial filtering step. Default is 0.5. - + n_erosion_threshold: int, optional Number of pixel by which to erode the identified features. Default is 0. - + n_min_threshold : int, optional Minimum number of identified features. Default is 0. - + min_distance : float, optional Minimum distance between detected features. Default is 0. - + feature_number_start : int, optional Feature id to start with. Default is 1. - + Returns ------- features : pandas.DataFrame Detected features. """ - + from .utils import add_coordinates if min_num != 0: @@ -650,25 +650,25 @@ def feature_detection_multithreshold( def filter_min_distance(features, dxy, min_distance): """Perform feature detection based on contiguous regions. - + Regions are above/below a threshold. - + Parameters ---------- features : pandas.DataFrame - + dxy : float Grid spacing of the input data. - + min_distance : float, optional Minimum distance between detected features. - + Returns ------- features : pandas.DataFrame Detected features. """ - + from itertools import combinations remove_list_distance = [] diff --git a/tobac/segmentation.py b/tobac/segmentation.py index 9cbd6c8c..eb79c3dd 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -45,13 +45,13 @@ def segmentation_3D( max_distance=None, ): """Wraper for the segmentation()-function. - + Notes ---------- - + Obsolete? """ - + return segmentation( features, field, @@ -75,10 +75,10 @@ def segmentation_2D( max_distance=None, ): """Wraper for the segmentation()-function. - + Notes ---------- - + Obsolete? """ return segmentation( @@ -104,7 +104,7 @@ def segmentation_timestep( max_distance=None, vertical_coord="auto", ): - """Perform watershedding for an individual time step of the data. Works + """Perform watershedding for an individual time step of the data. Works for both 2D and 3D data Parameters @@ -142,7 +142,7 @@ def segmentation_timestep( vertical_coord : str, optional Vertical coordinate in 3D input data. If 'auto', input is checked for - one of {'z', 'model_level_number', 'altitude','geopotential_height'} + one of {'z', 'model_level_number', 'altitude','geopotential_height'} as a likely coordinate name Returns @@ -170,7 +170,7 @@ def segmentation_timestep( If method is not 'watershed'. """ - + # The location of watershed within skimage submodules changes with v0.19, but I've kept both for backward compatibility for now try: from skimage.segmentation import watershed @@ -410,10 +410,10 @@ def segmentation( def watershedding_3D(track, field_in, **kwargs): """Wraper for the segmentation()-function. - + Notes ---------- - + Obsolete? """ kwargs.pop("method", None) @@ -422,10 +422,10 @@ def watershedding_3D(track, field_in, **kwargs): def watershedding_2D(track, field_in, **kwargs): """Wraper for the segmentation()-function. - + Notes ---------- - + Obsolete? """ kwargs.pop("method", None) diff --git a/tobac/testing.py b/tobac/testing.py index 413e8dfc..ef58f42d 100644 --- a/tobac/testing.py +++ b/tobac/testing.py @@ -30,7 +30,7 @@ def make_simple_sample_data_2D(data_type="iris"): ------- sample_data : iris.cube.Cube or xarray.DataArray """ - + from iris.cube import Cube from iris.coords import DimCoord, AuxCoord @@ -104,18 +104,18 @@ def make_sample_data_2D_3blobs(data_type="iris"): coordinates and arbitrary, but in realisitic range. The data contains three individual blobs travelling on a linear trajectory through the dataset for part of the time. - + Parameters ---------- data_type : {'iris', 'xarray'}, optional - Choose type of the dataset that will be produced. - Default is 'iris' + Choose type of the dataset that will be produced. + Default is 'iris' Returns ------- sample_data : iris.cube.Cube or xarray.DataArray """ - + from iris.cube import Cube from iris.coords import DimCoord, AuxCoord @@ -225,7 +225,7 @@ def make_sample_data_2D_3blobs_inv(data_type="iris"): ------- sample_data : iris.cube.Cube or xarray.DataArray """ - + from iris.cube import Cube from iris.coords import DimCoord, AuxCoord @@ -345,7 +345,7 @@ def make_sample_data_3D_3blobs(data_type="iris", invert_xy=False): ------- sample_data : iris.cube.Cube or xarray.DataArray """ - + from iris.cube import Cube from iris.coords import DimCoord, AuxCoord @@ -487,23 +487,23 @@ def make_dataset_from_arr( ---------- in_arr: array-like The input array to convert to iris/xarray - + data_type: str('xarray' or 'iris'), optional Type of the dataset to return Default is 'xarray' - + time_dim_num: int or None, optional What axis is the time dimension on, None for a single timestep Default is None - + z_dim_num: int or None, optional What axis is the z dimension on, None for a 2D array Default is None - + y_dim_num: int, optional What axis is the y dimension on, typically 0 for a 2D array Default is 0 - + x_dim_num: int, optional What axis is the x dimension on, typically 1 for a 2D array Deafult is 1 @@ -513,7 +513,7 @@ def make_dataset_from_arr( Iris or xarray dataset with everything we need for feature detection/tracking. """ - + import xarray as xr import iris @@ -550,43 +550,43 @@ def make_feature_blob( shape="rectangle", amplitude=1, ): - """Function to make a defined "blob" in location (zloc, yloc, xloc) with + """Function to make a defined "blob" in location (zloc, yloc, xloc) with user-specified shape and amplitude. Note that this function will round the size and locations to the nearest point within the array. - + Parameters ---------- in_arr: array-like input array to add the "blob" to - + h1_loc: float Center hdim_1 location of the blob, required - + h2_loc: float Center hdim_2 location of the blob, required - + v_loc: float, optional Center vdim location of the blob, optional. If this is None, we assume that the dataset is 2D. Default is None - + h1_size: float, optional Size of the bubble in array coordinates in hdim_1 Default is 1 - + h2_size: float, optional Size of the bubble in array coordinates in hdim_2 Default is 1 - + v_size: float, optional Size of the bubble in array coordinates in vdim Default is 1 - + shape: str('rectangle'), optional The shape of the blob that is added. For now, this is just rectangle 'rectangle' adds a rectangular/rectangular prism bubble with constant amplitude `amplitude`. Default is "rectangle" - + amplitude: float, optional Maximum amplitude of the blob Default is 1 @@ -596,7 +596,7 @@ def make_feature_blob( array-like An array with the same type as `in_arr` that has the blob added. """ - + import xarray as xr # Check if z location is there and set our 3D-ness based on this. @@ -650,27 +650,27 @@ def set_arr_2D_3D( ---------- in_arr: array-like Array of values to set - + value: int, float, or array-like of size (end_v-start_v, end_h1-start_h1, end_h2-start_h2) The value to assign to in_arr. This will work to assign an array, but the array must have the same dimensions as the size specified in the function. - + start_h1: int Start index to set for hdim_1 - + end_h1: int End index to set for hdim_1 (exclusive, so it acts like [start_h1:end_h1]) - + start_h2: int Start index to set for hdim_2 - + end_h2: int End index to set for hdim_2 - + start_v: int, optional Start index to set for vdim Default is None - + end_v: int, optional End index to set for vdim Default is None @@ -680,7 +680,7 @@ def set_arr_2D_3D( array-like in_arr with the new values set. """ - + if start_v is not None and end_v is not None: in_arr[start_v:end_v, start_h1:end_h1, start_h2:end_h2] = value else: @@ -712,61 +712,61 @@ def generate_single_feature( ---------- start_h1: float Starting point of the feature in hdim_1 space - + start_h2: float Starting point of the feature in hdim_2 space - + start_v: float, optional Starting point of the feature in vdim space (if 3D). For 2D, set to None. Default is None - + spd_h1: float, optional Speed (per frame) of the feature in hdim_1 Default is 1 - + spd_h2: float, optional Speed (per frame) of the feature in hdim_2 Default is 1 - + spd_v: float, optional Speed (per frame) of the feature in vdim Default is 1 - + min_h1: int, optional Minimum value of hdim_1 allowed. If PBC_flag is not 'none', then this will be used to know when to wrap around periodic boundaries. If PBC_flag is 'none', features will disappear if they are above/below these bounds. Default is 0 - + max_h1: int, optional Similar to min_h1, but the max value of hdim_1 allowed. Default is 1000 - + min_h2: int, optional Similar to min_h1, but the minimum value of hdim_2 allowed. Default is 0 - + max_h2: int, optional Similar to min_h1, but the maximum value of hdim_2 allowed. Default is 1000 - + num_frames: int, optional Number of frames to generate Default is 1 - + dt: datetime.timedelta, optional Difference in time between each frame Default is datetime.timedelta(minutes=5) - + start_date: datetime.datetime, optional Start datetime Default is datetime.datetime(2022, 1, 1, 0) - + frame_start: int, optional Number to start the frame at Default is 1 - + feature_num: int, optional What number to start the feature at Default is 1 diff --git a/tobac/tracking.py b/tobac/tracking.py index 6daea5db..c88c7fe2 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -46,7 +46,7 @@ def linking_trackpy( ): """Perform Linking of features in trajectories. - + The linking determines which of the features detected in a specific timestep is identical to an existing feature in the previous timestep. For each existing feature, the movement within a time step @@ -64,26 +64,26 @@ def linking_trackpy( data. The algorithm creates a continuous track for the cloud that most directly follows the direction of travel of the preceding or following cell path. [5]_ - + Parameters ---------- features : xarray.Dataset Detected features to be linked. - + field_in : xarray.DataArray Input field to perform the watershedding on (2D or 3D for one specific point in time). - + dt : float Time resolution of tracked features. - + dxy : float Grid spacing of the input data. - + d_max : float, optional Maximum search range Default is None. - + d_min : float, optional Variations in the shape of the regions used to determine the positions of the features can lead to quasi-instantaneous shifts @@ -92,59 +92,59 @@ def linking_trackpy( jeopardising the tracking procedure. To prevent this, tobac uses an additional minimum radius of the search range. [5]_ Default is None. - + subnetwork_size : int, optional Maximum size of subnetwork for linking. Default is None. - + v_max : float, optional Speed at which features are allowed to move. Default is None. - + memory : int, optional Number of output timesteps features allowed to vanish for to be still considered tracked. Default is 0. .. warning :: This parameter should be used with caution, as it can lead to erroneous trajectory linking, espacially for data with low time resolution. [5]_ - + stubs : int, optional Minimum number of timesteps of a tracked cell to be reported Default is 1 - + time_cell_min : float, optional Minimum length in time of tracked cell to be reported in minutes Default is None. - + order : int, optional Order of polynomial used to extrapolate trajectory into gaps and ond start and end point. Default is 1. - + extrapolate : int, optional Number or timesteps to extrapolate trajectories. Default is 0. - + method_linking : {'random', 'predict'}, optional Flag choosing method used for trajectory linking. Default is 'random'. - + adaptive_step : float, optional Reduce search range by multiplying it by this factor. - + adaptive_stop : float, optional If not None, when encountering an oversize subnet, retry by progressively reducing search_range until the subnet is solvable. If search_range becomes <= adaptive_stop, give up and raise a SubnetOversizeException. Default is None - + cell_number_start : int, optional Cell number for first tracked cell. Default is 1 - + cell_number_unassigned: int - Number to set the unassigned/non-tracked cells to. Note that if you set this + Number to set the unassigned/non-tracked cells to. Note that if you set this to `np.nan`, the data type of 'cell' will change to float. - Default is -1 - + Default is -1 + Returns ------- trajectories_final : xarray.Dataset @@ -156,7 +156,7 @@ def linking_trackpy( ValueError If method_linking is neither 'random' nor 'predict'. """ - + # from trackpy import link_df import trackpy as tp from copy import deepcopy @@ -332,7 +332,7 @@ def fill_gaps( Trajectories from trackpy with with filled gaps and potentially extrapolated. """ - + from scipy.interpolate import InterpolatedUnivariateSpline logging.debug("start filling gaps") From b550e3b713b3caaab0be1f5debd6a977ae889b20 Mon Sep 17 00:00:00 2001 From: Juli Date: Thu, 16 Jun 2022 14:22:43 +0200 Subject: [PATCH 021/187] black formatting --- tobac/merge_split.py | 322 ++++++++++++++++++++++++------------------- 1 file changed, 181 insertions(+), 141 deletions(-) diff --git a/tobac/merge_split.py b/tobac/merge_split.py index 0cbbc7e5..4183a3f0 100644 --- a/tobac/merge_split.py +++ b/tobac/merge_split.py @@ -1,4 +1,4 @@ -#Tobac merge and split v0.1 +# Tobac merge and split v0.1 from geopy.distance import geodesic from networkx import * @@ -6,6 +6,7 @@ from pandas.core.common import flatten import xarray as xr + def compress_all(nc_grids, min_dims=2): for var in nc_grids: if len(nc_grids[var].dims) >= min_dims: @@ -14,7 +15,7 @@ def compress_all(nc_grids, min_dims=2): nc_grids[var].encoding["complevel"] = 4 nc_grids[var].encoding["contiguous"] = False return nc_grids - + def standardize_track_dataset(TrackedFeatures, Mask, Projection): """ Combine a feature mask with the feature data table into a common dataset. @@ -36,70 +37,107 @@ def standardize_track_dataset(TrackedFeatures, Mask, Projection): """ feature_standard_names = { # new variable name, and long description for the NetCDF attribute - 'frame':('feature_time_index', - 'positional index of the feature along the time dimension of the mask, from 0 to N-1'), - 'hdim_1':('feature_hdim1_coordinate', - 'position of the feature along the first horizontal dimension in grid point space; a north-south coordinate for dim order (time, y, x).' - 'The numbering is consistent with positional indexing of the coordinate, but can be' - 'fractional, to account for a centroid not aligned to the grid.'), - 'hdim_2':('feature_hdim2_coordinate', - 'position of the feature along the second horizontal dimension in grid point space; an east-west coordinate for dim order (time, y, x)' - 'The numbering is consistent with positional indexing of the coordinate, but can be' - 'fractional, to account for a centroid not aligned to the grid.'), - 'idx':('feature_id_this_frame',), - 'num':('feature_grid_cell_count', - 'Number of grid points that are within the threshold of this feature'), - 'threshold_value':('feature_threshold_max', - "Feature number within that frame; starts at 1, increments by 1 to the number of features for each frame, and resets to 1 when the frame increments"), - 'feature':('feature_id', - "Unique number of the feature; starts from 1 and increments by 1 to the number of features"), - 'time':('feature_time','time of the feature, consistent with feature_time_index'), - 'timestr':('feature_time_str','String representation of the feature time, YYYY-MM-DD HH:MM:SS'), - 'projection_y_coordinate':('feature_projection_y_coordinate','y position of the feature in the projection given by ProjectionCoordinateSystem'), - 'projection_x_coordinate':('feature_projection_x_coordinate','x position of the feature in the projection given by ProjectionCoordinateSystem'), - 'lat':('feature_latitude','latitude of the feature'), - 'lon':('feature_longitude','longitude of the feature'), - 'ncells':('feature_ncells','number of grid cells for this feature (meaning uncertain)'), - 'areas':('feature_area',), - 'isolated':('feature_isolation_flag',), - 'num_objects':('number_of_feature_neighbors',), - 'cell':('feature_parent_cell_id',), - 'time_cell':('feature_parent_cell_elapsed_time',), - 'segmentation_mask':('2d segmentation mask',) + "frame": ( + "feature_time_index", + "positional index of the feature along the time dimension of the mask, from 0 to N-1", + ), + "hdim_1": ( + "feature_hdim1_coordinate", + "position of the feature along the first horizontal dimension in grid point space; a north-south coordinate for dim order (time, y, x)." + "The numbering is consistent with positional indexing of the coordinate, but can be" + "fractional, to account for a centroid not aligned to the grid.", + ), + "hdim_2": ( + "feature_hdim2_coordinate", + "position of the feature along the second horizontal dimension in grid point space; an east-west coordinate for dim order (time, y, x)" + "The numbering is consistent with positional indexing of the coordinate, but can be" + "fractional, to account for a centroid not aligned to the grid.", + ), + "idx": ("feature_id_this_frame",), + "num": ( + "feature_grid_cell_count", + "Number of grid points that are within the threshold of this feature", + ), + "threshold_value": ( + "feature_threshold_max", + "Feature number within that frame; starts at 1, increments by 1 to the number of features for each frame, and resets to 1 when the frame increments", + ), + "feature": ( + "feature_id", + "Unique number of the feature; starts from 1 and increments by 1 to the number of features", + ), + "time": ( + "feature_time", + "time of the feature, consistent with feature_time_index", + ), + "timestr": ( + "feature_time_str", + "String representation of the feature time, YYYY-MM-DD HH:MM:SS", + ), + "projection_y_coordinate": ( + "feature_projection_y_coordinate", + "y position of the feature in the projection given by ProjectionCoordinateSystem", + ), + "projection_x_coordinate": ( + "feature_projection_x_coordinate", + "x position of the feature in the projection given by ProjectionCoordinateSystem", + ), + "lat": ("feature_latitude", "latitude of the feature"), + "lon": ("feature_longitude", "longitude of the feature"), + "ncells": ( + "feature_ncells", + "number of grid cells for this feature (meaning uncertain)", + ), + "areas": ("feature_area",), + "isolated": ("feature_isolation_flag",), + "num_objects": ("number_of_feature_neighbors",), + "cell": ("feature_parent_cell_id",), + "time_cell": ("feature_parent_cell_elapsed_time",), + "segmentation_mask": ("2d segmentation mask",), + } + new_feature_var_names = { + k: feature_standard_names[k][0] + for k in feature_standard_names.keys() + if k in TrackedFeatures.variables.keys() } - new_feature_var_names = {k:feature_standard_names[k][0] for k in feature_standard_names.keys() - if k in TrackedFeatures.variables.keys()} - TrackedFeatures = TrackedFeatures.drop(['cell_parent_track_id']) + TrackedFeatures = TrackedFeatures.drop(["cell_parent_track_id"]) # Combine Track and Mask variables. Use the 'feature' variable as the coordinate variable instead of # the 'index' variable and call the dimension 'feature' - ds = xr.merge([TrackedFeatures.swap_dims({'index':'feature'}).drop('index').rename_vars(new_feature_var_names), - Mask]) + ds = xr.merge( + [ + TrackedFeatures.swap_dims({"index": "feature"}) + .drop("index") + .rename_vars(new_feature_var_names), + Mask, + ] + ) # Add the projection data back in - ds['ProjectionCoordinateSystem']=Projection + ds["ProjectionCoordinateSystem"] = Projection # Convert the cell ID variable from float to integer - if 'int' not in str(TrackedFeatures.cell.dtype): + if "int" not in str(TrackedFeatures.cell.dtype): # The raw output from the tracking is actually an object array # array([nan, 2, 3], dtype=object) # (and is cast to a float array when saved as NetCDF, I think). # Cast to float. - int_cell = xr.zeros_like(TrackedFeatures.cell, dtype='int64') + int_cell = xr.zeros_like(TrackedFeatures.cell, dtype="int64") - cell_id_data = TrackedFeatures.cell.astype('float64') + cell_id_data = TrackedFeatures.cell.astype("float64") valid_cell = np.isfinite(cell_id_data) valid_cell_ids = cell_id_data[valid_cell] if not (np.unique(valid_cell_ids) > 0).all(): - raise AssertionError('Lowest cell ID cell is less than one, conflicting with use of zero to indicate an untracked cell') - int_cell[valid_cell] = valid_cell_ids.astype('int64') - #ds['feature_parent_cell_id'] = int_cell + raise AssertionError( + "Lowest cell ID cell is less than one, conflicting with use of zero to indicate an untracked cell" + ) + int_cell[valid_cell] = valid_cell_ids.astype("int64") + # ds['feature_parent_cell_id'] = int_cell return ds - -def merge_split(TRACK,distance = 25000,frame_len = 5): - ''' +def merge_split(TRACK, distance=25000, frame_len=5): + """ function to postprocess tobac track data for merge/split cells Input: TRACK: xarray dataset of tobac Track information @@ -125,9 +163,9 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): both_ds = compress_all(both_ds) both_ds.to_netcdf(os.path.join(savedir,'Track_features_merges.nc')) - ''' - track_groups = TRACK.groupby('cell') - cell_ids = {cid:len(v) for cid, v in track_groups.groups.items()} + """ + track_groups = TRACK.groupby("cell") + cell_ids = {cid: len(v) for cid, v in track_groups.groups.items()} id_data = np.fromiter(cell_ids.keys(), dtype=int) count_data = np.fromiter(cell_ids.values(), dtype=int) all_frames = np.sort(np.unique(TRACK.frame)) @@ -136,43 +174,40 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): a_names = list() b_names = list() dist = list() - - + for i in id_data: - #print(i) + # print(i) a_pointx = track_groups[i].projection_x_coordinate[-1].values a_pointy = track_groups[i].projection_y_coordinate[-1].values for j in id_data: b_pointx = track_groups[j].projection_x_coordinate[0].values b_pointy = track_groups[j].projection_y_coordinate[0].values - d = np.linalg.norm(np.array((a_pointx, a_pointy)) - np.array((b_pointx, b_pointy))) + d = np.linalg.norm( + np.array((a_pointx, a_pointy)) - np.array((b_pointx, b_pointy)) + ) if d <= distance: - a_points.append([a_pointx,a_pointy]) + a_points.append([a_pointx, a_pointy]) b_points.append([b_pointx, b_pointy]) dist.append(d) a_names.append(i) b_names.append(j) - - - - -# for i in id_data: -# a_pointx = track_groups[i].grid_longitude[-1].values -# a_pointy = track_groups[i].grid_latitude[-1].values -# for j in id_data: -# b_pointx = track_groups[j].grid_longitude[0].values -# b_pointy = track_groups[j].grid_latitude[0].values -# d = geodesic((a_pointy,a_pointx),(b_pointy,b_pointx)).km -# if d <= distance: -# a_points.append([a_pointx,a_pointy]) -# b_points.append([b_pointx, b_pointy]) -# dist.append(d) -# a_names.append(i) -# b_names.append(j) + # for i in id_data: + # a_pointx = track_groups[i].grid_longitude[-1].values + # a_pointy = track_groups[i].grid_latitude[-1].values + # for j in id_data: + # b_pointx = track_groups[j].grid_longitude[0].values + # b_pointy = track_groups[j].grid_latitude[0].values + # d = geodesic((a_pointy,a_pointx),(b_pointy,b_pointx)).km + # if d <= distance: + # a_points.append([a_pointx,a_pointy]) + # b_points.append([b_pointx, b_pointy]) + # dist.append(d) + # a_names.append(i) + # b_names.append(j) id = [] - for i in range(len(dist)-1, -1, -1): + for i in range(len(dist) - 1, -1, -1): if a_names[i] == b_names[i]: id.append(i) a_points.pop(i) @@ -182,25 +217,27 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): b_names.pop(i) else: continue - + g = Graph() for i in np.arange(len(dist)): - g.add_edge(a_names[i], b_names[i],weight=dist[i]) + g.add_edge(a_names[i], b_names[i], weight=dist[i]) tree = minimum_spanning_edges(g) tree_list = list(minimum_spanning_edges(g)) new_tree = [] - for i,j in enumerate(tree_list): + for i, j in enumerate(tree_list): frame_a = np.nanmax(track_groups[j[0]].frame.values) frame_b = np.nanmin(track_groups[j[1]].frame.values) if np.abs(frame_a - frame_b) <= frame_len: new_tree.append(tree_list[i][0:2]) new_tree_arr = np.array(new_tree) - TRACK['cell_parent_track_id'] = np.zeros(len(TRACK['cell'].values)) - cell_id = np.unique(TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))]) - track_id = dict() #same size as number of total merged tracks + TRACK["cell_parent_track_id"] = np.zeros(len(TRACK["cell"].values)) + cell_id = np.unique( + TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))] + ) + track_id = dict() # same size as number of total merged tracks arr = np.array([0]) for p in cell_id: @@ -211,7 +248,7 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): k = np.where(new_tree_arr == p) if len(k[0]) == 0: track_id[p] = [p] - arr = np.append(arr,p) + arr = np.append(arr, p) else: temp1 = list(np.unique(new_tree_arr[k[0]])) temp = list(np.unique(new_tree_arr[k[0]])) @@ -226,112 +263,113 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): if len(temp1) == len(temp): break temp1 = np.array(temp) - + for i in temp1: k2 = np.where(new_tree_arr == i) temp.append(list(np.unique(new_tree_arr[k2[0]]).squeeze())) temp = list(flatten(temp)) temp = list(np.unique(temp)) - arr = np.append(arr,np.unique(temp)) - - track_id[np.nanmax(np.unique(temp))] = list(np.unique(temp)) - - + arr = np.append(arr, np.unique(temp)) - storm_id = [0] #default because we don't track larger storm systems *yet* - print('found storm id') + track_id[np.nanmax(np.unique(temp))] = list(np.unique(temp)) + storm_id = [0] # default because we don't track larger storm systems *yet* + print("found storm id") - track_parent_storm_id = np.repeat(0, len(track_id)) #This will always be zero when we don't track larger storm systems *yet* - print('found track parent storm ids') + track_parent_storm_id = np.repeat( + 0, len(track_id) + ) # This will always be zero when we don't track larger storm systems *yet* + print("found track parent storm ids") track_ids = np.array(list(track_id.keys())) - print('found track ids') + print("found track ids") - - cell_id = list(np.unique(TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))])) - print('found cell ids') + cell_id = list( + np.unique( + TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))] + ) + ) + print("found cell ids") cell_parent_track_id = [] for i, id in enumerate(track_id): - - if len(track_id[int(id)]) == 1: + + if len(track_id[int(id)]) == 1: cell_parent_track_id.append(int(id)) else: - cell_parent_track_id.append(np.repeat(int(id),len(track_id[int(id)]))) - + cell_parent_track_id.append(np.repeat(int(id), len(track_id[int(id)]))) cell_parent_track_id = list(flatten(cell_parent_track_id)) - print('found cell parent track ids') + print("found cell parent track ids") feature_parent_cell_id = list(TRACK.cell.values.astype(float)) - print('found feature parent cell ids') + print("found feature parent cell ids") - #This version includes all the feature regardless of if they are used in cells or not. + # This version includes all the feature regardless of if they are used in cells or not. feature_id = list(TRACK.feature.values.astype(int)) - print('found feature ids') + print("found feature ids") - feature_parent_storm_id = np.repeat(0,len(feature_id)) #we don't do storms atm - print('found feature parent storm ids') + feature_parent_storm_id = np.repeat(0, len(feature_id)) # we don't do storms atm + print("found feature parent storm ids") - feature_parent_track_id = [] + feature_parent_track_id = [] feature_parent_track_id = np.zeros(len(feature_id)) for i, id in enumerate(feature_id): cellid = feature_parent_cell_id[i] if np.isnan(cellid): - feature_parent_track_id[i] = -1 + feature_parent_track_id[i] = -1 else: j = np.where(cell_id == cellid) j = np.squeeze(j) trackid = cell_parent_track_id[j] feature_parent_track_id[i] = trackid - - print('found feature parent track ids') + print("found feature parent track ids") storm_child_track_count = [len(track_id)] - print('found storm child track count') + print("found storm child track count") track_child_cell_count = [] - for i,id in enumerate(track_id): + for i, id in enumerate(track_id): track_child_cell_count.append(len(track_id[int(id)])) - print('found track child cell count') - + print("found track child cell count") cell_child_feature_count = [] - for i,id in enumerate(cell_id): + for i, id in enumerate(cell_id): cell_child_feature_count.append(len(track_groups[id].feature.values)) - print('found cell child feature count') + print("found cell child feature count") - storm_child_cell_count = [len(cell_id)] + storm_child_cell_count = [len(cell_id)] storm_child_feature_count = [len(feature_id)] - - storm_dim = 'nstorms' - track_dim = 'ntracks' - cell_dim = 'ncells' - feature_dim = 'nfeatures' - - d = xr.Dataset({ - 'storm_id': (storm_dim, storm_id), - 'track_id': (track_dim, track_ids), - 'track_parent_storm_id': (track_dim, track_parent_storm_id), - 'cell_id': (cell_dim, cell_id), - 'cell_parent_track_id': (cell_dim, cell_parent_track_id), - 'feature_id': (feature_dim, feature_id), - 'feature_parent_cell_id': (feature_dim, feature_parent_cell_id), - 'feature_parent_track_id': (feature_dim, feature_parent_track_id), - 'feature_parent_storm_id': (feature_dim, feature_parent_storm_id), - 'storm_child_track_count': (storm_dim, storm_child_track_count), - 'storm_child_cell_count': (storm_dim, storm_child_cell_count), - 'storm_child_feature_count': (storm_dim, storm_child_feature_count), - 'track_child_cell_count': (track_dim, track_child_cell_count), - 'cell_child_feature_count': (cell_dim, cell_child_feature_count), - }) - d = d.set_coords(['feature_id','cell_id', 'track_id', 'storm_id']) + + storm_dim = "nstorms" + track_dim = "ntracks" + cell_dim = "ncells" + feature_dim = "nfeatures" + + d = xr.Dataset( + { + "storm_id": (storm_dim, storm_id), + "track_id": (track_dim, track_ids), + "track_parent_storm_id": (track_dim, track_parent_storm_id), + "cell_id": (cell_dim, cell_id), + "cell_parent_track_id": (cell_dim, cell_parent_track_id), + "feature_id": (feature_dim, feature_id), + "feature_parent_cell_id": (feature_dim, feature_parent_cell_id), + "feature_parent_track_id": (feature_dim, feature_parent_track_id), + "feature_parent_storm_id": (feature_dim, feature_parent_storm_id), + "storm_child_track_count": (storm_dim, storm_child_track_count), + "storm_child_cell_count": (storm_dim, storm_child_cell_count), + "storm_child_feature_count": (storm_dim, storm_child_feature_count), + "track_child_cell_count": (track_dim, track_child_cell_count), + "cell_child_feature_count": (cell_dim, cell_child_feature_count), + } + ) + d = d.set_coords(["feature_id", "cell_id", "track_id", "storm_id"]) assert len(track_id) == len(track_parent_storm_id) assert len(cell_id) == len(cell_parent_track_id) @@ -340,6 +378,8 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): assert sum(storm_child_cell_count) == len(cell_id) assert sum(storm_child_feature_count) == len(feature_id) assert sum(track_child_cell_count) == len(cell_id) - assert sum([sum(cell_child_feature_count),(len(np.where(feature_parent_track_id < 0)[0]))]) == len(feature_id) - - return d \ No newline at end of file + assert sum( + [sum(cell_child_feature_count), (len(np.where(feature_parent_track_id < 0)[0]))] + ) == len(feature_id) + + return d From edc5f25bdf60021d2bfa98dbf115fe16a87eefcf Mon Sep 17 00:00:00 2001 From: Juli Date: Thu, 16 Jun 2022 14:39:40 +0200 Subject: [PATCH 022/187] black formatting --- tobac/merge_split.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tobac/merge_split.py b/tobac/merge_split.py index 4183a3f0..2f4915a7 100644 --- a/tobac/merge_split.py +++ b/tobac/merge_split.py @@ -18,7 +18,7 @@ def compress_all(nc_grids, min_dims=2): def standardize_track_dataset(TrackedFeatures, Mask, Projection): - """ Combine a feature mask with the feature data table into a common dataset. + """Combine a feature mask with the feature data table into a common dataset. Also renames th returned by tobac.themes.tobac_v1.segmentation @@ -34,7 +34,7 @@ def standardize_track_dataset(TrackedFeatures, Mask, Projection): TODO: Add metadata attributes to - """ + """ feature_standard_names = { # new variable name, and long description for the NetCDF attribute "frame": ( @@ -141,20 +141,20 @@ def merge_split(TRACK, distance=25000, frame_len=5): function to postprocess tobac track data for merge/split cells Input: TRACK: xarray dataset of tobac Track information - + distance: float, optional distance threshold prior to adding a pair of points into the minimum spanning tree. Default is 25000 meters. - + frame_len: float, optional threshold for the spanning length within which two points can be separated. Default is five (5) frames. Output: - d: xarray dataset of + d: xarray dataset of feature position along 1st horizontal dimension hdim2_index: float feature position along 2nd horizontal dimension - + Example: d = merge_split(Track) ds = standardize_track_dataset(Track, refl_mask, data['ProjectionCoordinateSystem']) @@ -162,7 +162,7 @@ def merge_split(TRACK, distance=25000, frame_len=5): both_ds = xr.merge([ds, d],compat ='override') both_ds = compress_all(both_ds) both_ds.to_netcdf(os.path.join(savedir,'Track_features_merges.nc')) - + """ track_groups = TRACK.groupby("cell") cell_ids = {cid: len(v) for cid, v in track_groups.groups.items()} From 9130f0094eaa1b6f257e0fce137a5157fee276cd Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:07:32 +0200 Subject: [PATCH 023/187] fixing typo Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index b01400b5..e28dcaee 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -2,7 +2,7 @@ This module provides a set of routines that enables performing analyses and deriving statistics for individual clouds, such as the time series of integrated properties and vertical profiles. It also provides -routines to calculate summary statistics of the entire populatin of +routines to calculate summary statistics of the entire population of tracked clouds in the cloud field like histograms of cloud areas/volumes or cloud mass and a detailed cell lifetime analysis. These analysis routines are all built in a modular manner. Thus, users can reuse the From 40d9f9948f93e5a74d40cd9869eed11e26a4f0d7 Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:08:33 +0200 Subject: [PATCH 024/187] fixing typo Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index e28dcaee..3cbbed0e 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -8,7 +8,7 @@ routines are all built in a modular manner. Thus, users can reuse the most basic methods for interacting with the data structure of the package in their own analysis procedures in Python. This includes -functions perfomring simple tasks like looping over all identified +functions performing simple tasks like looping over all identified objects or cloud trajectories and masking arrays for the analysis of individual cloud objects. Plotting routines include both visualizations for individual convective cells and their properties. [1]_ From 075171155e239981f8a6e36cf8daa97ae4e7589e Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:09:07 +0200 Subject: [PATCH 025/187] deleting note Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/analysis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index 3cbbed0e..e3bedb8a 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -23,7 +23,6 @@ Notes ----- -unsure about page numer in the reference """ import pandas as pd From bfc771fe41a45208ae277b83822ea53de298e3cc Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:09:21 +0200 Subject: [PATCH 026/187] add unit Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/feature_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index be57d5ec..f2efe659 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -658,7 +658,7 @@ def filter_min_distance(features, dxy, min_distance): features : pandas.DataFrame dxy : float - Grid spacing of the input data. + Grid spacing (in meter) of the input data. min_distance : float, optional Minimum distance between detected features. From 7d1d3d23af3bfb9cf2625540ec2722a69e2f737b Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:09:38 +0200 Subject: [PATCH 027/187] add unit Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/feature_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index f2efe659..d3f286b4 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -661,7 +661,7 @@ def filter_min_distance(features, dxy, min_distance): Grid spacing (in meter) of the input data. min_distance : float, optional - Minimum distance between detected features. + Minimum distance (in meter) between detected features. Returns ------- From 90c871b0644fbfb7d6d68ce54c18174ca0cba74b Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:09:51 +0200 Subject: [PATCH 028/187] fixing typo Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/segmentation.py b/tobac/segmentation.py index eb79c3dd..3c587726 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -44,7 +44,7 @@ def segmentation_3D( method="watershed", max_distance=None, ): - """Wraper for the segmentation()-function. + """Wrapper for the segmentation()-function. Notes ---------- From fb8f5519d31047cf201cc1a19547a941b2b4ca29 Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:09:59 +0200 Subject: [PATCH 029/187] fixing typo Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/testing.py b/tobac/testing.py index ef58f42d..160619df 100644 --- a/tobac/testing.py +++ b/tobac/testing.py @@ -99,7 +99,7 @@ def make_sample_data_2D_3blobs(data_type="iris"): The grid has a grid spacing of 1km in both horizontal directions and 100 grid cells in x direction and 200 in y direction. Time resolution is 1 minute and the total length of the dataset is - 100 minutes around a abritraty date (2000-01-01 12:00). + 100 minutes around a arbitrary date (2000-01-01 12:00). The longitude and latitude coordinates are added as 2D aux coordinates and arbitrary, but in realisitic range. The data contains three individual blobs travelling on a linear From 17c317eb4d18d38be864e7a38208354c50222b5e Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:10:20 +0200 Subject: [PATCH 030/187] fixing typo Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index e3bedb8a..d332723b 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -545,7 +545,7 @@ def calculate_velocity_individual(feature_old, feature_new, method_distance=None and projection_y_coordinate or latitude and longitude coordinates. method_distance : {None, 'xy', 'latlon'}, optional - Method of distance calculation, used to calculate the velocit. + Method of distance calculation, used to calculate the velocity. 'xy' uses the length of the vector between the two features, 'latlon' uses the haversine distance. None checks wether the required coordinates are present and starts with 'xy'. From 799a818a1703960c2fc4b0e6102ebea439e91d04 Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:11:32 +0200 Subject: [PATCH 031/187] fixing typo Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/feature_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index d3f286b4..7994d0df 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -3,7 +3,7 @@ This module can work with any two-dimensional field either present or derived from the input data. To identify the features, contiguous regions above or -below a threshold arendetermined and labelled individually. +below a threshold are determined and labelled individually. To describe the specific location of the feature at a specific point in time, different spatial properties are used to describe the identified region. [2]_ From 1270bcfc631753933a0f5d88f89e83364ccafe7c Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:12:10 +0200 Subject: [PATCH 032/187] deleting comment Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/feature_detection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index 7994d0df..9a549a3d 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -135,7 +135,6 @@ def feature_position( def test_overlap(region_inner, region_outer): """Test for overlap between two regions - (probably scope for further speedup here) Parameters ---------- From 8a9b9ddde2feba17246637c59a89ccd2f620fe4d Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:13:24 +0200 Subject: [PATCH 033/187] improved wording Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/feature_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index 9a549a3d..f1465743 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -412,7 +412,7 @@ def feature_detection_multithreshold_timestep( Based on iteratively finding regions above/below a set of thresholds. Smoothing the input data with the Gaussian filter makes - output more reliable. [2]_ + output less sensitive to noisiness of input data. Parameters ---------- From 1e110e8ea095032170b57aab9de02079b09e775d Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:13:59 +0200 Subject: [PATCH 034/187] fixing typo Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/testing.py b/tobac/testing.py index 160619df..cb2c750f 100644 --- a/tobac/testing.py +++ b/tobac/testing.py @@ -506,7 +506,7 @@ def make_dataset_from_arr( x_dim_num: int, optional What axis is the x dimension on, typically 1 for a 2D array - Deafult is 1 + Default is 1 Returns ------- From 7a3f2caefb08d0aa20e4c65682714b466382718f Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:14:30 +0200 Subject: [PATCH 035/187] fixing typo Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index c88c7fe2..1c4d2a5b 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -3,7 +3,7 @@ The individual features and associated area/volumes identified in each timestep have to be linked into cloud trajectories to analyse the time evolution of cloud properties for a better understanding of -the underlying pyhsical processes. [5]_ +the underlying physical processes. The implementations are structured in a way that allows for the future addition of more complex tracking methods recording a more complex network of relationships between cloud objects at different points in From f6f81b4133276a3fccbce1d452754bdbc336382c Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:16:17 +0200 Subject: [PATCH 036/187] delete remnant Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index 1c4d2a5b..172cbd9b 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -7,7 +7,7 @@ The implementations are structured in a way that allows for the future addition of more complex tracking methods recording a more complex network of relationships between cloud objects at different points in -time. [5]_ +time. References ---------- From 76fa690158d8384334432912cd3614f7b83c4a30 Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:16:35 +0200 Subject: [PATCH 037/187] remove remnant Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/segmentation.py b/tobac/segmentation.py index 3c587726..6f9d0a95 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -20,7 +20,7 @@ feature at all grid points belonging to that specific cloud/updraft. this mask can be conveniently and efficiently used to select the volume of each cloud object at a specific time step for further analysis or -visialization. [4]_ +visialization. References ---------- From ceace3c4d574be9ed881c246307d05a7f42865c8 Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:17:12 +0200 Subject: [PATCH 038/187] fixing typos Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index 172cbd9b..e5b85f2a 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -55,7 +55,7 @@ def linking_trackpy( candidate features by restricting the search to a circular search region centered around the predicted position of the feature in the next time step. For newly initialized trajectories, where no - velocity from previous timesteps is available, the algorithm resort + velocity from previous time steps is available, the algorithm resorts to the average velocity of the nearest tracked objects. v_max and d_min are given as physical quantities and then converted into pixel-based values used in trackpy. This allows for cloud tracking From 918fce90ee1e319687516cd79b21de6bc6f0c459 Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:17:36 +0200 Subject: [PATCH 039/187] removing remnant Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index e5b85f2a..8184e8b0 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -63,7 +63,7 @@ def linking_trackpy( independent of the temporal and spatial resolution of the input data. The algorithm creates a continuous track for the cloud that most directly follows the direction of travel of the preceding or - following cell path. [5]_ + following cell path. Parameters ---------- From b77a517728f115b348138043d0962aee37f2676a Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:18:16 +0200 Subject: [PATCH 040/187] correcting datatype Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index 8184e8b0..9cdc1ecb 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -389,7 +389,7 @@ def add_cell_time(t): Parameters ---------- - t : pandas DataFrame + t : pandas.DataFrame trajectories with added coordinates Returns From e0e4ddad4b5e4767f271df74bd36f910fb1b6db7 Mon Sep 17 00:00:00 2001 From: Nils_P <76663232+snilsn@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:18:42 +0200 Subject: [PATCH 041/187] correctign datatype Co-authored-by: JuliaKukulies <44163060+JuliaKukulies@users.noreply.github.com> --- tobac/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index 9cdc1ecb..14bd7bdb 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -394,7 +394,7 @@ def add_cell_time(t): Returns ------- - t : pandas dataframe + t : pandas.Dataframe trajectories with added cell time """ From b905205cc2bbbcb903f027fc77a07d2276c54afe Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 23 Jun 2022 17:15:00 +0200 Subject: [PATCH 042/187] adding requested changes --- tobac/analysis.py | 15 ++++++--------- tobac/feature_detection.py | 17 ++++++++--------- tobac/segmentation.py | 11 ++++++----- tobac/tracking.py | 13 +++++++------ 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index d332723b..c24b4f12 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -15,11 +15,12 @@ References ---------- -.. [1] Heikenfeld, M., Marinescu, P. J., Christensen, M., Watson-Parris, - D., Senf, F., van den Heever, S. C., and Stier, P.: tobac v1.0: - towards a flexible framework for tracking and analysis of clouds in - diverse datasets, Geosci. Model Dev. Discuss., - https://doi.org/10.5194/gmd-2019-105 , in review, 2019, 10. +.. Heikenfeld, M., Marinescu, P. J., Christensen, M., + Watson-Parris, D., Senf, F., van den Heever, S. C. + & Stier, P. (2019). tobac 1.2: towards a flexible + framework for tracking and analysis of clouds in + diverse datasets. Geoscientific Model Development, + 12(11), 4551-4570. Notes ----- @@ -589,10 +590,6 @@ def calculate_velocity(track, method_distance=None): DataFrame from the input, with an additional column 'v', contain the value of the velocity for every feature at every possible timestep - - Notes - ----- - needs short summary, description and type of track """ for cell_i, track_i in track.groupby("cell"): diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index f1465743..dff0c35a 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -1,8 +1,7 @@ """Provide feature detection. -This module can work with any two-dimensional field -either present or derived from the input data. To -identify the features, contiguous regions above or +This module can work with any two-dimensional field. +To identify the features, contiguous regions above or below a threshold are determined and labelled individually. To describe the specific location of the feature at a specific point in time, different spatial properties @@ -10,12 +9,12 @@ References ---------- -.. [2] Heikenfeld, M., Marinescu, P. J., Christensen, M., - Watson-Parris, D., Senf, F., van den Heever, S. C., - and Stier, P.: tobac v1.0: towards a flexible framework - for tracking and analysis of clouds in diverse datasets, - Geosci. Model Dev. Discuss., - https://doi.org/10.5194/gmd-2019-105 , in review, 2019, 6f. +.. Heikenfeld, M., Marinescu, P. J., Christensen, M., + Watson-Parris, D., Senf, F., van den Heever, S. C. + & Stier, P. (2019). tobac 1.2: towards a flexible + framework for tracking and analysis of clouds in + diverse datasets. Geoscientific Model Development, + 12(11), 4551-4570. """ import logging diff --git a/tobac/segmentation.py b/tobac/segmentation.py index 6f9d0a95..f0b95736 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -24,11 +24,12 @@ References ---------- -.. [4] Heikenfeld, M., Marinescu, P. J., Christensen, M., Watson-Parris, - D., Senf, F., van den Heever, S. C., and Stier, P.: tobac v1.0: - towards a flexible framework for tracking and analysis of clouds in - diverse datasets, Geosci. Model Dev. Discuss., - https://doi.org/10.5194/gmd-2019-105 , in review, 2019, 7ff. +.. Heikenfeld, M., Marinescu, P. J., Christensen, M., + Watson-Parris, D., Senf, F., van den Heever, S. C. + & Stier, P. (2019). tobac 1.2: towards a flexible + framework for tracking and analysis of clouds in + diverse datasets. Geoscientific Model Development, + 12(11), 4551-4570. """ import logging diff --git a/tobac/tracking.py b/tobac/tracking.py index 14bd7bdb..3f83cc8a 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -11,11 +11,12 @@ References ---------- -.. [5] Heikenfeld, M., Marinescu, P. J., Christensen, M., Watson-Parris, - D., Senf, F., van den Heever, S. C., and Stier, P.: tobac v1.0: - towards a flexible framework for tracking and analysis of clouds in - diverse datasets, Geosci. Model Dev. Discuss., - https://doi.org/10.5194/gmd-2019-105 , in review, 2019, 9f. +.. Heikenfeld, M., Marinescu, P. J., Christensen, M., + Watson-Parris, D., Senf, F., van den Heever, S. C. + & Stier, P. (2019). tobac 1.2: towards a flexible + framework for tracking and analysis of clouds in + diverse datasets. Geoscientific Model Development, + 12(11), 4551-4570. """ import logging @@ -63,7 +64,7 @@ def linking_trackpy( independent of the temporal and spatial resolution of the input data. The algorithm creates a continuous track for the cloud that most directly follows the direction of travel of the preceding or - following cell path. + following cell path. Parameters ---------- From a7cdc19b136860c7d0c3d949a0a48ee8402a78c4 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 23 Jun 2022 18:33:25 +0200 Subject: [PATCH 043/187] remove comments --- tobac/segmentation.py | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/tobac/segmentation.py b/tobac/segmentation.py index f0b95736..f824d4d5 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -46,11 +46,6 @@ def segmentation_3D( max_distance=None, ): """Wrapper for the segmentation()-function. - - Notes - ---------- - - Obsolete? """ return segmentation( @@ -75,12 +70,7 @@ def segmentation_2D( method="watershed", max_distance=None, ): - """Wraper for the segmentation()-function. - - Notes - ---------- - - Obsolete? + """Wrapper for the segmentation()-function. """ return segmentation( features, @@ -410,24 +400,14 @@ def segmentation( def watershedding_3D(track, field_in, **kwargs): - """Wraper for the segmentation()-function. - - Notes - ---------- - - Obsolete? + """Wrapper for the segmentation()-function. """ kwargs.pop("method", None) return segmentation_3D(track, field_in, method="watershed", **kwargs) def watershedding_2D(track, field_in, **kwargs): - """Wraper for the segmentation()-function. - - Notes - ---------- - - Obsolete? + """Wrapper for the segmentation()-function. """ kwargs.pop("method", None) return segmentation_2D(track, field_in, method="watershed", **kwargs) From cd5dacde88b4d61a891a719fc63920c1444c3684 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Thu, 23 Jun 2022 18:36:02 +0200 Subject: [PATCH 044/187] fix formatting --- tobac/segmentation.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tobac/segmentation.py b/tobac/segmentation.py index f824d4d5..397e8e6d 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -45,8 +45,7 @@ def segmentation_3D( method="watershed", max_distance=None, ): - """Wrapper for the segmentation()-function. - """ + """Wrapper for the segmentation()-function.""" return segmentation( features, @@ -70,8 +69,7 @@ def segmentation_2D( method="watershed", max_distance=None, ): - """Wrapper for the segmentation()-function. - """ + """Wrapper for the segmentation()-function.""" return segmentation( features, field, @@ -400,14 +398,12 @@ def segmentation( def watershedding_3D(track, field_in, **kwargs): - """Wrapper for the segmentation()-function. - """ + """Wrapper for the segmentation()-function.""" kwargs.pop("method", None) return segmentation_3D(track, field_in, method="watershed", **kwargs) def watershedding_2D(track, field_in, **kwargs): - """Wrapper for the segmentation()-function. - """ + """Wrapper for the segmentation()-function.""" kwargs.pop("method", None) return segmentation_2D(track, field_in, method="watershed", **kwargs) From eb412dec6398e4a54fbbcdd379d6a754f41ee8ec Mon Sep 17 00:00:00 2001 From: Max Heikenfeld Date: Mon, 27 Jun 2022 00:04:12 +0200 Subject: [PATCH 045/187] add zenodo json file --- .zenodo.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .zenodo.json diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 00000000..0ad3078f --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,12 @@ +{ + "title": "tobac - Tracking and Object-based Analysis of Clouds", + "creators": [ + { "name": "Heikenfeld, Max", + "affiliation": "University of Oxford", + "orcid": "0000-0001-8124-8048"} + ], + "keywords": [ + "cloud tracking" + ], + "upload_type": "software" +} \ No newline at end of file From 47d3c47146e3692dc6c057c4c7f3a9ec372a3da2 Mon Sep 17 00:00:00 2001 From: Juli Date: Mon, 27 Jun 2022 09:21:56 +0200 Subject: [PATCH 046/187] added my author info to json file --- .zenodo.json | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 0ad3078f..b4ca4157 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,12 +1,17 @@ -{ - "title": "tobac - Tracking and Object-based Analysis of Clouds", - "creators": [ - { "name": "Heikenfeld, Max", - "affiliation": "University of Oxford", - "orcid": "0000-0001-8124-8048"} - ], - "keywords": [ - "cloud tracking" - ], - "upload_type": "software" -} \ No newline at end of file +{ + "title": "tobac - Tracking and Object-based Analysis of Clouds", + "creators": [ + { + "name": "Heikenfeld, Max", + "affiliation": "University of Oxford", + "orcid": "0000-0001-8124-8048", + }, + { + "name": "Julia Kukulies", + "affiliation": "University of Gothenburg (Sweden)", + "orcid": "0000-0001-6084-0069", + }, + ], + "keywords": ["cloud tracking"], + "upload_type": "software", +} From e140c9c7f951b21b64ab93e7f432fa41ec7d1183 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Tue, 28 Jun 2022 15:05:05 +0200 Subject: [PATCH 047/187] replacing the word cloud, more explanation of subnetwork_size, clarifying tracking algorithm --- tobac/analysis.py | 10 +++++----- tobac/centerofgravity.py | 14 ++++++------- tobac/segmentation.py | 22 ++++++++++---------- tobac/tracking.py | 43 ++++++++++++++++++++++------------------ 4 files changed, 46 insertions(+), 43 deletions(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index c24b4f12..9d797e52 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -1,16 +1,16 @@ """Provide tools to analyse and visualize the tracked objects. This module provides a set of routines that enables performing analyses -and deriving statistics for individual clouds, such as the time series +and deriving statistics for individual tracks, such as the time series of integrated properties and vertical profiles. It also provides routines to calculate summary statistics of the entire population of -tracked clouds in the cloud field like histograms of cloud areas/volumes -or cloud mass and a detailed cell lifetime analysis. These analysis +tracked features in the field like histograms of areas/volumes +or mass and a detailed cell lifetime analysis. These analysis routines are all built in a modular manner. Thus, users can reuse the most basic methods for interacting with the data structure of the package in their own analysis procedures in Python. This includes functions performing simple tasks like looping over all identified -objects or cloud trajectories and masking arrays for the analysis of -individual cloud objects. Plotting routines include both visualizations +objects or trajectories and masking arrays for the analysis of +individual features. Plotting routines include both visualizations for individual convective cells and their properties. [1]_ References diff --git a/tobac/centerofgravity.py b/tobac/centerofgravity.py index 8a5cd84d..472412a6 100644 --- a/tobac/centerofgravity.py +++ b/tobac/centerofgravity.py @@ -18,14 +18,14 @@ def calculate_cog(tracks, mass, mask): 'projection_y_coordinate'). mask : iris.cube.Cube - Cube containing mask (int > where belonging to cloud volume, - 0 everywhere else). + Cube containing mask (int > where belonging to area/volume + of feature, 0 else). Returns ------- tracks_out : pandas.DataFrame Dataframe containing t, x, y, z positions of center of gravity - and total cloud mass each tracked cells at each timestep. + and total mass of each tracked cell at each timestep. """ from .utils import mask_cube_cell @@ -63,14 +63,14 @@ def calculate_cog_untracked(mass, mask): 'projection_y_coordinate'). mask : iris.cube.Cube - Cube containing mask (int > where belonging to cloud volume, - 0 everywhere else). + Cube containing mask (int > where belonging to area/volume + of feature, 0 else). Returns ------- tracks_out : pandas.DataFrame Dataframe containing t, x, y, z positions of center of gravity - and total cloud mass for untracked part of domain. + and total mass for untracked part of the domain. """ from pandas import DataFrame @@ -117,7 +117,7 @@ def calculate_cog_domain(mass): ------- tracks_out : pandas.DataFrame Dataframe containing t, x, y, z positions of center of gravity - and total cloud mass. + and total mass of the entire domain. """ from pandas import DataFrame diff --git a/tobac/segmentation.py b/tobac/segmentation.py index 397e8e6d..c59b1448 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -13,14 +13,12 @@ the threshold condition are set to the respective marker. The algorithm then fills the area (2D) or volume (3D) based on the input field starting from these markers until reaching the threshold. If two or more -cloud objects are directly connected, the border runs along the -watershed line between the two regions. This procedure creates a mask of -the same shape as the input data, with zeros at all grid points where -there is no cloud or updraft and the integer number of the associated -feature at all grid points belonging to that specific cloud/updraft. -this mask can be conveniently and efficiently used to select the volume -of each cloud object at a specific time step for further analysis or -visialization. +features are directly connected, the border runs along the +watershed line between the two regions. This procedure creates a mask +that has the same form as the input data, with the corresponding integer +number at all grid points that belong to a feature, else with zero. This +mask can be conveniently and efficiently used to select the volume of each +feature at a specific time step for further analysis or visialization. References ---------- @@ -137,8 +135,8 @@ def segmentation_timestep( Returns ------- segmentation_out : iris.cube.Cube - Cloud mask, 0 outside and integer numbers according to track - inside the clouds. + Mask, 0 outside and integer numbers according to track + inside the ojects. features_out : pandas.DataFrame Feature dataframe including the number of cells (2D or 3D) in @@ -335,8 +333,8 @@ def segmentation( Returns ------- segmentation_out : iris.cube.Cube - Cloud mask, 0 outside and integer numbers according to track - inside the clouds. + Mask, 0 outside and integer numbers according to track + inside the area/volume of the feature. features_out : pandas.DataFrame Feature dataframe including the number of cells (2D or 3D) in diff --git a/tobac/tracking.py b/tobac/tracking.py index 3f83cc8a..fce3203e 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -1,12 +1,12 @@ """Provide tracking methods. The individual features and associated area/volumes identified in -each timestep have to be linked into cloud trajectories to analyse -the time evolution of cloud properties for a better understanding of +each timestep have to be linked into trajectories to analyse +the time evolution of their properties for a better understanding of the underlying physical processes. The implementations are structured in a way that allows for the future addition of more complex tracking methods recording a more complex -network of relationships between cloud objects at different points in +network of relationships between features at different points in time. References @@ -49,22 +49,21 @@ def linking_trackpy( """Perform Linking of features in trajectories. The linking determines which of the features detected in a specific - timestep is identical to an existing feature in the previous - timestep. For each existing feature, the movement within a time step - is extrapolated based on the velocities in a number previous time - steps. The algorithm then breaks the search process down to a few - candidate features by restricting the search to a circular search - region centered around the predicted position of the feature in the - next time step. For newly initialized trajectories, where no - velocity from previous time steps is available, the algorithm resorts - to the average velocity of the nearest tracked objects. v_max and - d_min are given as physical quantities and then converted into - pixel-based values used in trackpy. This allows for cloud tracking - that is controlled by physically-based parameters that are + timestep is most likely identical to an existing feature in the + previous timestep. For each existing feature, the movement within + a time step is extrapolated based on the velocities in a number + previous time steps. The algorithm then breaks the search process + down to a few candidate features by restricting the search to a + circular search region centered around the predicted position of + the feature in the next time step. For newly initialized trajectories, + where no velocity from previous time steps is available, the + algorithm resorts to the average velocity of the nearest tracked + objects. v_max and d_min are given as physical quantities and then + converted into pixel-based values used in trackpy. This allows for + tracking that is controlled by physically-based parameters that are independent of the temporal and spatial resolution of the input - data. The algorithm creates a continuous track for the cloud that - most directly follows the direction of travel of the preceding or - following cell path. + data. The algorithm creates a continuous track for the feature + that is the most probable based on the previous cell path. Parameters ---------- @@ -95,7 +94,12 @@ def linking_trackpy( Default is None. subnetwork_size : int, optional - Maximum size of subnetwork for linking. Default is None. + Maximum size of subnetwork for linking. This parameter should be + adjusted when using adaptive search. Usually a lower value is desired + in that case. For a more in depth explanation have look + `here `_ + If None, 30 is used for regular search and 15 for adaptive search. + Default is None. v_max : float, optional Speed at which features are allowed to move. Default is None. @@ -152,6 +156,7 @@ def linking_trackpy( This enables filtering the resulting trajectories, e.g. to reject trajectories that are only partially captured at the boundaries of the input field both in space and time. [5]_ + Raises ------ ValueError From cb4b3bb3324377c769d1a0526ed0d29a91e2d938 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Tue, 28 Jun 2022 15:10:14 +0200 Subject: [PATCH 048/187] reformatting --- tobac/tracking.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index fce3203e..11f526b4 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -49,20 +49,20 @@ def linking_trackpy( """Perform Linking of features in trajectories. The linking determines which of the features detected in a specific - timestep is most likely identical to an existing feature in the - previous timestep. For each existing feature, the movement within - a time step is extrapolated based on the velocities in a number - previous time steps. The algorithm then breaks the search process - down to a few candidate features by restricting the search to a - circular search region centered around the predicted position of - the feature in the next time step. For newly initialized trajectories, - where no velocity from previous time steps is available, the - algorithm resorts to the average velocity of the nearest tracked - objects. v_max and d_min are given as physical quantities and then - converted into pixel-based values used in trackpy. This allows for + timestep is most likely identical to an existing feature in the + previous timestep. For each existing feature, the movement within + a time step is extrapolated based on the velocities in a number + previous time steps. The algorithm then breaks the search process + down to a few candidate features by restricting the search to a + circular search region centered around the predicted position of + the feature in the next time step. For newly initialized trajectories, + where no velocity from previous time steps is available, the + algorithm resorts to the average velocity of the nearest tracked + objects. v_max and d_min are given as physical quantities and then + converted into pixel-based values used in trackpy. This allows for tracking that is controlled by physically-based parameters that are independent of the temporal and spatial resolution of the input - data. The algorithm creates a continuous track for the feature + data. The algorithm creates a continuous track for the feature that is the most probable based on the previous cell path. Parameters @@ -94,8 +94,8 @@ def linking_trackpy( Default is None. subnetwork_size : int, optional - Maximum size of subnetwork for linking. This parameter should be - adjusted when using adaptive search. Usually a lower value is desired + Maximum size of subnetwork for linking. This parameter should be + adjusted when using adaptive search. Usually a lower value is desired in that case. For a more in depth explanation have look `here `_ If None, 30 is used for regular search and 15 for adaptive search. @@ -156,7 +156,7 @@ def linking_trackpy( This enables filtering the resulting trajectories, e.g. to reject trajectories that are only partially captured at the boundaries of the input field both in space and time. [5]_ - + Raises ------ ValueError From fee001439f61cf3bee54dae8f37b066de94c7293 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer <76663232+snilsn@users.noreply.github.com> Date: Tue, 28 Jun 2022 21:38:16 +0200 Subject: [PATCH 049/187] adding author info Pfeifer (#33) --- .zenodo.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.zenodo.json b/.zenodo.json index b4ca4157..c7ee8170 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -11,6 +11,11 @@ "affiliation": "University of Gothenburg (Sweden)", "orcid": "0000-0001-6084-0069", }, + { + "name": "Nils Pfeifer", + "affiliation": "University of Leipzig", + "orcid": "0000-0002-5350-1445", + }, ], "keywords": ["cloud tracking"], "upload_type": "software", From b014ff0673d8a0ba34a1707e9a6307b967df184d Mon Sep 17 00:00:00 2001 From: Eric Bruning Date: Tue, 28 Jun 2022 14:43:40 -0500 Subject: [PATCH 050/187] Add Bruning affiliation (#32) Co-authored-by: Max Heikenfeld --- .zenodo.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.zenodo.json b/.zenodo.json index c7ee8170..7a2ff14d 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -12,10 +12,15 @@ "orcid": "0000-0001-6084-0069", }, { + "name": "Eric Bruning", + "affiliation": "Texas Tech University", + "orcid": "0000-0003-1959-442X", + }, + { "name": "Nils Pfeifer", "affiliation": "University of Leipzig", "orcid": "0000-0002-5350-1445", - }, + } ], "keywords": ["cloud tracking"], "upload_type": "software", From 7685f47c876256b21a1fa5b6462b13c1d539b574 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Tue, 28 Jun 2022 16:57:47 -0600 Subject: [PATCH 051/187] Added documentation from 3D/PBC PR Moved documentation from #127 to this PR. --- doc/_static/theme_overrides.css | 16 +++++ doc/analysis.rst | 5 +- doc/conf.py | 38 +++++++++++ doc/data_input.rst | 22 ++----- doc/feature_detection_base_out_vars.csv | 16 +++++ doc/feature_detection_output.rst | 12 ++++ doc/index.rst | 28 ++++++-- doc/installation.rst | 18 ++++-- doc/modules.rst | 7 ++ doc/plotting.rst | 4 +- doc/tobac.rst | 85 +++++++++++++++++++++++++ 11 files changed, 216 insertions(+), 35 deletions(-) create mode 100644 doc/_static/theme_overrides.css create mode 100644 doc/conf.py create mode 100644 doc/feature_detection_base_out_vars.csv create mode 100644 doc/feature_detection_output.rst create mode 100644 doc/modules.rst create mode 100644 doc/tobac.rst diff --git a/doc/_static/theme_overrides.css b/doc/_static/theme_overrides.css new file mode 100644 index 00000000..690ecc91 --- /dev/null +++ b/doc/_static/theme_overrides.css @@ -0,0 +1,16 @@ +/* from https://github.com/readthedocs/sphinx_rtd_theme/issues/117#issuecomment-41506687 */ +/* with augmentations from https://github.com/readthedocs/sphinx_rtd_theme/issues/117#issuecomment-153083280 */ +/* override table width restrictions */ +@media screen and (min-width: 767px) { + + .wy-table-responsive table td { + /* !important prevents the common CSS stylesheets from + overriding this as on RTD they are loaded after this stylesheet */ + white-space: normal !important; + } + + .wy-table-responsive { + overflow: visible !important; + } + + } diff --git a/doc/analysis.rst b/doc/analysis.rst index 13483a2a..eb6b934b 100644 --- a/doc/analysis.rst +++ b/doc/analysis.rst @@ -1,5 +1,4 @@ Analysis -======= -tobac provides several analysis functions that allow for the calculation of important quantities based on the tracking results. This includes the calculation of important properties of the tracked objects such as cloud lifetimes, cloud areas/volumes, but also allows for a convenient calculation of statistics for arbitratry fields of the same shape as as the input data used for the tracking analysis. - +========= +tobac provides several analysis functions that allow for the calculation of important quantities based on the tracking results. This includes the calculation of properties such as cloud lifetimes and cloud areas/volumes, but also allows for a convenient calculation of statistics for arbitrary fields of the same shape as as the input data used for the tracking analysis. diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 00000000..6d5ae7ee --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,38 @@ +import sphinx_rtd_theme +import sys, os + +sys.path.insert(0, os.path.abspath('extensions')) + +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', + 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.ifconfig', + 'sphinx_rtd_theme','sphinx.ext.napoleon'] + + +html_theme = "sphinx_rtd_theme" + +project = u'tobac' + + +def setup(app): + app.add_css_file("theme_overrides.css") + +autodoc_mock_imports = ['numpy', 'scipy', 'scikit-image', 'pandas', 'pytables', 'matplotlib', 'iris', + 'cf-units', 'xarray', 'cartopy', 'trackpy', 'numba'] + +sys.path.insert(0, os.path.abspath("../")) + +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = False +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True +napoleon_preprocess_types = False +napoleon_type_aliases = None +napoleon_attr_annotations = True \ No newline at end of file diff --git a/doc/data_input.rst b/doc/data_input.rst index 68eb3277..d0746ff5 100644 --- a/doc/data_input.rst +++ b/doc/data_input.rst @@ -1,5 +1,6 @@ -*Data input and output -====================== +.. _Data Input: +Data input +========== Input data for tobac should consist of one or more fields on a common, regular grid with a time dimension and two or more spatial dimensions. The input data should also include latitude and longitude coordinates, either as 1-d or 2-d variables depending on the grid used. @@ -12,21 +13,6 @@ The output of the different analysis steps in tobac are output as either pandas (quick note on terms; “feature” is a detected object at a single time step. “cell” is a series of features linked together over multiple timesteps) -Overview of the output dataframe from feature_dection - - Frame: the index along the time dimension in which the feature was detected - - hdim_1, hdim_2…: the central index location of the feature in the spatial dimensions of the input data - - num: the number of connected pixels that meet the threshold for detection for this feature - - threshold_value: the threshold value that was used to detect this feature. When using feature_detection_multithreshold this is the max/min (depending on whether the threshold values are increasing (e.g. precip) or decreasing (e.g. temperature) with intensity) threshold value used. - - feature: a unique integer >0 value corresponding to each feature - - time: the date and time of the feature, in datetime format - - timestr: the date and time of the feature in string format - - latitude, longitude: the central lat/lon of the feature - - x,y, etc: these are the central location of the feature in the original dataset coordinates - -Also in the tracked output: - - Cell: The cell which each feature belongs to. Is nan if the feature could not be linked into a valid trajectory - - time_cell: The time of the feature along the tracked cell, in numpy.timedelta64[ns] format - -The output from segmentation is an n-dimensional array produced by segmentation in the same coordinates of the input data. It has a single field, which provides a mask for the pixels in the data which are linked to each detected feature by the segmentation routine. Each non-zero value in the array provides the integer value of the feature which that region is attributed to. +For information on feature detection *output*, see `Feature Detection Output`_. Note that in future versions of tobac, it is planned to combine both output data types into a single hierarchical data structure containing both spatial and object information. Additional information about the planned changes can be found in the v2.0-dev project, as well as the tobac roadmap diff --git a/doc/feature_detection_base_out_vars.csv b/doc/feature_detection_base_out_vars.csv new file mode 100644 index 00000000..d04a2a2d --- /dev/null +++ b/doc/feature_detection_base_out_vars.csv @@ -0,0 +1,16 @@ +Variable Name,Description,Units,Type +frame,Frame/time/file number; starts from 0 and increments by 1 to N times. ,n/a,int64 +idx,"Feature number within that frame; starts at 1, increments by 1 to the number of features for each frame, and resets to 1 when the frame increments",n/a,int +hdim_1,"First horizontal dimension in grid point space (typically, although not always, N/S or y space)",Number of grid points,float +hdim_2,"Second horizontal dimension in grid point space (typically, although not always, E/W or x space)",Number of grid points,float +num,Number of grid points that are within the threshold of this feature,Number of grid points,int +threshold_value,Maximum threshold value reached by the feature,Units of the input feature,int +feature,Unique number of the feature; starts from 1 and increments by 1 to the number of features,n/a,int +time,Time of the feature,Date and time,object/python datetime +timestr,String representation of the feature time,YYYY-MM-DD HH:MM:SS,object/string +y,Grid point y location of the feature (see hdim_1 and hdim_2). Note that this is not necessarily an integer value depending on your selection of position_threshold,Number of grid points,float +x,Grid point x location of the feature (see also y),Number of grid points,float +projection_y_coordinate,Y location of the feature in projection coordinates,Projection coordinates (usually m),float +projection_x_coordinate,X location of the feature in projection coodinates,Projection coordinates (usually m),float +lat,Latitude of the feature,Decimal degrees,float +lon,Longitude of the feature,Decimal degrees,float \ No newline at end of file diff --git a/doc/feature_detection_output.rst b/doc/feature_detection_output.rst new file mode 100644 index 00000000..336632d9 --- /dev/null +++ b/doc/feature_detection_output.rst @@ -0,0 +1,12 @@ +.. _Feature Detection Output: +Feature detection output +------------------------- + +Feature detection outputs a `pandas` dataframe with several variables. The variables, (with column names listed in the `Variable Name` column), are described below, with units. Note that while these variables come initially from the feature detection step, segmentation and tracking also share some of these variables. + +Variables that are common to all feature detection files: + +.. csv-table:: tobac Feature Detection Output Variables + :file: ./feature_detection_base_out_vars.csv + :widths: 3, 35, 3, 3 + :header-rows: 1 diff --git a/doc/index.rst b/doc/index.rst index 86540728..c7027654 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,18 +1,20 @@ tobac - Tracking and Object-Based Analysis of Clouds ------------ +------------------------------------------------------- -**tobac** is a Python package to identify, track and analyse clouds in different types of gridded datasets, such as 3D model output from cloud resolving model simulations or 2D data from satellite retrievals. +**tobac** is a Python package to identify, track and analyze clouds in different types of gridded datasets, such as 3D model output from cloud-resolving model simulations or 2D data from satellite retrievals. -The software is set up in a modular way to include different algorithms for feature identification, tracking and analyses. -In the current implementation, individual features are indentified as either maxima or minima in a two dimensional time varying field. The volume/are associated with the identified object can be determined based on a time-varying 2D or 3D field and a threshold value. In the tracking step, the identified objects are linked into consistent trajectories representing the cloud over its lifecycle. Analysis and visualisation methods provide a convenient way to use and display the tracking results. +The software is set up in a modular way to include different algorithms for feature identification, tracking, and analyses. **tobac** is also input variable agnostic and doesn't rely on specific input variables to work. -Version 1.0 of tobac and some example applications are described in a paper that is currently in discussion for the journal "Geoscientific Model Development" as: +In the current implementation, individual features are identified as either maxima or minima in a two-dimensional time-varying field. An associated volume can then be determined using these features with a separate (or identical) time-varying 2D or 3D field and a threshold value. The identified objects are linked into consistent trajectories representing the cloud over its lifecycle in the tracking step. Analysis and visualization methods provide a convenient way to use and display the tracking results. -Heikenfeld, M., Marinescu, P. J., Christensen, M., Watson-Parris, D., Senf, F., van den Heever, S. C., and Stier, P.: tobac v1.0: towards a flexible framework for tracking and analysis of clouds in diverse datasets, Geosci. Model Dev. Discuss., `https://doi.org/10.5194/gmd-2019-105 `_ , in review, 2019. +Version 1.2 of tobac and some example applications are described in a manuscript in Geoscientific Model Development as: -The project is currently extended by several contributors to include additional workflows and algorithms using the same structure, synthax and data formats. +Heikenfeld, M., Marinescu, P. J., Christensen, M., Watson-Parris, D., Senf, F., van den Heever, S. C., and Stier, P.: tobac 1.2: towards a flexible framework for tracking and analysis of clouds in diverse datasets, Geosci. Model Dev., 12, 4551–4570, https://doi.org/10.5194/gmd-12-4551-2019, 2019. + +The project is currently being extended by several contributors to include additional workflows and algorithms using the same structure, syntax, and data formats. .. toctree:: + :caption: Basic Information :maxdepth: 2 :numbered: @@ -24,3 +26,15 @@ The project is currently extended by several contributors to include additional analysis plotting examples + +.. toctree:: + :caption: Output Documentation + :maxdepth: 2 + + feature_detection_output + +.. toctree:: + :caption: API Reference + :maxdepth: 2 + + tobac diff --git a/doc/installation.rst b/doc/installation.rst index f8895e4a..7bb07da9 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -1,21 +1,29 @@ Installation ------------ -tobac is now capable of working with both Python 2 and Python 3 (tested for 2.7,3.6 and 3.7) installations. +tobac works with Python 3 installations. -The easiest way is to install the most recent version of tobac via conda and the conda-forge channel: +The easiest way is to install the most recent version of tobac via conda or mamba and the conda-forge channel: ``` conda install -c conda-forge tobac ``` +or +``` +mamba install -c conda-forge tobac +``` -This will take care of all necessary dependencies and should do the job for most users and also allows for an easy update of the installation by +This will take care of all necessary dependencies and should do the job for most users. It also allows for an easy update of the installation by ``` conda update -c conda-forge tobac ``` +or +``` +mamba update -c conda-forge tobac +``` -You can also install conda via pip, which is mainly interesting for development purposed or to use specific development branches for the Github repository. +You can also install conda via pip, which is mainly interesting for development purposes or using specific development branches for the Github repository. The follwoing python packages are required (including dependencies of these packages): @@ -39,4 +47,4 @@ You can also clone the package with any of the two following commands: and install the package from the locally cloned version (The trailing slash is actually necessary): - ``pip install --upgrade tobac/`` + ``pip install --upgrade tobac/`` \ No newline at end of file diff --git a/doc/modules.rst b/doc/modules.rst new file mode 100644 index 00000000..5d673df2 --- /dev/null +++ b/doc/modules.rst @@ -0,0 +1,7 @@ +tobac +===== + +.. toctree:: + :maxdepth: 4 + + tobac \ No newline at end of file diff --git a/doc/plotting.rst b/doc/plotting.rst index ae11e5cf..b2ad3653 100644 --- a/doc/plotting.rst +++ b/doc/plotting.rst @@ -1,3 +1,3 @@ Plotting -------- -tobac provides functions to conveniently visualise the tracking results and analyses. +======== +tobac provides functions to conveniently visualise the tracking results and analyses. \ No newline at end of file diff --git a/doc/tobac.rst b/doc/tobac.rst new file mode 100644 index 00000000..6f7f4ccb --- /dev/null +++ b/doc/tobac.rst @@ -0,0 +1,85 @@ +tobac package +============= + +Submodules +---------- + +tobac.analysis module +--------------------- + +.. automodule:: tobac.analysis + :members: + :undoc-members: + :show-inheritance: + +tobac.centerofgravity module +---------------------------- + +.. automodule:: tobac.centerofgravity + :members: + :undoc-members: + :show-inheritance: + +tobac.feature\_detection module +------------------------------- + +.. automodule:: tobac.feature_detection + :members: + :undoc-members: + :show-inheritance: + +tobac.plotting module +--------------------- + +.. automodule:: tobac.plotting + :members: + :undoc-members: + :show-inheritance: + +tobac.segmentation module +------------------------- + +.. automodule:: tobac.segmentation + :members: + :undoc-members: + :show-inheritance: + +tobac.testing module +-------------------- + +.. automodule:: tobac.testing + :members: + :undoc-members: + :show-inheritance: + +tobac.tracking module +--------------------- + +.. automodule:: tobac.tracking + :members: + :undoc-members: + :show-inheritance: + +tobac.utils module +------------------ + +.. automodule:: tobac.utils + :members: + :undoc-members: + :show-inheritance: + +tobac.wrapper module +-------------------- + +.. automodule:: tobac.wrapper + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tobac + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file From 495e603a79b4b6ae7e1925caa01aedab466cc917 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Tue, 28 Jun 2022 16:58:11 -0600 Subject: [PATCH 052/187] Updated formatting of examples --- doc/examples.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples.rst b/doc/examples.rst index 7a98fc71..5a79e8d7 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -1,5 +1,5 @@ Example notebooks -=============== +================= tobac is provided with a set of Jupyter notebooks that show examples of the application of tobac for different types of datasets. The notebooks can be found in the **examples** folder in the the repository. The necessary input data for these examples is avaliable on zenodo: From b53906072be06cf93c48cd32632805275f95688d Mon Sep 17 00:00:00 2001 From: Fabian Senf Date: Wed, 29 Jun 2022 09:44:31 +0200 Subject: [PATCH 053/187] Fabian added his name and affl. to zenodo file --- .zenodo.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 7a2ff14d..b6134618 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -15,11 +15,15 @@ "name": "Eric Bruning", "affiliation": "Texas Tech University", "orcid": "0000-0003-1959-442X", - }, - { + }, + { "name": "Nils Pfeifer", "affiliation": "University of Leipzig", "orcid": "0000-0002-5350-1445", + }, + "name": "Fabian Senf", + "affiliation": "Leibniz Institute for Tropospheric Research, Leipzig (Germany)", + "orcid": "0000-0003-1685-2657", } ], "keywords": ["cloud tracking"], From bcf2e0825f1f1e001c92a4bd419c2298f5d910fe Mon Sep 17 00:00:00 2001 From: Fabian Senf Date: Wed, 29 Jun 2022 09:59:13 +0200 Subject: [PATCH 054/187] bugifx: forgot a bracket... --- .zenodo.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.zenodo.json b/.zenodo.json index b6134618..70e9cef4 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -21,6 +21,7 @@ "affiliation": "University of Leipzig", "orcid": "0000-0002-5350-1445", }, + { "name": "Fabian Senf", "affiliation": "Leibniz Institute for Tropospheric Research, Leipzig (Germany)", "orcid": "0000-0003-1685-2657", From 4e68e7e3ada407282d4765863347b0fdeb5801cb Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Wed, 29 Jun 2022 10:51:29 +0200 Subject: [PATCH 055/187] work in comments --- tobac/analysis.py | 31 +++++++++++-------------------- tobac/feature_detection.py | 5 +++-- tobac/segmentation.py | 15 ++++++++------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/tobac/analysis.py b/tobac/analysis.py index 9d797e52..d8651522 100644 --- a/tobac/analysis.py +++ b/tobac/analysis.py @@ -85,10 +85,6 @@ def cell_statistics_all( Returns ------- None - - Notes - ----- - Not sure what this function does """ if cell_selection is None: cell_selection = np.unique(track["cell"]) @@ -159,10 +155,6 @@ def cell_statistics( Returns ------- None - - Notes - ----- - Not sure what this function does """ from iris.cube import Cube, CubeList @@ -319,10 +311,6 @@ def cog_cell( Returns ------- None - - Notes - ----- - Not sure what this function does """ from iris import Constraint @@ -383,7 +371,8 @@ def lifetime_histogram( If bin_edges is an int, it defines the number of equal-width bins in the given range. If bins is a ndarray, it defines a monotonically increasing array of bin edges, including the - rightmost edge. Default is np.arange(0, 200, 20). + rightmost edge. The unit is minutes. + Default is np.arange(0, 200, 20). density : bool, optional If False, the result will contain the number of samples in @@ -408,6 +397,7 @@ def lifetime_histogram( minutes, optional : ndarray Numpy.array of the lifetime of each feature in minutes. + Returned if return_values is True. """ @@ -480,9 +470,9 @@ def calculate_distance(feature_1, feature_2, method_distance=None): ------- distance : float or pandas.Series Float with the distance between the two features in meters if - the input are two pandas.Series containing one feature, - pandas.Series of the distancesif one of the inputs contains - multiple features. + the input are two pandas.Series containing one feature, + pandas.Series of the distances if one of the inputs contains + multiple features. """ if method_distance is None: @@ -1045,7 +1035,8 @@ def histogram_cellwise( Track, variable=None, bin_edges=None, quantity="max", density=False ): """Create a histogram of the maximum, minimum or mean of - a variable for the cells of a track. Essentially a wrapper + a variable for the cells (series of features linked together + over multiple timesteps) of a track. Essentially a wrapper of the numpy.histogram() method. Parameters @@ -1110,9 +1101,9 @@ def histogram_cellwise( def histogram_featurewise(Track, variable=None, bin_edges=None, density=False): - """Create a histogram of a variable from the features of a - track. Essentially a wrapper of the numpy.histogram() - method. + """Create a histogram of a variable from the features + (detected objects at a single time step) of a track. + Essentially a wrapper of the numpy.histogram() method. Parameters ---------- diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index dff0c35a..995979dd 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -424,7 +424,7 @@ def feature_detection_multithreshold_timestep( is None. min_num : int, optional - Default is 0. + This parameter is not used in the function. Default is 0. target : {'maximum', 'minimum'}, optinal Flag to determine if tracking is targetting minima or maxima @@ -578,7 +578,8 @@ def feature_detection_multithreshold( Returns ------- features : pandas.DataFrame - Detected features. + Detected features. The structure of this dataframe is explained + `here `__ """ from .utils import add_coordinates diff --git a/tobac/segmentation.py b/tobac/segmentation.py index c59b1448..ca06a798 100644 --- a/tobac/segmentation.py +++ b/tobac/segmentation.py @@ -287,13 +287,14 @@ def segmentation( max_distance=None, vertical_coord="auto", ): - """Use watershedding or random walker technique to determine region above - a threshold value around initial seeding position for all time steps of - the input data. Works both in 2D (based on single seeding point) and 3D and - returns a mask with zeros everywhere around the identified regions and the - feature id inside the regions. - - Calls segmentation_timestep at each individal timestep of the input data. + """Use watershedding to determine region above a threshold + value around initial seeding position for all time steps of + the input data. Works both in 2D (based on single seeding + point) and 3D and returns a mask with zeros everywhere around + the identified regions and the feature id inside the regions. + + Calls segmentation_timestep at each individal timestep of the + input data. Parameters ---------- From 15257019c080a62e5dac5409e7cc0fa931882e95 Mon Sep 17 00:00:00 2001 From: Xin Zhang Date: Wed, 29 Jun 2022 11:04:54 +0200 Subject: [PATCH 056/187] Add Xin affiliation --- .zenodo.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.zenodo.json b/.zenodo.json index 70e9cef4..af8be591 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -25,6 +25,11 @@ "name": "Fabian Senf", "affiliation": "Leibniz Institute for Tropospheric Research, Leipzig (Germany)", "orcid": "0000-0003-1685-2657", + }, + { + "name": "Xin Zhang", + "affiliation": "Nanjing University of Information Science & Technology (China)", + "orcid": "0000-0002-1756-6620", } ], "keywords": ["cloud tracking"], From 0d0f8a2705421e769ab2e5b08067edbd4780d242 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Wed, 29 Jun 2022 11:12:39 +0200 Subject: [PATCH 057/187] working in remarks tracking.py --- tobac/tracking.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tobac/tracking.py b/tobac/tracking.py index 11f526b4..7dd198d2 100644 --- a/tobac/tracking.py +++ b/tobac/tracking.py @@ -67,7 +67,7 @@ def linking_trackpy( Parameters ---------- - features : xarray.Dataset + features : pandas.DataFrame Detected features to be linked. field_in : xarray.DataArray @@ -90,14 +90,14 @@ def linking_trackpy( of the position of the feature by one or two grid cells even for a very high temporal resolution of the input data, potentially jeopardising the tracking procedure. To prevent this, tobac uses - an additional minimum radius of the search range. [5]_ + an additional minimum radius of the search range. Default is None. subnetwork_size : int, optional Maximum size of subnetwork for linking. This parameter should be adjusted when using adaptive search. Usually a lower value is desired in that case. For a more in depth explanation have look - `here `_ + `here `_ If None, 30 is used for regular search and 15 for adaptive search. Default is None. @@ -109,7 +109,7 @@ def linking_trackpy( be still considered tracked. Default is 0. .. warning :: This parameter should be used with caution, as it can lead to erroneous trajectory linking, - espacially for data with low time resolution. [5]_ + espacially for data with low time resolution. stubs : int, optional Minimum number of timesteps of a tracked cell to be reported @@ -152,10 +152,11 @@ def linking_trackpy( Returns ------- - trajectories_final : xarray.Dataset - This enables filtering the resulting trajectories, e.g. to - reject trajectories that are only partially captured at the - boundaries of the input field both in space and time. [5]_ + trajectories_final : pandas.DataFrame + Dataframe of the linked features, containing the variable 'cell', + with integers indicating the affiliation of a feature to a specific + track, and the variable 'time_cell' with the time the cell has + already existed. Raises ------ From 761ecb714ad2cb600270d18e35ad6be31b0f2135 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 29 Jun 2022 14:47:07 +0200 Subject: [PATCH 058/187] made all the necessary changes to have wavelengths in same unit as dxy -> unit: meter --- tobac/feature_detection.py | 10 +++++----- tobac/utils.py | 9 +++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index cf68ecaa..6ed9c0fb 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -367,7 +367,7 @@ def feature_detection_multithreshold_timestep( feature_number_start: int feature number to start with wavelength_filtering: tuple, optional - minimum and maximum wavelengths in km, if spectral filtering of input field is desired + minimum and maximum wavelengths in m, if spectral filtering of input field is desired Output: @@ -474,7 +474,7 @@ def feature_detection_multithreshold( min_distance: float minimum distance between detected features (m) wavelength_filtering: tuple, optional - minimum and maximum wavelengths in km, if spectral filtering of input field is desired + minimum and maximum wavelengths in m, if spectral filtering of input field is desired Output: features: pandas DataFrame detected features @@ -501,12 +501,12 @@ def feature_detection_multithreshold( # if wavelength_filtering is given, check that value cannot be larger than distances along x and y if wavelength_filtering is not None: - distance_x = field_in.shape[1] * (dxy / 1000) - distance_y = field_in.shape[2] * (dxy / 1000) + distance_x = field_in.shape[1] * (dxy) + distance_y = field_in.shape[2] * (dxy) distance = min(distance_x, distance_y) if wavelength_filtering[0] > distance or wavelength_filtering[1] > distance: raise ValueError( - "The given wavelengths cannot be larger than the total distance in km along the axes of the domain." + "The given wavelengths cannot be larger than the total distance in m along the axes of the domain." ) for i_time, data_i in enumerate(data_time): diff --git a/tobac/utils.py b/tobac/utils.py index 765a3606..2bf04371 100644 --- a/tobac/utils.py +++ b/tobac/utils.py @@ -594,9 +594,9 @@ def spectral_filtering( field_in: numpy.array 2D field with input data lambda_min: float - minimum wavelength in km + minimum wavelength in m lambda_max: float - maximum wavelength in km + maximum wavelength in m return_transfer_function: boolean, optional default: False. If set to True, then the 2D transfer function and the corresponding wavelengths are returned. @@ -618,9 +618,6 @@ def spectral_filtering( "Invalid value for dxy. Please provide the grid spacing in meter." ) - # convert grid spacing to km to get same units as given wavelengths - dxy = dxy / 1000 - # get number of grid cells in x and y direction Ni = field_in.shape[-2] Nj = field_in.shape[-1] @@ -635,7 +632,7 @@ def spectral_filtering( # if domain is a rectangle: # alpha is the normalized wavenumber in wavenumber space alpha = np.sqrt(m**2 / Ni**2 + n**2 / Nj**2) - # compute wavelengths for target grid in km + # compute wavelengths for target grid in m lambda_mn = 2 * dxy / alpha ############### create a 2D bandpass filter (butterworth) ####################### From 099552ce66021dc5680cb036e7dc45a69d3fc036 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 29 Jun 2022 15:21:14 +0200 Subject: [PATCH 059/187] added ValueError and warning to make sure wavelengths are given in right unit --- tobac/feature_detection.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index 6ed9c0fb..ea2816db 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -499,15 +499,30 @@ def feature_detection_multithreshold( if type(threshold) in [int, float]: threshold = [threshold] - # if wavelength_filtering is given, check that value cannot be larger than distances along x and y + # if wavelength_filtering is given, check that value cannot be larger than distances along x and y, + # that the value cannot be smaller or equal to the grid spacing + # and throw a warning if dxy and wavelengths have about the same order of magnitude if wavelength_filtering is not None: distance_x = field_in.shape[1] * (dxy) distance_y = field_in.shape[2] * (dxy) distance = min(distance_x, distance_y) - if wavelength_filtering[0] > distance or wavelength_filtering[1] > distance: + + # make sure the smaller value is taken as the minimum and the larger as the maximum + lambda_min = min(wavelength_filtering) + lambda_max = max(wavelength_filtering) + + if lambda_min > distance or lambda_max > distance: raise ValueError( "The given wavelengths cannot be larger than the total distance in m along the axes of the domain." ) + elif lambda_min <= dxy: + raise ValueError("The given minimum wavelength cannot be smaller than gridspacing dxy. Please note + that both dxy and the values for wavelength_filtering should be given in meter.") + + elif np.floor(np.log10(lambda_min)) - np.floor(np.log10(dxy)) > 1 : + warnings.warn("Warning: The values for dxy and the minimum wavelength are close in order of magnitude. Please + note that both dxy and for wavelength_filtering should be given in meter.") + for i_time, data_i in enumerate(data_time): time_i = data_i.coord("time").units.num2date(data_i.coord("time").points[0]) From a20dbed34f4e47ce9ae89a539694c97b98572686 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 29 Jun 2022 18:35:36 +0200 Subject: [PATCH 060/187] modified testing for spectral filtering based on dat with wave signal --- tobac/tests/test_utils.py | 40 ++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index 0f9755dd..5a265c10 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -1,32 +1,50 @@ import numpy as np import tobac.utils as tb_utils - +from scipy.fft import idct def test_spectral_filtering(): - """Testing tobac.utils.spectral_filtering with random test data.""" + """Testing tobac.utils.spectral_filtering with random test data that contains a wave signal.""" - # generate 3D data with random values - random_data = np.random.rand(100, 300, 500) - # define grid spacing [m] and minimum and maximum wavelength [km] + # set wavelengths for filtering and grid spacing dxy = 4000 - lambda_min, lambda_max = 400, 1000 + lambda_min = 400*1000 + lambda_max = 1000*1000 + + # get wavelengths for domain + matrix= np.zeros((200,100)) + Ni = matrix.shape[-2] + Nj = matrix.shape[-1] + m, n = np.meshgrid(np.arange(Ni), np.arange(Nj), indexing="ij") + alpha = np.sqrt(m**2 / Ni**2 + n**2 / Nj**2) + lambda_mn = 2 * dxy / alpha + + # seed wave signal that lies within wavelength range for filtering + signal_min = np.where(lambda_mn[0] < lambda_min)[0].min() + signal_idx = np.random.randint(signal_min, matrix.shape[-1]) + matrix[0,signal_idx] = 1 + wave_data = fft.idctn(matrix) - # use spectral filtering function on random data + # use spectral filtering function on random wave data transfer_function, filtered_data = tb_utils.spectral_filtering( - dxy, random_data, lambda_min, lambda_max, return_transfer_function=True + dxy, wave_data, lambda_min, lambda_max, return_transfer_function=True ) # a few checks on the output wavelengths = transfer_function[0] # first element in wavelengths-space is inf because normalized wavelengths are 0 here assert wavelengths[0, 0] == np.inf - # the first elements should correspond to twice the distance of the corresponding axis (in km) + # the first elements should correspond to twice the distance of the corresponding axis (in m) # this is because the maximum spatial scale is half a wavelength through the domain - assert wavelengths[1, 0] == (dxy / 1000) * random_data.shape[-2] * 2 - assert wavelengths[0, 1] == (dxy / 1000) * random_data.shape[-1] * 2 + assert wavelengths[1, 0] == (dxy) * wave_data.shape[-2] * 2 + assert wavelengths[0, 1] == (dxy) * wave_data.shape[-1] * 2 # check that filtered/ smoothed field exhibits smaller range of values assert (filtered_data.max() - filtered_data.min()) < ( random_data.max() - random_data.min() ) + + # because the randomly generated wave lies outside of range that is set for filtering, + # make sure that the filtering results in the disappearance of this signal + assert abs(np.floor(np.log10(filtered_data.mean())) - np.floor(np.log10(random_data.mean()) ) >= 1 + From 0c56b5584c779e64d041a285f10af5f2268337e4 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 29 Jun 2022 18:50:02 +0200 Subject: [PATCH 061/187] corrected import of fft function --- tobac/tests/test_utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index 5a265c10..03144e06 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -1,11 +1,10 @@ import numpy as np import tobac.utils as tb_utils -from scipy.fft import idct +from scipy import fft def test_spectral_filtering(): """Testing tobac.utils.spectral_filtering with random test data that contains a wave signal.""" - # set wavelengths for filtering and grid spacing dxy = 4000 lambda_min = 400*1000 @@ -46,5 +45,5 @@ def test_spectral_filtering(): # because the randomly generated wave lies outside of range that is set for filtering, # make sure that the filtering results in the disappearance of this signal - assert abs(np.floor(np.log10(filtered_data.mean())) - np.floor(np.log10(random_data.mean()) ) >= 1 + assert abs( np.floor(np.log10(filtered_data.mean())) - np.floor(np.log10(random_data.mean())) ) >= 1 From dbe9544fd0cc31664b0e680e421ff40071528bbb Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 29 Jun 2022 19:04:11 +0200 Subject: [PATCH 062/187] formatting --- tobac/feature_detection.py | 16 +++++++++------- tobac/tests/test_utils.py | 22 ++++++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/tobac/feature_detection.py b/tobac/feature_detection.py index ea2816db..2f5c36da 100644 --- a/tobac/feature_detection.py +++ b/tobac/feature_detection.py @@ -507,7 +507,7 @@ def feature_detection_multithreshold( distance_y = field_in.shape[2] * (dxy) distance = min(distance_x, distance_y) - # make sure the smaller value is taken as the minimum and the larger as the maximum + # make sure the smaller value is taken as the minimum and the larger as the maximum lambda_min = min(wavelength_filtering) lambda_max = max(wavelength_filtering) @@ -515,14 +515,16 @@ def feature_detection_multithreshold( raise ValueError( "The given wavelengths cannot be larger than the total distance in m along the axes of the domain." ) - elif lambda_min <= dxy: - raise ValueError("The given minimum wavelength cannot be smaller than gridspacing dxy. Please note - that both dxy and the values for wavelength_filtering should be given in meter.") - elif np.floor(np.log10(lambda_min)) - np.floor(np.log10(dxy)) > 1 : - warnings.warn("Warning: The values for dxy and the minimum wavelength are close in order of magnitude. Please - note that both dxy and for wavelength_filtering should be given in meter.") + elif lambda_min <= dxy: + raise ValueError( + "The given minimum wavelength cannot be smaller than gridspacing dxy. Please note that both dxy and the values for wavelength_filtering should be given in meter." + ) + elif np.floor(np.log10(lambda_min)) - np.floor(np.log10(dxy)) > 1: + warnings.warn( + "Warning: The values for dxy and the minimum wavelength are close in order of magnitude. Please note that both dxy and for wavelength_filtering should be given in meter." + ) for i_time, data_i in enumerate(data_time): time_i = data_i.coord("time").units.num2date(data_i.coord("time").points[0]) diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index 03144e06..a0fdb146 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -2,16 +2,17 @@ import tobac.utils as tb_utils from scipy import fft + def test_spectral_filtering(): """Testing tobac.utils.spectral_filtering with random test data that contains a wave signal.""" # set wavelengths for filtering and grid spacing dxy = 4000 - lambda_min = 400*1000 - lambda_max = 1000*1000 + lambda_min = 400 * 1000 + lambda_max = 1000 * 1000 - # get wavelengths for domain - matrix= np.zeros((200,100)) + # get wavelengths for domain + matrix = np.zeros((200, 100)) Ni = matrix.shape[-2] Nj = matrix.shape[-1] m, n = np.meshgrid(np.arange(Ni), np.arange(Nj), indexing="ij") @@ -21,7 +22,7 @@ def test_spectral_filtering(): # seed wave signal that lies within wavelength range for filtering signal_min = np.where(lambda_mn[0] < lambda_min)[0].min() signal_idx = np.random.randint(signal_min, matrix.shape[-1]) - matrix[0,signal_idx] = 1 + matrix[0, signal_idx] = 1 wave_data = fft.idctn(matrix) # use spectral filtering function on random wave data @@ -44,6 +45,11 @@ def test_spectral_filtering(): ) # because the randomly generated wave lies outside of range that is set for filtering, - # make sure that the filtering results in the disappearance of this signal - assert abs( np.floor(np.log10(filtered_data.mean())) - np.floor(np.log10(random_data.mean())) ) >= 1 - + # make sure that the filtering results in the disappearance of this signal + assert ( + abs( + np.floor(np.log10(filtered_data.mean())) + - np.floor(np.log10(random_data.mean())) + ) + >= 1 + ) From 334741827c7f2ad4304cdfcaf670241ae189cd5f Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 29 Jun 2022 19:44:16 +0200 Subject: [PATCH 063/187] fixed issues for testing --- tobac/tests/test_utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tobac/tests/test_utils.py b/tobac/tests/test_utils.py index a0fdb146..630985c8 100644 --- a/tobac/tests/test_utils.py +++ b/tobac/tests/test_utils.py @@ -17,7 +17,9 @@ def test_spectral_filtering(): Nj = matrix.shape[-1] m, n = np.meshgrid(np.arange(Ni), np.arange(Nj), indexing="ij") alpha = np.sqrt(m**2 / Ni**2 + n**2 / Nj**2) - lambda_mn = 2 * dxy / alpha + # turn off warning for zero divide here, because it is not avoidable with normalized wavenumbers + with np.errstate(divide="ignore", invalid="ignore"): + lambda_mn = 2 * dxy / alpha # seed wave signal that lies within wavelength range for filtering signal_min = np.where(lambda_mn[0] < lambda_min)[0].min() @@ -41,15 +43,15 @@ def test_spectral_filtering(): # check that filtered/ smoothed field exhibits smaller range of values assert (filtered_data.max() - filtered_data.min()) < ( - random_data.max() - random_data.min() + wave_data.max() - wave_data.min() ) # because the randomly generated wave lies outside of range that is set for filtering, # make sure that the filtering results in the disappearance of this signal assert ( abs( - np.floor(np.log10(filtered_data.mean())) - - np.floor(np.log10(random_data.mean())) + np.floor(np.log10(abs(filtered_data.mean()))) + - np.floor(np.log10(abs(wave_data.mean()))) ) >= 1 ) From 2f0bead7829f9de84a9e619c3f8894be1ee4ae00 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 29 Jun 2022 20:02:48 +0200 Subject: [PATCH 064/187] fixed wavelength unit also in test_feature_detection.py --- tobac/tests/test_feature_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tests/test_feature_detection.py b/tobac/tests/test_feature_detection.py index f3731dbb..9933fd12 100644 --- a/tobac/tests/test_feature_detection.py +++ b/tobac/tests/test_feature_detection.py @@ -5,7 +5,7 @@ @pytest.mark.parametrize( "test_threshs, dxy, wavelength_filtering", - [([1.5], -1, None), ([1.5], 10000, (100, 500))], + [([1.5], -1, None), ([1.5], 10000, (100*1000, 500*1000))], ) def test_feature_detection_multithreshold_timestep( test_threshs, dxy, wavelength_filtering From f8ba18c62ba4ef4ddbf7dd15c7d138eb14f8c812 Mon Sep 17 00:00:00 2001 From: Juli Date: Wed, 29 Jun 2022 20:04:09 +0200 Subject: [PATCH 065/187] black formatting --- tobac/tests/test_feature_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tobac/tests/test_feature_detection.py b/tobac/tests/test_feature_detection.py index 9933fd12..956173bd 100644 --- a/tobac/tests/test_feature_detection.py +++ b/tobac/tests/test_feature_detection.py @@ -5,7 +5,7 @@ @pytest.mark.parametrize( "test_threshs, dxy, wavelength_filtering", - [([1.5], -1, None), ([1.5], 10000, (100*1000, 500*1000))], + [([1.5], -1, None), ([1.5], 10000, (100 * 1000, 500 * 1000))], ) def test_feature_detection_multithreshold_timestep( test_threshs, dxy, wavelength_filtering From bbf889e9beee5d630eaf5e677e6c373b073fff26 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Wed, 29 Jun 2022 20:39:57 -0600 Subject: [PATCH 066/187] Added tracking output columns --- doc/feature_detection_output.rst | 2 +- doc/tracking_base_out_vars.csv | 3 +++ doc/tracking_output.rst | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 doc/tracking_base_out_vars.csv create mode 100644 doc/tracking_output.rst diff --git a/doc/feature_detection_output.rst b/doc/feature_detection_output.rst index 336632d9..e03b605a 100644 --- a/doc/feature_detection_output.rst +++ b/doc/feature_detection_output.rst @@ -2,7 +2,7 @@ Feature detection output ------------------------- -Feature detection outputs a `pandas` dataframe with several variables. The variables, (with column names listed in the `Variable Name` column), are described below, with units. Note that while these variables come initially from the feature detection step, segmentation and tracking also share some of these variables. +Feature detection outputs a `pandas` dataframe with several variables. The variables, (with column names listed in the `Variable Name` column), are described below, with units. Note that while these variables come initially from the feature detection step, segmentation and tracking also share some of these variables. See `Tracking Output`_ for the additional columns added by tracking. Variables that are common to all feature detection files: diff --git a/doc/tracking_base_out_vars.csv b/doc/tracking_base_out_vars.csv new file mode 100644 index 00000000..cdb496e5 --- /dev/null +++ b/doc/tracking_base_out_vars.csv @@ -0,0 +1,3 @@ +Variable Name,Description,Units,Type +cell,Tracked cell number; generally starts from 1. Untracked cell value can be set; but by default is -1.,n/a,int +time_cell,Time since cell was first detected.,typically minutes,object/python timedelta diff --git a/doc/tracking_output.rst b/doc/tracking_output.rst new file mode 100644 index 00000000..fff121f3 --- /dev/null +++ b/doc/tracking_output.rst @@ -0,0 +1,12 @@ +.. _Tracking Output: +Tracking output +------------------------- + +Tracking outputs a `pandas` dataframe with additional variables in addition to the variables output by Feature Detection (see `Feature Detection Output`_). The additional variables added by tracking, with column names listed in the `Variable Name` column, are described below with units. + +Variables that are common to all feature detection files: + +.. csv-table:: tobac Tracking Output Variables + :file: ./tracking_base_out_vars.csv + :widths: 3, 35, 3, 3 + :header-rows: 1 From 0db6d3b2b80a17634e35f1d21ee283ba1dca495d Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Wed, 29 Jun 2022 20:46:46 -0600 Subject: [PATCH 067/187] Switched main doc page to index Following the suggestion here: https://stackoverflow.com/a/56448499/629110 , I switched the new `conf.py` file to switch the master_doc to `index` --- doc/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 6d5ae7ee..2e8878ab 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -12,6 +12,8 @@ project = u'tobac' +master_doc = 'index' + def setup(app): app.add_css_file("theme_overrides.css") From 286872b758260d1db0da1ee9a13c1cff7704f616 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Wed, 29 Jun 2022 20:48:36 -0600 Subject: [PATCH 068/187] Added tracking_output to Output Documentation subheader --- doc/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/index.rst b/doc/index.rst index c7027654..7fb8ca3b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -32,6 +32,8 @@ The project is currently being extended by several contributors to include addit :maxdepth: 2 feature_detection_output + tracking_output + .. toctree:: :caption: API Reference From fd1005e604edb03ce00a176b0d85e6367b30dd7b Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sat, 2 Jul 2022 16:49:36 -0600 Subject: [PATCH 069/187] including documentation on conf.py --- doc/conf.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 2e8878ab..019d6302 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,8 +1,13 @@ +'''This file is used to configure the Sphinx build of our documentation. +The documentation on setting this up is here: https://www.sphinx-doc.org/en/master/usage/configuration.html +''' + import sphinx_rtd_theme import sys, os sys.path.insert(0, os.path.abspath('extensions')) +# What Sphinx extensions do we need extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.ifconfig', 'sphinx_rtd_theme','sphinx.ext.napoleon'] @@ -14,16 +19,20 @@ master_doc = 'index' - +# Include our custom CSS (currently for special table config) def setup(app): app.add_css_file("theme_overrides.css") +# This should include all modules used in tobac. These are dummy imports, +# but should include both required and optional dependencies. autodoc_mock_imports = ['numpy', 'scipy', 'scikit-image', 'pandas', 'pytables', 'matplotlib', 'iris', 'cf-units', 'xarray', 'cartopy', 'trackpy', 'numba'] sys.path.insert(0, os.path.abspath("../")) -# Napoleon settings +# Napoleon settings for configuring the Napoleon extension +# See documentation here: +# https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html napoleon_google_docstring = True napoleon_numpy_docstring = True napoleon_include_init_with_doc = False From d1f23b48effa041fb7e8636417b6745354f5f7b0 Mon Sep 17 00:00:00 2001 From: kelcyno <88055123+kelcyno@users.noreply.github.com> Date: Sat, 2 Jul 2022 21:36:49 -0600 Subject: [PATCH 070/187] Update merge_split.py Updated changes to merge/split based on suggestions for lat/lon convection, and documentation. --- tobac/merge_split.py | 467 +++++++++++++++++++++++-------------------- 1 file changed, 249 insertions(+), 218 deletions(-) diff --git a/tobac/merge_split.py b/tobac/merge_split.py index 2f4915a7..60e929be 100644 --- a/tobac/merge_split.py +++ b/tobac/merge_split.py @@ -1,4 +1,13 @@ -# Tobac merge and split v0.1 +''' + Tobac merge and split v0.1 + This submodule is a post processing step to address tracked cells which merge/split. + The first iteration of this module is to combine the cells which are merging but receive + a new cell id once merged. In general this submodule will label these three cell-ids using + the largest cell number of those within the merged/split cell ids. + + +''' + from geopy.distance import geodesic from networkx import * @@ -6,8 +15,30 @@ from pandas.core.common import flatten import xarray as xr - def compress_all(nc_grids, min_dims=2): + ''' + The purpose of this subroutine is to compress the netcdf variables as they are saved + this does not change the data, but sets netcdf encoding parameters. + We allocate a minimum number of dimensions as variables with dimensions + under the minimum value do not benefit from tangibly from this encoding. + + Parameters + ---------- + nc_grids : xarray.core.dataset.Dataset + Xarray dataset that is intended to be exported as netcdf + + min_dims : integer + The minimum number of dimesnions, in integer value, a variable must have in order + set the netcdf compression encoding. + + Returns + ------- + nc_grids : xarray.core.dataset.Dataset + Xarray dataset with netcdf compression encoding for variables with two (2) or more dimensions + + ''' + + for var in nc_grids: if len(nc_grids[var].dims) >= min_dims: # print("Compressing ", var) @@ -15,11 +46,11 @@ def compress_all(nc_grids, min_dims=2): nc_grids[var].encoding["complevel"] = 4 nc_grids[var].encoding["contiguous"] = False return nc_grids + - -def standardize_track_dataset(TrackedFeatures, Mask, Projection): - """Combine a feature mask with the feature data table into a common dataset. - Also renames th +def standardize_track_dataset(TrackedFeatures, Mask, Projection = None): + ''' + Combine a feature mask with the feature data table into a common dataset. returned by tobac.themes.tobac_v1.segmentation with the TrackedFeatures dataset returned by tobac.themes.tobac_v1.linking_trackpy. @@ -32,140 +63,143 @@ def standardize_track_dataset(TrackedFeatures, Mask, Projection): Projection is an xarray DataArray - TODO: Add metadata attributes to - - """ + TODO: Add metadata attributes + + Parameters + ---------- + TrackedFeatures : xarray.core.dataset.Dataset + xarray dataset of tobac Track information, the xarray dataset returned by tobac.tracking.linking_trackpy + + Mask: xarray.core.dataset.Dataset + xarray dataset of tobac segmentation mask information, the xarray dataset returned + by tobac.segmentation.segmentation + + + Projection : xarray.core.dataarray.DataArray, default = None + array.DataArray of the original input dataset (gridded nexrad data for example). + If using gridded nexrad data, this can be input as: data['ProjectionCoordinateSystem'] + An example of the type of information in the dataarray includes the following attributes: + latitude_of_projection_origin :29.471900939941406 + longitude_of_projection_origin :-95.0787353515625 + _CoordinateTransformType :Projection + _CoordinateAxes :x y z time + _CoordinateAxesTypes :GeoX GeoY Height Time + grid_mapping_name :azimuthal_equidistant + semi_major_axis :6370997.0 + inverse_flattening :298.25 + longitude_of_prime_meridian :0.0 + false_easting :0.0 + false_northing :0.0 + + Returns + ------- + + ds : xarray.core.dataset.Dataset + xarray dataset of merged Track and Segmentation Mask datasets with renamed variables. + + ''' feature_standard_names = { # new variable name, and long description for the NetCDF attribute - "frame": ( - "feature_time_index", - "positional index of the feature along the time dimension of the mask, from 0 to N-1", - ), - "hdim_1": ( - "feature_hdim1_coordinate", - "position of the feature along the first horizontal dimension in grid point space; a north-south coordinate for dim order (time, y, x)." - "The numbering is consistent with positional indexing of the coordinate, but can be" - "fractional, to account for a centroid not aligned to the grid.", - ), - "hdim_2": ( - "feature_hdim2_coordinate", - "position of the feature along the second horizontal dimension in grid point space; an east-west coordinate for dim order (time, y, x)" - "The numbering is consistent with positional indexing of the coordinate, but can be" - "fractional, to account for a centroid not aligned to the grid.", - ), - "idx": ("feature_id_this_frame",), - "num": ( - "feature_grid_cell_count", - "Number of grid points that are within the threshold of this feature", - ), - "threshold_value": ( - "feature_threshold_max", - "Feature number within that frame; starts at 1, increments by 1 to the number of features for each frame, and resets to 1 when the frame increments", - ), - "feature": ( - "feature_id", - "Unique number of the feature; starts from 1 and increments by 1 to the number of features", - ), - "time": ( - "feature_time", - "time of the feature, consistent with feature_time_index", - ), - "timestr": ( - "feature_time_str", - "String representation of the feature time, YYYY-MM-DD HH:MM:SS", - ), - "projection_y_coordinate": ( - "feature_projection_y_coordinate", - "y position of the feature in the projection given by ProjectionCoordinateSystem", - ), - "projection_x_coordinate": ( - "feature_projection_x_coordinate", - "x position of the feature in the projection given by ProjectionCoordinateSystem", - ), - "lat": ("feature_latitude", "latitude of the feature"), - "lon": ("feature_longitude", "longitude of the feature"), - "ncells": ( - "feature_ncells", - "number of grid cells for this feature (meaning uncertain)", - ), - "areas": ("feature_area",), - "isolated": ("feature_isolation_flag",), - "num_objects": ("number_of_feature_neighbors",), - "cell": ("feature_parent_cell_id",), - "time_cell": ("feature_parent_cell_elapsed_time",), - "segmentation_mask": ("2d segmentation mask",), - } - new_feature_var_names = { - k: feature_standard_names[k][0] - for k in feature_standard_names.keys() - if k in TrackedFeatures.variables.keys() + 'frame':('feature_time_index', + 'positional index of the feature along the time dimension of the mask, from 0 to N-1'), + 'hdim_1':('feature_hdim1_coordinate', + 'position of the feature along the first horizontal dimension in grid point space; a north-south coordinate for dim order (time, y, x).' + 'The numbering is consistent with positional indexing of the coordinate, but can be' + 'fractional, to account for a centroid not aligned to the grid.'), + 'hdim_2':('feature_hdim2_coordinate', + 'position of the feature along the second horizontal dimension in grid point space; an east-west coordinate for dim order (time, y, x)' + 'The numbering is consistent with positional indexing of the coordinate, but can be' + 'fractional, to account for a centroid not aligned to the grid.'), + 'idx':('feature_id_this_frame',), + 'num':('feature_grid_cell_count', + 'Number of grid points that are within the threshold of this feature'), + 'threshold_value':('feature_threshold_max', + "Feature number within that frame; starts at 1, increments by 1 to the number of features for each frame, and resets to 1 when the frame increments"), + 'feature':('feature_id', + "Unique number of the feature; starts from 1 and increments by 1 to the number of features"), + 'time':('feature_time','time of the feature, consistent with feature_time_index'), + 'timestr':('feature_time_str','String representation of the feature time, YYYY-MM-DD HH:MM:SS'), + 'projection_y_coordinate':('feature_projection_y_coordinate','y position of the feature in the projection given by ProjectionCoordinateSystem'), + 'projection_x_coordinate':('feature_projection_x_coordinate','x position of the feature in the projection given by ProjectionCoordinateSystem'), + 'lat':('feature_latitude','latitude of the feature'), + 'lon':('feature_longitude','longitude of the feature'), + 'ncells':('feature_ncells','number of grid cells for this feature (meaning uncertain)'), + 'areas':('feature_area',), + 'isolated':('feature_isolation_flag',), + 'num_objects':('number_of_feature_neighbors',), + 'cell':('feature_parent_cell_id',), + 'time_cell':('feature_parent_cell_elapsed_time',), + 'segmentation_mask':('2d segmentation mask',) } + new_feature_var_names = {k:feature_standard_names[k][0] for k in feature_standard_names.keys() + if k in TrackedFeatures.variables.keys()} - TrackedFeatures = TrackedFeatures.drop(["cell_parent_track_id"]) + TrackedFeatures = TrackedFeatures.drop(['cell_parent_track_id']) # Combine Track and Mask variables. Use the 'feature' variable as the coordinate variable instead of # the 'index' variable and call the dimension 'feature' - ds = xr.merge( - [ - TrackedFeatures.swap_dims({"index": "feature"}) - .drop("index") - .rename_vars(new_feature_var_names), - Mask, - ] - ) + ds = xr.merge([TrackedFeatures.swap_dims({'index':'feature'}).drop('index').rename_vars(new_feature_var_names), + Mask]) # Add the projection data back in - ds["ProjectionCoordinateSystem"] = Projection + if not None in Projection: + ds['ProjectionCoordinateSystem']=Projection # Convert the cell ID variable from float to integer - if "int" not in str(TrackedFeatures.cell.dtype): + if 'int' not in str(TrackedFeatures.cell.dtype): # The raw output from the tracking is actually an object array # array([nan, 2, 3], dtype=object) # (and is cast to a float array when saved as NetCDF, I think). # Cast to float. - int_cell = xr.zeros_like(TrackedFeatures.cell, dtype="int64") + int_cell = xr.zeros_like(TrackedFeatures.cell, dtype='int64') - cell_id_data = TrackedFeatures.cell.astype("float64") + cell_id_data = TrackedFeatures.cell.astype('float64') valid_cell = np.isfinite(cell_id_data) valid_cell_ids = cell_id_data[valid_cell] if not (np.unique(valid_cell_ids) > 0).all(): - raise AssertionError( - "Lowest cell ID cell is less than one, conflicting with use of zero to indicate an untracked cell" - ) - int_cell[valid_cell] = valid_cell_ids.astype("int64") - # ds['feature_parent_cell_id'] = int_cell + raise AssertionError('Lowest cell ID cell is less than one, conflicting with use of zero to indicate an untracked cell') + int_cell[valid_cell] = valid_cell_ids.astype('int64') + #ds['feature_parent_cell_id'] = int_cell return ds -def merge_split(TRACK, distance=25000, frame_len=5): - """ - function to postprocess tobac track data for merge/split cells - Input: - TRACK: xarray dataset of tobac Track information - distance: float, optional distance threshold prior to adding a pair of points - into the minimum spanning tree. Default is 25000 meters. - - frame_len: float, optional threshold for the spanning length within which two points - can be separated. Default is five (5) frames. - - - Output: - d: xarray dataset of - feature position along 1st horizontal dimension - hdim2_index: float - feature position along 2nd horizontal dimension - - Example: +def merge_split(TRACK,distance = 25000,frame_len = 5): + ''' + function to postprocess tobac track data for merge/split cells + + + Parameters + ---------- + TRACK : xarray.core.dataset.Dataset + xarray dataset of tobac Track information + + distance : float, optional + Distance threshold prior to adding a pair of points into the minimum spanning tree. + Default is 25000 meters. + + frame_len : float, optional + Threshold for the spanning length within which two points can be separated. + Default is five (5) frames. + + Returns + ------- + + d : xarray.core.dataset.Dataset + xarray dataset of tobac merge/split cells with parent and child designations. + + + Example usage: d = merge_split(Track) - ds = standardize_track_dataset(Track, refl_mask, data['ProjectionCoordinateSystem']) - # both_ds = xarray.combine_by_coords((ds,d), compat='override') + ds = standardize_track_dataset(Track, refl_mask) both_ds = xr.merge([ds, d],compat ='override') both_ds = compress_all(both_ds) both_ds.to_netcdf(os.path.join(savedir,'Track_features_merges.nc')) - - """ - track_groups = TRACK.groupby("cell") - cell_ids = {cid: len(v) for cid, v in track_groups.groups.items()} + + ''' + + print('this is an update 3') + track_groups = TRACK.groupby('cell') + cell_ids = {cid:len(v) for cid, v in track_groups.groups.items()} id_data = np.fromiter(cell_ids.keys(), dtype=int) count_data = np.fromiter(cell_ids.values(), dtype=int) all_frames = np.sort(np.unique(TRACK.frame)) @@ -174,40 +208,42 @@ def merge_split(TRACK, distance=25000, frame_len=5): a_names = list() b_names = list() dist = list() + + if hasattr(TRACK, 'grid_longitude'): + print('is in lat/lonx') + for i in id_data: + a_pointx = track_groups[i].grid_longitude[-1].values + a_pointy = track_groups[i].grid_latitude[-1].values + for j in id_data: + b_pointx = track_groups[j].grid_longitude[0].values + b_pointy = track_groups[j].grid_latitude[0].values + d = geodesic((a_pointy,a_pointx),(b_pointy,b_pointx)).m + if d <= distance: + a_points.append([a_pointx,a_pointy]) + b_points.append([b_pointx, b_pointy]) + dist.append(d) + a_names.append(i) + b_names.append(j) + else: + + for i in id_data: + #print(i) + a_pointx = track_groups[i].projection_x_coordinate[-1].values + a_pointy = track_groups[i].projection_y_coordinate[-1].values + for j in id_data: + b_pointx = track_groups[j].projection_x_coordinate[0].values + b_pointy = track_groups[j].projection_y_coordinate[0].values + d = np.linalg.norm(np.array((a_pointx, a_pointy)) - np.array((b_pointx, b_pointy))) + if d <= distance: + a_points.append([a_pointx,a_pointy]) + b_points.append([b_pointx, b_pointy]) + dist.append(d) + a_names.append(i) + b_names.append(j) - for i in id_data: - # print(i) - a_pointx = track_groups[i].projection_x_coordinate[-1].values - a_pointy = track_groups[i].projection_y_coordinate[-1].values - for j in id_data: - b_pointx = track_groups[j].projection_x_coordinate[0].values - b_pointy = track_groups[j].projection_y_coordinate[0].values - d = np.linalg.norm( - np.array((a_pointx, a_pointy)) - np.array((b_pointx, b_pointy)) - ) - if d <= distance: - a_points.append([a_pointx, a_pointy]) - b_points.append([b_pointx, b_pointy]) - dist.append(d) - a_names.append(i) - b_names.append(j) - - # for i in id_data: - # a_pointx = track_groups[i].grid_longitude[-1].values - # a_pointy = track_groups[i].grid_latitude[-1].values - # for j in id_data: - # b_pointx = track_groups[j].grid_longitude[0].values - # b_pointy = track_groups[j].grid_latitude[0].values - # d = geodesic((a_pointy,a_pointx),(b_pointy,b_pointx)).km - # if d <= distance: - # a_points.append([a_pointx,a_pointy]) - # b_points.append([b_pointx, b_pointy]) - # dist.append(d) - # a_names.append(i) - # b_names.append(j) id = [] - for i in range(len(dist) - 1, -1, -1): + for i in range(len(dist)-1, -1, -1): if a_names[i] == b_names[i]: id.append(i) a_points.pop(i) @@ -217,27 +253,25 @@ def merge_split(TRACK, distance=25000, frame_len=5): b_names.pop(i) else: continue - + g = Graph() for i in np.arange(len(dist)): - g.add_edge(a_names[i], b_names[i], weight=dist[i]) + g.add_edge(a_names[i], b_names[i],weight=dist[i]) tree = minimum_spanning_edges(g) tree_list = list(minimum_spanning_edges(g)) new_tree = [] - for i, j in enumerate(tree_list): + for i,j in enumerate(tree_list): frame_a = np.nanmax(track_groups[j[0]].frame.values) frame_b = np.nanmin(track_groups[j[1]].frame.values) if np.abs(frame_a - frame_b) <= frame_len: new_tree.append(tree_list[i][0:2]) new_tree_arr = np.array(new_tree) - TRACK["cell_parent_track_id"] = np.zeros(len(TRACK["cell"].values)) - cell_id = np.unique( - TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))] - ) - track_id = dict() # same size as number of total merged tracks + TRACK['cell_parent_track_id'] = np.zeros(len(TRACK['cell'].values)) + cell_id = np.unique(TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))]) + track_id = dict() #same size as number of total merged tracks arr = np.array([0]) for p in cell_id: @@ -248,7 +282,7 @@ def merge_split(TRACK, distance=25000, frame_len=5): k = np.where(new_tree_arr == p) if len(k[0]) == 0: track_id[p] = [p] - arr = np.append(arr, p) + arr = np.append(arr,p) else: temp1 = list(np.unique(new_tree_arr[k[0]])) temp = list(np.unique(new_tree_arr[k[0]])) @@ -263,113 +297,112 @@ def merge_split(TRACK, distance=25000, frame_len=5): if len(temp1) == len(temp): break temp1 = np.array(temp) - + for i in temp1: k2 = np.where(new_tree_arr == i) temp.append(list(np.unique(new_tree_arr[k2[0]]).squeeze())) temp = list(flatten(temp)) temp = list(np.unique(temp)) - arr = np.append(arr, np.unique(temp)) - + arr = np.append(arr,np.unique(temp)) + track_id[np.nanmax(np.unique(temp))] = list(np.unique(temp)) + + + + storm_id = [0] #default because we don't track larger storm systems *yet* + print('found storm id') - storm_id = [0] # default because we don't track larger storm systems *yet* - print("found storm id") - track_parent_storm_id = np.repeat( - 0, len(track_id) - ) # This will always be zero when we don't track larger storm systems *yet* - print("found track parent storm ids") + track_parent_storm_id = np.repeat(0, len(track_id)) #This will always be zero when we don't track larger storm systems *yet* + print('found track parent storm ids') track_ids = np.array(list(track_id.keys())) - print("found track ids") + print('found track ids') + - cell_id = list( - np.unique( - TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))] - ) - ) - print("found cell ids") + cell_id = list(np.unique(TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))])) + print('found cell ids') cell_parent_track_id = [] for i, id in enumerate(track_id): - - if len(track_id[int(id)]) == 1: + + if len(track_id[int(id)]) == 1: cell_parent_track_id.append(int(id)) else: - cell_parent_track_id.append(np.repeat(int(id), len(track_id[int(id)]))) + cell_parent_track_id.append(np.repeat(int(id),len(track_id[int(id)]))) + cell_parent_track_id = list(flatten(cell_parent_track_id)) - print("found cell parent track ids") + print('found cell parent track ids') feature_parent_cell_id = list(TRACK.cell.values.astype(float)) - print("found feature parent cell ids") + print('found feature parent cell ids') - # This version includes all the feature regardless of if they are used in cells or not. + #This version includes all the feature regardless of if they are used in cells or not. feature_id = list(TRACK.feature.values.astype(int)) - print("found feature ids") + print('found feature ids') - feature_parent_storm_id = np.repeat(0, len(feature_id)) # we don't do storms atm - print("found feature parent storm ids") + feature_parent_storm_id = np.repeat(0,len(feature_id)) #we don't do storms atm + print('found feature parent storm ids') - feature_parent_track_id = [] + feature_parent_track_id = [] feature_parent_track_id = np.zeros(len(feature_id)) for i, id in enumerate(feature_id): cellid = feature_parent_cell_id[i] if np.isnan(cellid): - feature_parent_track_id[i] = -1 + feature_parent_track_id[i] = -1 else: j = np.where(cell_id == cellid) j = np.squeeze(j) trackid = cell_parent_track_id[j] feature_parent_track_id[i] = trackid + + print('found feature parent track ids') - print("found feature parent track ids") storm_child_track_count = [len(track_id)] - print("found storm child track count") + print('found storm child track count') track_child_cell_count = [] - for i, id in enumerate(track_id): + for i,id in enumerate(track_id): track_child_cell_count.append(len(track_id[int(id)])) - print("found track child cell count") + print('found track child cell count') + cell_child_feature_count = [] - for i, id in enumerate(cell_id): + for i,id in enumerate(cell_id): cell_child_feature_count.append(len(track_groups[id].feature.values)) - print("found cell child feature count") + print('found cell child feature count') - storm_child_cell_count = [len(cell_id)] + storm_child_cell_count = [len(cell_id)] storm_child_feature_count = [len(feature_id)] - - storm_dim = "nstorms" - track_dim = "ntracks" - cell_dim = "ncells" - feature_dim = "nfeatures" - - d = xr.Dataset( - { - "storm_id": (storm_dim, storm_id), - "track_id": (track_dim, track_ids), - "track_parent_storm_id": (track_dim, track_parent_storm_id), - "cell_id": (cell_dim, cell_id), - "cell_parent_track_id": (cell_dim, cell_parent_track_id), - "feature_id": (feature_dim, feature_id), - "feature_parent_cell_id": (feature_dim, feature_parent_cell_id), - "feature_parent_track_id": (feature_dim, feature_parent_track_id), - "feature_parent_storm_id": (feature_dim, feature_parent_storm_id), - "storm_child_track_count": (storm_dim, storm_child_track_count), - "storm_child_cell_count": (storm_dim, storm_child_cell_count), - "storm_child_feature_count": (storm_dim, storm_child_feature_count), - "track_child_cell_count": (track_dim, track_child_cell_count), - "cell_child_feature_count": (cell_dim, cell_child_feature_count), - } - ) - d = d.set_coords(["feature_id", "cell_id", "track_id", "storm_id"]) + + storm_dim = 'nstorms' + track_dim = 'ntracks' + cell_dim = 'ncells' + feature_dim = 'nfeatures' + + d = xr.Dataset({ + 'storm_id': (storm_dim, storm_id), + 'track_id': (track_dim, track_ids), + 'track_parent_storm_id': (track_dim, track_parent_storm_id), + 'cell_id': (cell_dim, cell_id), + 'cell_parent_track_id': (cell_dim, cell_parent_track_id), + 'feature_id': (feature_dim, feature_id), + 'feature_parent_cell_id': (feature_dim, feature_parent_cell_id), + 'feature_parent_track_id': (feature_dim, feature_parent_track_id), + 'feature_parent_storm_id': (feature_dim, feature_parent_storm_id), + 'storm_child_track_count': (storm_dim, storm_child_track_count), + 'storm_child_cell_count': (storm_dim, storm_child_cell_count), + 'storm_child_feature_count': (storm_dim, storm_child_feature_count), + 'track_child_cell_count': (track_dim, track_child_cell_count), + 'cell_child_feature_count': (cell_dim, cell_child_feature_count), + }) + d = d.set_coords(['feature_id','cell_id', 'track_id', 'storm_id']) assert len(track_id) == len(track_parent_storm_id) assert len(cell_id) == len(cell_parent_track_id) @@ -378,8 +411,6 @@ def merge_split(TRACK, distance=25000, frame_len=5): assert sum(storm_child_cell_count) == len(cell_id) assert sum(storm_child_feature_count) == len(feature_id) assert sum(track_child_cell_count) == len(cell_id) - assert sum( - [sum(cell_child_feature_count), (len(np.where(feature_parent_track_id < 0)[0]))] - ) == len(feature_id) - - return d + assert sum([sum(cell_child_feature_count),(len(np.where(feature_parent_track_id < 0)[0]))]) == len(feature_id) + + return d \ No newline at end of file From 17b7883007e4d78d966a5874b77d4cc3c3d22619 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sat, 2 Jul 2022 21:47:03 -0600 Subject: [PATCH 071/187] Cleaning up some of the documentation pages --- doc/analysis.rst | 3 +++ doc/data_input.rst | 15 ++++++++------- doc/feature_detection.rst | 5 +++-- doc/index.rst | 15 ++++++++++++--- doc/installation.rst | 8 ++++---- doc/linking.rst | 4 ---- doc/segmentation.rst | 2 -- doc/tracking_output.rst | 4 ++-- 8 files changed, 32 insertions(+), 24 deletions(-) diff --git a/doc/analysis.rst b/doc/analysis.rst index eb6b934b..3ff9a516 100644 --- a/doc/analysis.rst +++ b/doc/analysis.rst @@ -1,3 +1,6 @@ +.. + Documentation of analysis functions + TODO: include descriptions of the analysis functions and examples Analysis ========= tobac provides several analysis functions that allow for the calculation of important quantities based on the tracking results. This includes the calculation of properties such as cloud lifetimes and cloud areas/volumes, but also allows for a convenient calculation of statistics for arbitrary fields of the same shape as as the input data used for the tracking analysis. diff --git a/doc/data_input.rst b/doc/data_input.rst index d0746ff5..33c19a13 100644 --- a/doc/data_input.rst +++ b/doc/data_input.rst @@ -1,18 +1,19 @@ +.. + Description of the input data required. .. _Data Input: Data input ========== -Input data for tobac should consist of one or more fields on a common, regular grid with a time dimension and two or more spatial dimensions. The input data should also include latitude and longitude coordinates, either as 1-d or 2-d variables depending on the grid used. +Input data for tobac should consist of one or more fields on a common, regular grid with a time dimension and two or more spatial dimensions. The input data can also include latitude and longitude coordinates, either as 1-d or 2-d variables depending on the grid used. Interoperability with xarray is provided by the convenient functions allowing for a transformation between the two data types. -xarray DataArays can be easily converted into iris cubes using xarray's `to__iris() `_ method, while the Iris cubes produced as output of tobac can be turned into xarray DataArrays using the `from__iris() `_ method. +xarray DataArays can be easily converted into iris cubes using xarray's `to_iris() `_ method, while the Iris cubes produced as output of tobac can be turned into xarray DataArrays using the `from_iris() `_ method. -For the future development of the next major version of tobac, we are envisaging moving the basic data structures from Iris cubes to xarray DataArrays for improved computing performance and interoperability with other open-source sorftware packages. +For the future development of the next major version of tobac (v2.0), we are moving the basic data structures from Iris cubes to xarray DataArrays for improved computing performance and interoperability with other open-source sorftware packages, including the Pangeo project. -The output of the different analysis steps in tobac are output as either pandas DataFrames in the case of one-dimensional data, such a lists of identified features or cloud trajectories or as Iris cubes in the case of 2D/3D/4D fields such as cloud masks. Note that the dataframe output from tracking is a superset of the features dataframe. - -(quick note on terms; “feature” is a detected object at a single time step. “cell” is a series of features linked together over multiple timesteps) +The output of the different analysis steps in tobac are output as either pandas DataFrames in the case of one-dimensional data, such a lists of identified features or feature tracks or as Iris cubes in the case of 2D/3D/4D fields such as cloud masks. Note that the dataframe output from tracking is a superset of the features dataframe. For information on feature detection *output*, see `Feature Detection Output`_. +For information on tracking *output*, see `Tracking Output`_. -Note that in future versions of tobac, it is planned to combine both output data types into a single hierarchical data structure containing both spatial and object information. Additional information about the planned changes can be found in the v2.0-dev project, as well as the tobac roadmap +Note that in future versions of tobac, it is planned to combine both output data types into a single hierarchical data structure containing both spatial and object information. Additional information about the planned changes can be found in the v2.0-dev branch of the main tobac repository, as well as the tobac roadmap. diff --git a/doc/feature_detection.rst b/doc/feature_detection.rst index 8f81d726..71ab9545 100644 --- a/doc/feature_detection.rst +++ b/doc/feature_detection.rst @@ -1,7 +1,8 @@ -Feature detection +.. _Feature Detection: +Feature Detection Basics --------------------- -The feature detection form the first step of the analysis. +The feature detection is the first step in using **tobac**. **Currently implemented methods:** diff --git a/doc/index.rst b/doc/index.rst index 7fb8ca3b..82cd2787 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,11 +1,14 @@ +.. + Homepage +.. _tobac Home: tobac - Tracking and Object-Based Analysis of Clouds ------------------------------------------------------- **tobac** is a Python package to identify, track and analyze clouds in different types of gridded datasets, such as 3D model output from cloud-resolving model simulations or 2D data from satellite retrievals. -The software is set up in a modular way to include different algorithms for feature identification, tracking, and analyses. **tobac** is also input variable agnostic and doesn't rely on specific input variables to work. +The software is set up in a modular way to include different algorithms for feature identification, tracking, and analyses. **tobac** is also input variable agnostic and doesn't rely on specific input variables, nor a specific grid to work. -In the current implementation, individual features are identified as either maxima or minima in a two-dimensional time-varying field. An associated volume can then be determined using these features with a separate (or identical) time-varying 2D or 3D field and a threshold value. The identified objects are linked into consistent trajectories representing the cloud over its lifecycle in the tracking step. Analysis and visualization methods provide a convenient way to use and display the tracking results. +In the current implementation, individual features are identified as either maxima or minima in a two-dimensional time-varying field (see `Feature Detection`_). An associated volume can then be determined using these features with a separate (or identical) time-varying 2D or 3D field and a threshold value (see `Segmentation`_). The identified objects are linked into consistent trajectories representing the cloud over its lifecycle in the tracking step. Analysis and visualization methods provide a convenient way to use and display the tracking results. Version 1.2 of tobac and some example applications are described in a manuscript in Geoscientific Model Development as: @@ -16,7 +19,6 @@ The project is currently being extended by several contributors to include addit .. toctree:: :caption: Basic Information :maxdepth: 2 - :numbered: installation data_input @@ -27,6 +29,13 @@ The project is currently being extended by several contributors to include addit plotting examples +.. toctree:: + :caption: Feature Detection + :maxdepth: 2 + + feature_detection + feature_detection_output + .. toctree:: :caption: Output Documentation :maxdepth: 2 diff --git a/doc/installation.rst b/doc/installation.rst index 7bb07da9..7cacb335 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -35,15 +35,15 @@ If you are using anaconda, the following command should make sure all dependenci You can directly install the package directly from github with pip and either of the two following commands: - ``pip install --upgrade git+ssh://git@github.com/climate-processes/tobac.git`` + ``pip install --upgrade git+ssh://git@github.com/tobac-project/tobac.git`` - ``pip install --upgrade git+https://github.com/climate-processes/tobac.git`` + ``pip install --upgrade git+https://github.com/tobac-project/tobac.git`` You can also clone the package with any of the two following commands: - ``git clone git@github.com:climate-processes/tobac.git`` + ``git clone git@github.com:tobac-project/tobac.git`` - ``git clone https://github.com/climate-processes/tobac.git`` + ``git clone https://github.com/tobac-project/tobac.git`` and install the package from the locally cloned version (The trailing slash is actually necessary): diff --git a/doc/linking.rst b/doc/linking.rst index dd493893..6c3e840e 100644 --- a/doc/linking.rst +++ b/doc/linking.rst @@ -10,7 +10,3 @@ This approach only takes the point-like position of the feature, e.g. determined .. image:: images/linking_prediction.png :width: 500 px -**Current development:** - -We are currently actively working on additional options for the tracking of the clouds that take into account the shape of the identified features by evaluating overlap between adjacent time steps, as well as the inclusion of cloud splitting and merging. - diff --git a/doc/segmentation.rst b/doc/segmentation.rst index 527305a2..4260cc69 100644 --- a/doc/segmentation.rst +++ b/doc/segmentation.rst @@ -10,5 +10,3 @@ The segmentation step aims at associating cloud areas (2D data) or cloud volumes **Watershedding in 3D:** Markers are set in the entire column above the individual feature positions identified in the detection step. Then watershedding with a fixed threshold is used to determine the volume around each feature above/below that threshold value. This results in a mask with the feature id at all voxels identified as part of the clouds and zeros in all cloud free areas. -**Current development:** -We are currently working on providing additional approaches and algorithms for the segmentation step. Several of these approaches will combine the feature detection and segmentation into a single st pixe on diff --git a/doc/tracking_output.rst b/doc/tracking_output.rst index fff121f3..bb62c8f8 100644 --- a/doc/tracking_output.rst +++ b/doc/tracking_output.rst @@ -2,9 +2,9 @@ Tracking output ------------------------- -Tracking outputs a `pandas` dataframe with additional variables in addition to the variables output by Feature Detection (see `Feature Detection Output`_). The additional variables added by tracking, with column names listed in the `Variable Name` column, are described below with units. +Tracking outputs a `pandas` dataframe with additional variables in addition to the variables output by Feature Detection (see `Feature Detection Output`_). The additional variables added by tracking, with column names listed in the `Variable Name` column, are described below with units. -Variables that are common to all feature detection files: +Variables that are common to all tracking files: .. csv-table:: tobac Tracking Output Variables :file: ./tracking_base_out_vars.csv From 3c17299f4d90dfa0cf42d01a526d069d771fefdf Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sat, 2 Jul 2022 22:04:23 -0600 Subject: [PATCH 072/187] Fixed links --- doc/data_input.rst | 4 ++-- doc/feature_detection.rst | 2 +- doc/feature_detection_output.rst | 2 +- doc/index.rst | 20 +++++++++++--------- doc/tracking_output.rst | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/data_input.rst b/doc/data_input.rst index 33c19a13..6cb36d09 100644 --- a/doc/data_input.rst +++ b/doc/data_input.rst @@ -13,7 +13,7 @@ For the future development of the next major version of tobac (v2.0), we are mov The output of the different analysis steps in tobac are output as either pandas DataFrames in the case of one-dimensional data, such a lists of identified features or feature tracks or as Iris cubes in the case of 2D/3D/4D fields such as cloud masks. Note that the dataframe output from tracking is a superset of the features dataframe. -For information on feature detection *output*, see `Feature Detection Output`_. -For information on tracking *output*, see `Tracking Output`_. +For information on feature detection *output*, see :doc:`feature_detection_output`. +For information on tracking *output*, see :doc:`tracking_output`. Note that in future versions of tobac, it is planned to combine both output data types into a single hierarchical data structure containing both spatial and object information. Additional information about the planned changes can be found in the v2.0-dev branch of the main tobac repository, as well as the tobac roadmap. diff --git a/doc/feature_detection.rst b/doc/feature_detection.rst index 71ab9545..ec52ff90 100644 --- a/doc/feature_detection.rst +++ b/doc/feature_detection.rst @@ -1,4 +1,4 @@ -.. _Feature Detection: +.. _feature-detection-overview: Feature Detection Basics --------------------- diff --git a/doc/feature_detection_output.rst b/doc/feature_detection_output.rst index e03b605a..82319cb3 100644 --- a/doc/feature_detection_output.rst +++ b/doc/feature_detection_output.rst @@ -2,7 +2,7 @@ Feature detection output ------------------------- -Feature detection outputs a `pandas` dataframe with several variables. The variables, (with column names listed in the `Variable Name` column), are described below, with units. Note that while these variables come initially from the feature detection step, segmentation and tracking also share some of these variables. See `Tracking Output`_ for the additional columns added by tracking. +Feature detection outputs a `pandas` dataframe with several variables. The variables, (with column names listed in the `Variable Name` column), are described below, with units. Note that while these variables come initially from the feature detection step, segmentation and tracking also share some of these variables. See :doc:`tracking_output` for the additional columns added by tracking. Variables that are common to all feature detection files: diff --git a/doc/index.rst b/doc/index.rst index 82cd2787..5ce18234 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,6 +1,6 @@ .. - Homepage -.. _tobac Home: + Tobac homepage + tobac - Tracking and Object-Based Analysis of Clouds ------------------------------------------------------- @@ -8,7 +8,7 @@ tobac - Tracking and Object-Based Analysis of Clouds The software is set up in a modular way to include different algorithms for feature identification, tracking, and analyses. **tobac** is also input variable agnostic and doesn't rely on specific input variables, nor a specific grid to work. -In the current implementation, individual features are identified as either maxima or minima in a two-dimensional time-varying field (see `Feature Detection`_). An associated volume can then be determined using these features with a separate (or identical) time-varying 2D or 3D field and a threshold value (see `Segmentation`_). The identified objects are linked into consistent trajectories representing the cloud over its lifecycle in the tracking step. Analysis and visualization methods provide a convenient way to use and display the tracking results. +In the current implementation, individual features are identified as either maxima or minima in a two-dimensional time-varying field (see :doc:`feature_detection`). An associated volume can then be determined using these features with a separate (or identical) time-varying 2D or 3D field and a threshold value (see :doc:`segmentation`). The identified objects are linked into consistent trajectories representing the cloud over its lifecycle in the tracking step. Analysis and visualization methods provide a convenient way to use and display the tracking results. Version 1.2 of tobac and some example applications are described in a manuscript in Geoscientific Model Development as: @@ -22,9 +22,6 @@ The project is currently being extended by several contributors to include addit installation data_input - feature_detection - segmentation - linking analysis plotting examples @@ -37,12 +34,17 @@ The project is currently being extended by several contributors to include addit feature_detection_output .. toctree:: - :caption: Output Documentation + :caption: Segmentation :maxdepth: 2 - feature_detection_output - tracking_output + segmentation + +.. toctree:: + :caption: Tracking + :maxdepth: 2 + linking + tracking_output .. toctree:: :caption: API Reference diff --git a/doc/tracking_output.rst b/doc/tracking_output.rst index bb62c8f8..7eaf92b2 100644 --- a/doc/tracking_output.rst +++ b/doc/tracking_output.rst @@ -2,7 +2,7 @@ Tracking output ------------------------- -Tracking outputs a `pandas` dataframe with additional variables in addition to the variables output by Feature Detection (see `Feature Detection Output`_). The additional variables added by tracking, with column names listed in the `Variable Name` column, are described below with units. +Tracking outputs a `pandas` dataframe with additional variables in addition to the variables output by Feature Detection (see :doc:`feature_detection_output`). The additional variables added by tracking, with column names listed in the `Variable Name` column, are described below with units. Variables that are common to all tracking files: From 589d80354b684d993c58a555eed2bef206b19d9a Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sat, 2 Jul 2022 22:05:25 -0600 Subject: [PATCH 073/187] Fixed some sphinx warnings --- doc/data_input.rst | 2 +- doc/feature_detection_output.rst | 1 - doc/tracking_output.rst | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/data_input.rst b/doc/data_input.rst index 6cb36d09..de2aafe8 100644 --- a/doc/data_input.rst +++ b/doc/data_input.rst @@ -1,6 +1,6 @@ .. Description of the input data required. -.. _Data Input: + Data input ========== diff --git a/doc/feature_detection_output.rst b/doc/feature_detection_output.rst index 82319cb3..bf4cddd4 100644 --- a/doc/feature_detection_output.rst +++ b/doc/feature_detection_output.rst @@ -1,4 +1,3 @@ -.. _Feature Detection Output: Feature detection output ------------------------- diff --git a/doc/tracking_output.rst b/doc/tracking_output.rst index 7eaf92b2..9aa9ad39 100644 --- a/doc/tracking_output.rst +++ b/doc/tracking_output.rst @@ -1,4 +1,3 @@ -.. _Tracking Output: Tracking output ------------------------- From c28b47df876f33654b81069713f678967502556d Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sat, 2 Jul 2022 22:10:24 -0600 Subject: [PATCH 074/187] Cleaned up installation --- doc/installation.rst | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/doc/installation.rst b/doc/installation.rst index 7cacb335..6ea63479 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -4,34 +4,29 @@ tobac works with Python 3 installations. The easiest way is to install the most recent version of tobac via conda or mamba and the conda-forge channel: -``` -conda install -c conda-forge tobac -``` + ``conda install -c conda-forge tobac`` + or -``` -mamba install -c conda-forge tobac -``` + ``mamba install -c conda-forge tobac`` This will take care of all necessary dependencies and should do the job for most users. It also allows for an easy update of the installation by -``` -conda update -c conda-forge tobac -``` + ``conda update -c conda-forge tobac`` + or -``` -mamba update -c conda-forge tobac -``` + + ``mamba update -c conda-forge tobac`` You can also install conda via pip, which is mainly interesting for development purposes or using specific development branches for the Github repository. The follwoing python packages are required (including dependencies of these packages): - -*trackpy*, *scipy*, *numpy*, *iris*, *scikit-learn*, *scikit-image*, *cartopy*, *pandas*, *pytables* - +*numpy*, *scipy*, *scikit-image*, *pandas*, *pytables*, *matplotlib*, *iris*, *xarray*, *cartopy*, *trackpy* + If you are using anaconda, the following command should make sure all dependencies are met and up to date: - ``conda install -c conda-forge -y trackpy scipy numpy iris scikit-learn scikit-image cartopy pandas pytables`` + + ``conda install -c conda-forge -y numpy scipy scikit-image pandas pytables matplotlib iris xarray cartopy trackpy`` You can directly install the package directly from github with pip and either of the two following commands: From f4484e5782c0aba9f1b81c844c464cb010b287b4 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sun, 3 Jul 2022 15:47:34 -0600 Subject: [PATCH 075/187] More documentation on feature detection parameters --- doc/conf.py | 5 +- doc/feature_detection/index.rst | 9 + .../multiple_thresholds_example.ipynb | 281 +++++++++++++++++ .../notebooks/n_min_threshold_example.ipynb | 288 ++++++++++++++++++ ...ion.rst => feature_detection_overview.rst} | 0 doc/index.rst | 6 +- doc/threshold_detection_parameters.rst | 38 +++ 7 files changed, 624 insertions(+), 3 deletions(-) create mode 100644 doc/feature_detection/index.rst create mode 100644 doc/feature_detection/notebooks/multiple_thresholds_example.ipynb create mode 100644 doc/feature_detection/notebooks/n_min_threshold_example.ipynb rename doc/{feature_detection.rst => feature_detection_overview.rst} (100%) create mode 100644 doc/threshold_detection_parameters.rst diff --git a/doc/conf.py b/doc/conf.py index 019d6302..0d648ff4 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -10,7 +10,7 @@ # What Sphinx extensions do we need extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.ifconfig', - 'sphinx_rtd_theme','sphinx.ext.napoleon'] + 'sphinx_rtd_theme','sphinx.ext.napoleon', 'nbsphinx'] html_theme = "sphinx_rtd_theme" @@ -19,6 +19,9 @@ master_doc = 'index' +# allow dropdowns +collapse_navigation = False + # Include our custom CSS (currently for special table config) def setup(app): app.add_css_file("theme_overrides.css") diff --git a/doc/feature_detection/index.rst b/doc/feature_detection/index.rst new file mode 100644 index 00000000..a347198d --- /dev/null +++ b/doc/feature_detection/index.rst @@ -0,0 +1,9 @@ +#################### + Feature Detection Parameter Examples +#################### + +.. toctree:: + :maxdepth: 2 + + notebooks/multiple_thresholds_example + notebooks/n_min_threshold_example diff --git a/doc/feature_detection/notebooks/multiple_thresholds_example.ipynb b/doc/feature_detection/notebooks/multiple_thresholds_example.ipynb new file mode 100644 index 00000000..04465007 --- /dev/null +++ b/doc/feature_detection/notebooks/multiple_thresholds_example.ipynb @@ -0,0 +1,281 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How multiple thresholds changes the features detected" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import tobac\n", + "import xarray as xr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Generate Feature Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we will generate some simple feature data where the features that we want to detect are *higher* values than the surrounding (0)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Dimensions here are time, y, x.\n", + "input_field_arr = np.zeros((1,100,200))\n", + "input_field_arr[0, 15:85, 10:185]=50\n", + "input_field_arr[0, 20:80, 20:80]=100\n", + "input_field_arr[0, 40:60, 125:170] = 100\n", + "input_field_arr[0, 30:40, 30:40]=200\n", + "input_field_arr[0, 50:75, 50:75]=200\n", + "input_field_arr[0, 55:70, 55:70]=300\n", + "\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "plt.title(\"Base data\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# We now need to generate an Iris DataCube out of this dataset to run tobac feature detection. \n", + "# One can use xarray to generate a DataArray and then convert it to Iris, as done here. \n", + "input_field_iris = xr.DataArray(input_field_arr, dims=['time', 'Y', 'X'], coords={'time': [np.datetime64('2019-01-01T00:00:00')]}).to_iris()\n", + "# Version 2.0 of tobac (currently in development) will allow the use of xarray directly with tobac. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Single Threshold\n", + "Let's say that you are looking to detect any features above value 50 and don't need to separate out individual cells within the larger feature. For example, if you're interested in tracking a single MCS, you may not care about the paths of individual convective cells within the feature. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWoAAAEICAYAAAB25L6yAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAAsTAAALEwEAmpwYAAAj9ElEQVR4nO3deZhcVZ3/8fcnC2kSwKxkQhIgQkBATIAMMLIow4wsEoIoIeASkDE4AoLCKIsQUCJBWWRU1DAEgiIQth9BUXYVVIIECSSEJSQQmsRsbEkgS3d/f3/c27HSdHVXd1dV30p9Xs9zn646de85p25Xf/vUueecq4jAzMyyq0tnV8DMzFrmQG1mlnEO1GZmGedAbWaWcQ7UZmYZ50BtZpZxDtQVRtLnJT1QpLz+IOm/ipFXOfJtppwbJV3azmPz1lHSjpJCUrd25LulpHslvSPp9vbUzawpB+oMknSgpL+kf+xvSvqzpH8FiIibI+JTnVi3gyStTrc1aUBbnbNt31l1y4jPAQOBfhFxXNMXJZ0kqb7JOftkzut9Jd2dntvXJJ1YvqpbVrW5xWClJWkb4DfAfwPTgS2Ag4B1nVmvRhHxGLAVJC1PYCHQOyLqGveRVHB+krrlHrsZ2AF4qZX39NeIODDPaz8F1pME+5HAbyXNjoi5xa2mVRK3qLNnF4CIuCUi6iPi/Yh4ICKehY0tsscbd05btF+V9LKktyT9VGmklNRV0pWSVkhaKOn0lr7SS/qypHlpPvdL2qED72OH9JvAKkkPSOqfltHYrXCKpEXAIy2VrcTVkpal3zCelfTRnHL6SPptWs5MSTvlvJ+PS/pbetzfJH08z/vuKumK9DwtAD7d0huTtFvadfK2pLmSjk7TLwEuAo5PW8qntOWESeoFfBa4MCJWR8TjwAzgi23JxzY/DtTZ8xJQL2mapCMk9SngmKOAfwVGAGOBw9L0rwBHkLTM9gaOyZeBpGOA84FjgQHAY8At7XoHiROBk4FtSb4VnNPk9U8AuwGHtVL2p4CDSf6B9QaOB1bm5HMCcAnQB5gPTErfT1/gt8D/Av2Aq0hap/2aqetXSM7hXsAoku6LZknqDtwLPJC+tzOAmyXtGhETge8Dt0XEVhFxfZ5s9kr/Kbwk6cKcf5y7APUR8VLOvrOBPfLVx6qDA3XGRMS7wIFAANcByyXNkDSwhcMmR8TbEbEIeJQkMEMStK+JiNqIeAuY3EIepwKXRcS89Gv794GRHWhV3xARL0XE+yRdOCObvH5xRKxJX2+p7A3A1sBHAKX7LMnJ566IeDI97uaccj4NvBwRv4yIuoi4BXgBGN1MXccCP4qI1yPiTeCyFt7X/iRdP5MjYn1EPELSVXVCYaeFPwEfJQnyn02P+5/0ta2Ad5rs/w7J+7cq5kCdQWkwOikihpD8UW8H/KiFQ/6R8/g90j7k9LjXc17LfdzUDsA16df5t4E3AQGD21b7VuvUXF3ylp0Gwp+Q9N0ulTQl7cdvrZztgNealPkazb+fpuep6XEf2DciGgrI9wMiYkFELIyIhoh4Dvgu/2zBrwa2aXLINsCqQvK2zZcDdcZFxAvAjSQBu62WAENyng9tYd/XgVMjonfOtmVE/KUd5RYid9nGFsuOiP+NiH1IugB24Z8t0JYsJvkHkGt74I1m9l3CpuempZEri4GhknL/dvLlW4gg+acESbdXN0nDc14fAfhCYpVzoM4YSR+RdLakIenzoSRfj59oR3bTgTMlDZbUG/h2C/v+HDhP0h5puR+S9IHhZSWSt2xJ/yppv7RveA2wFqgvIM/7gF0knSipm6Tjgd1Juimamg58XdKQ9JrAuS3kOzOtx7ckdVcytG40cGshbzS97jAwffwR4ELgHoCIWAPcBXxXUi9JBwBjgF8Wkrdtvhyos2cVsB8wU9IakgA9Bzi7HXldR3LR61ng7yTBq45mAl1E3A1cDtwq6d20zCPa8wbaqpWytyF5H2+RdDGsBK4oIM+VJBcIz06P+RZwVESsaGb364D7SS7cPU0SLPPlux44Oq3fCuBa4EvpN59CHAo8m/5u70vL+n7O618DtgSWkVxQ/W8PzTP5xgHVQ9IRwM8joiPD7syszNyi3owpmc58ZPrVfzAwEbi7s+tlZm3TaqCWNDWdbDAnJ62vpAeVTLJ4MHesr6TzJM2X9KKkw5rP1cpEJGOM3yLp+phHMiHDzIpEUo2kJyXNTidAXZKmFy1Ottr1IelgkmFDN0XER9O0HwBvRsRkSecCfSLi25J2J+lX25dkGNNDwC4RUcjFHzOziiNJQK+IWJ1e9H4cOJNkAldR4mSrLeqI+BPJuNZcY4Bp6eNp/HPG2xjg1ohYFxELSWaK7VvQuzUzq0CRWJ0+7Z5uQRHjZHsXZRrYODssIpZI2jZNH8ymw8hqyTMRQNIEYAJAV7ru0/MD4/zNzD5oFW+tiIgBHcnjsEN6xco3C/uiP+vZdXNJhoU2mhIRU3L3kdQVmAXsDPw0ImZK6nCcbFTs1fOaWzat2b6V9I1OAdhGfWM/HVrkqpjZ5uihuKOlmaMFWfFmPTPvH9L6jkD3Qa+sjYhRLe2TdluMTOcr3K1NFw5rquA42ai9oz6WShoEkP5clqbXsukMryEkM7nMzDIkqI+GgrY25RrxNvAH4HCKGCfbG6hnAOPTx+NJZ1al6eMk9ZA0DBgOPNnOMszMSiKABqKgrTWSBqQtaSRtCfwHyQJgRYuTrXZ9SLoF+CTQX1ItyVjcycD0dL3dRcBxABExV9J04HmSGXCnecSHmWVRA21rLbdgEDAt7afuAkyPiN9I+itFipOtBuqIyLd8Y7OdyhExiXRNYLOO6tWnJ2MnjmbQzgNQl8LvHGOVLRqCJfOXM/2Se1nz1nvFz59gQxu7NfLmldzUY69m0ldSpDjpW3FZpo2dOJo99v0INd1qULPXYGxzFAR9+/Zj7ES44azbSpA/1BfQrZEVDtSWaYN2HuAgXYWEqOlWw6CdOzQKr0WF9D9nhQO1ZZq6yEG6SgmVrLsrgPoKWpDOgdrMqlLRLiWWgVfPM2vFbvvtwpgTR/PpsYdz9IlHccPN19PQ0PKfee3iWu79/Yx2l3nXvXeydPnSNh1Tu7iWo47/4BLitYtr+diBezDmxNEbt/Ub1pelTlkVBPUFblngFrVZK2p61HDPr+8FYOWbKzn7O99g1epVfP3Us/Ie88aSWn5z/72MPvzodpV592/uZPhOuzBwQEv3NC7c9oO33/ge2qs9daqrq6Nbt+yFmQjYkI0YXJDsnUGzDtj6dzPof+0VdFu6hLqBg1jxtXNYdUT7gmVz+vXtx/fOv5TPnXQsZ0w4k4aGBq74yQ95ctZM1m9Yz+eP+wLjjj2BK3/yQ15Z+ApjThzNZ476DF88fnyz+wFcd9MUZtz3/1CXLhz8bwfz0d33ZM68OZxz4Tep6VHDbVNvZ/7C+Uy+ehLvvf8efXr34bKJP2Db/tsyZ94czv/euWxZU8PeI1qc5fwBjz/xGD+ecg3r169n6JDtueyiy+nVsxc/ue7HPPrYI6xbt5a9PrY33z3/Uu5/5PcfqNORYw/jjpvupm/vvjz3/HP84JrL+OUvfs2Pp1zDsuXLeGNJLX169+WCs7/DxMsuYvE/ksl355/9HfYZsQ9PzprJpCsvBUCCX025ha16Nb0HcqmI+gq69uFAbZuNrX83g4HfP58ua5P1c7r/YzEDv38+QFGD9dAh29PQ0MDKN1fy8B8fYuuttubOm+5m/fp1jPuv4zlgvwM5+/T/YeqvrucXV18HwG133drsfgteXcDDf3iQ6TfeyZY1W/L2O2/T+0O9uXn6L/nWmeex5+57sqFuA5f+8BKuvfLn9O3Tj/se+C1XX3sVl100mfO++20uPOci9t1nPy6/ZnLeOi96YxFjThwNwN4j9uaMU8/kZ1Ov5Yaf3kTPLXsyZdovuOHmqZz+lTP4wtgvcvpXzgDgfy46m0cfe4TDDz1ikzq1Zu4Lc/j1dbdRU1PD2d/5BuNPPJlRI0ex+B+LOeWMk/nd7fcz9Vf/x0Xfvph9RuzDmvfW0GOLHkX47RQmgAa3qM3Kr/+1V2wM0o26rF1L/2uvKGqgBmhcx/3PMx/jxfkvcv/Dvwdg1ZpVvPb6q3Tv3n2T/fPt99cn/8yxoz/LljVbAtD7Q70/UNbCVxfy0oKXOPm0kwBoaKhnQP8BrFq9ilWr3mXfffYDYMyRx/DYX/7YbH2bdn08+tgjzF8wnxNOOR6ADXXrGblnMmdj5qwn+L+brmPt2vd5+913GP7h4fz7wW1bNO3fDz6UmpoaAP7y5J+Zv2D+xtdWr1nN6jWr2XvEPky++vuMPvxoPnXIp+g1cFCbyugot6jNOkG3pUvalN5er9cuomvXrvTr248I+M45F3HQvx28yT4zZ2160/h8+z321z+RrDufXxAM//Bwbpt6xybp7656t9Vj8+YZwQH7HcBVk360Sfq6deu45PKJ3Dntbgb9y3b8eMo1rFu/rtk8unbtSqTN0qb7bFnTc+Pjhobgtqm3bwzcjSac9FU+ceAh/PHPf2Dslz/HDT+9iZ123Kld76etkgkvlROoPerDNht1eVpk+dLb4823VjJx8oV8/rgvIIkD9z+IW+78NRvqNgCw8LWFvPf+e/TquRVr1qzeeFy+/Q7Y70DunHEH7699H4C333kbgF49e7HmveT4YTsM48233uTvzz4NwIa6Dbz8yktss/U2bLXV1jz1zFMAbRplMnLPkTw9exavvf4qAO+vfZ+Fry3cGHD79O7LmvfWbPwG0LROAIMHDWHOvOQOfQ888s/9mjpw/wP51e2/3Ph83ovPA7Co9jV23XlXJow/lY/uticLX11QcP07KoAN0aWgLQvcorbNxoqvnbNJHzVAQ00NK752TofyXbtuLWNOHE1d3Qa6duvGmCOO4eTPfxmA444ZyxtLajn2C2OICPr06cu1V/ycXYfvSteu3Tj6xKM49qhj+dK4k5rd7+CPf4IXXprHZ790DN27bcEnDvgE3zztHD4z+rNMvOyijRfu/nfyT7j0yu+xavUq6uvqGH/CSQzfaRcuu+jyjRcTD9z/oILfU98+/bhs4g/45gXf2DhU76yvfoNhOwzjuGOOZ/QJRzJ40BD23P1jG49pWqfTv3IGF1x6Hr+48WeM2GNE3rIuOOdCvnv5xYw+4dPU19cxaq99+e5532PaLTcy86kn6NK1KzsP25mDP35w3jyKLRD1FdRObfWeieXgGwdYPhfcdwbb9W/x5hebKPWoDyuvxSveYNKRP94k7aG4Y1ZrC/m3ZreP9Ygb792uoH333/HVDpfXUW5R22Zl1RFHOzBbqyqtj9qB2syqkKjPSP9zIRyoLdOiIQjCCzNVoSA2jiopft7QUEF91A7UlmlL5i+nb99+Xuq0ygTB2rq1LJm/vDT5h1gfXUuSdyk4UFumTb/kXsZOxHd4qTK5d3gplYYK+sfvQG2Ztuat90pyhw+rbsnFRHd9mJllmC8mmpllmi8mmplVgPpwH7WZWWYFYkNUTvirnJqamRWJLyaamWVcIHd9mJllnS8mmpllWAQenlc2++dfA9fMMuKJ2Z1dgw9ILiYWZwq5pKHATcC/AA3AlIi4RtLFwFeAxnnw50fEfekx5wGnAPXA1yPi/pbKqOxAbWbWTkW8mFgHnB0RT0vaGpgl6cH0tasj4orcnSXtDowD9gC2Ax6StEtE1OcrwIHazKpOIBqKdDExIpYAS9LHqyTNA1q628UY4NaIWAcslDQf2Bf4a74DKqeTxsysiOrpUtDWFpJ2BPYCZqZJp0t6VtJUSX3StMHA6zmH1dJyYHegNrPqE0BDdCloA/pLeipnm9BcnpK2Au4EzoqId4GfATsBI0la3Fc27pqnSnm568PMqpDaciuuFa3dM1FSd5IgfXNE3AUQEUtzXr8O+E36tBYYmnP4EGBxS/m7RW1mVSeADdG1oK01kgRcD8yLiKty0gfl7PYZYE76eAYwTlIPScOA4cCTLZXhFrWZVZ0INXZrFMMBwBeB5yQ9k6adD5wgaSTJ/4VXgVOTsmOupOnA8yQjRk5racQHdDBQS/oG8F9pRZ4DTgZ6ArcBO6aVGxsRb3WkHDOzYivWhJeIeJzm+53va+GYScCkQstod00lDQa+DoyKiI8CXUnGBp4LPBwRw4GH0+dmZpmRrEetgrYs6Oi/lG7AlpK6kbSkF5OMEZyWvj4NOKaDZZiZFVlyh5dCtixod9dHRLwh6QpgEfA+8EBEPCBpYDoAnIhYImnb5o5Ph7hMAKihZ3ur0WYrR/QqW1mbo36z13R2Fcw6LBmel43WciHaHajTwdtjgGHA28Dtkr5Q6PERMQWYArCN+rY4htDMrJiKudZHOXTkYuJ/AAsjYjmApLuAjwNLJQ1KW9ODgGVFqKeZWVFV0jKnHanpImB/ST3TcYSHAvNIxgiOT/cZD9zTsSqamRVXssypCtqyoCN91DMl3QE8TTIW8O8kXRlbAdMlnUISzI8rRkXNzIqpKvqoASJiIjCxSfI6kta1mVkmJavnVU7Xh2cmmlnVSaaQO1CbmWWYW9RmZpmXlVmHhXCgNrOq0zjqo1I4UJtZVXLXh7VbtzHLW9+pDOruGdDZVTArmWLeM7EcHKjNrOoEUOcWtZlZtrnrw8wsy8JdH2ZmmdZ444BK4UBtZlXJLWozswyrmhsHmJlVqkDUNfhioplZprmP2swsy8JdH2ZmmeY+aiuZJ0beUdT89n/mc0XNz6ySOFCbmWVYIOp9MdHMLNt8MdHMLMOiwi4mVk7b38ysiCJU0NYaSUMlPSppnqS5ks5M0/tKelDSy+nPPjnHnCdpvqQXJR3WWhkO1GZWhZJFmQrZClAHnB0RuwH7A6dJ2h04F3g4IoYDD6fPSV8bB+wBHA5cK6lrSwU4UJtZVSpWizoilkTE0+njVcA8YDAwBpiW7jYNOCZ9PAa4NSLWRcRCYD6wb0tluI/azKpOBNQ3FNxH3V/SUznPp0TElOZ2lLQjsBcwExgYEUuS8mKJpG3T3QYDT+QcVpum5eVAbWZVqQ2jPlZExKjWdpK0FXAncFZEvCvlzb+5F6KlvN31YWZVJyhe1weApO4kQfrmiLgrTV4qaVD6+iBgWZpeCwzNOXwIsLil/B2ozawKFe9iopKm8/XAvIi4KuelGcD49PF44J6c9HGSekgaBgwHnmypDHd9mFlVihY7G9rkAOCLwHOSnknTzgcmA9MlnQIsAo5Lyo25kqYDz5OMGDktIupbKsCB2syqUqHdGq3nE4/TfL8zwKF5jpkETCq0DAdqM6s6yaiPyun5daA2s6pUxK6PknOgNrOqVKyuj3JwoDazqhMUPvQuCxyozawqVVDPR8fGUUvqLekOSS+kK0f9W0srRpmZZUJANKigLQs6etnzGuD3EfERYATJYiTNrhhlZpYlxZyZWGrtDtSStgEOJpmRQ0Ssj4i3yb9ilJlZZkQUtmVBR/qoPwwsB26QNAKYBZxJ/hWjNiFpAjABoIaeHaiGWWmtHNGrs6tQVv1mr+nsKpRc41oflaIjXR/dgL2Bn0XEXsAa2tDNERFTImJURIzqTo8OVMPMrI0CCBW2ZUBHAnUtUBsRM9Pnd5AE7nwrRpmZZUYldX20O1BHxD+A1yXtmiYdSrLISL4Vo8zMMqKwER9ZGfXR0XHUZwA3S9oCWACcTBL8P7BilJlZpmSktVyIDgXqiHgGaO7OB82uGGVmlglRWRcTPTPRzKpTtbSozcwql1vUZmbZ1tDZFSicA7WZVZ/GcdQVwoHazKpSVsZIF8KBuoLs/8znOrsKZpsPB2ozs4xz14eZWbbJLWozswwLQUamhxfCgdrMqpNb1GZmGedAbWaWcQ7UZmYZVmETXjp6c1szs4qkKGxrNR9pqqRlkubkpF0s6Q1Jz6TbkTmvnSdpvqQXJR1WSF3dorZOd8jy2Zyy6EEGrH+H5Vt8iOu3/08eHTCis6tlm7vidX3cCPwEuKlJ+tURcUVugqTdgXHAHsB2wEOSdomI+pYKcIvaOtUhy2fzzQX3MHD9O3QBBq5/h28uuIdDls/u7KrZZq5YLeqI+BPwZoHFjgFujYh1EbEQmA/s29pBblFnTN09Azq7CmV1yqIHqWnYsElaTcMGTln0oFvVVlqF91H3l/RUzvMpETGlgONOl/Ql4Cng7Ih4CxgMPJGzT22a1iK3qK1TDVj/TpvSzYoi2rDBiogYlbMVEqR/BuwEjASWAFem6c39d2i13e5AbZ1q+RYfalO6WdEUHqjbnnXE0oioj4gG4Dr+2b1RCwzN2XUIsLi1/ByorVNdv/1/srZL903S1nbpzvXb/2cn1ciqhRoK29qVtzQo5+lngMYRITOAcZJ6SBoGDAeebC0/91Fbp2rsh/aoDyu7Io36kHQL8EmSvuxaYCLwSUkj01JeBU4FiIi5kqYDzwN1wGmtjfgAB2rLgEcHjHBgtrIqdERHISLihGaSr29h/0nApLaU4UBtZtWpgmYmOlCbWXXyWh9mZtnmGweYmWVZtH9ER2dwoDaz6uQWtZlZxjlQm5llWyX1UXtmoplZxrlFbWbVqYJa1A7UZlZ9POrDzKwCuEVtZpZdorIuJjpQm1l1qqBA3eFRH5K6Svq7pN+kz/tKelDSy+nPPh2vpplZERV4v8SstLqLMTzvTGBezvNzgYcjYjjwcPrczCxbGgrcMqBDgVrSEODTwP/lJI8BpqWPpwHHdKQMM7NSqKQWdUf7qH8EfAvYOidtYEQsAYiIJZK2be5ASROACQA19OxgNUqv25jlbT6m2u4ovrnqN3tNZ1fBSiEjQbgQ7W5RSzoKWBYRs9pzfERMabyrb3d6tLcaZmZt17a7kHe6jrSoDwCOlnQkUANsI+lXwFJJg9LW9CBgWTEqamZWTFnp1ihEu1vUEXFeRAyJiB2BccAjEfEFkrvsjk93Gw/c0+FampkVW5W0qPOZDEyXdAqwCDiuBGWYmXVI1U0hj4g/AH9IH68EDi1GvmZmJZGh1nIhPDPRzKqO0q1SOFCbWXVyi9rMLNsqadSHA7WZVScHajOzDKuwGwf4nolmVp2KNI5a0lRJyyTNyUnLu4qopPMkzZf0oqTDCqmqA7WZVaUiLsp0I3B4k7RmVxGVtDvJBME90mOuldS1tQIcqM2sOhWpRR0RfwLebJKcbxXRMcCtEbEuIhYC84F9WyvDgdrMqlIbWtT9JT2Vs00oIPtNVhEFGlcRHQy8nrNfbZrWIl9MNLPqE7TlpgArImJUkUpubp5Nq+12t6jNrOo03ty2hDcOWJquHkqTVURrgaE5+w0BFreWmQO1mVWn0q6el28V0RnAOEk9JA0DhgNPtpaZuz7MrCopijPjRdItwCdJ+rJrgYnkWUU0IuZKmg48D9QBp0VEfWtlOFCbWfUp4up5EXFCnpeaXUU0IiYBk9pShgO1mVUlr/VhZpZxlTSF3IG6QL6juNlmxi1qM7MM69jQu7JzoDaz6uRAbWaWXY0TXiqFA7WZVSU1VE6kdqA2s+rju5CbmWWfh+eZmWWdW9RmZtnmi4lmZlkWQJEWZSoHB2ozq0ruozYzyzCPozYzy7oId32YmWWdW9RmZlnnQG1mlm1uUZuZZVkA9ZUTqR2ozawqVVKLukt7D5Q0VNKjkuZJmivpzDS9r6QHJb2c/uxTvOqamRVJ48iP1rYMaHegJrnV+dkRsRuwP3CapN2Bc4GHI2I48HD63MwsUxSFbVnQ7kAdEUsi4un08SpgHjAYGANMS3ebBhzTwTqamRVXtGHLgKL0UUvaEdgLmAkMjIglkARzSdvmOWYCMAGghp7FqEZB+s1eU7ayzCybBKiaLiZK2gq4EzgrIt6VVNBxETEFmAKwjfpWzhkzs82CMtL/XIiO9FEjqTtJkL45Iu5Kk5dKGpS+PghY1rEqmpkVWYV1fXRk1IeA64F5EXFVzkszgPHp4/HAPe2vnplZKRQ44iMjre6OdH0cAHwReE7SM2na+cBkYLqkU4BFwHEdqqGZWQkUc0SHpFeBVUA9UBcRoyT1BW4DdgReBcZGxFvtyb/dgToiHifpk2/Ooe3N18ysLIrfWj4kIlbkPG8cqjxZ0rnp82+3J+MO9VGbmVWkSEZ9FLJ1QNGGKjtQm1l1Ku7FxAAekDQrHXoMTYYqA80OVS6E1/ows6rUhuF5/SU9lfN8Sjq8ONcBEbE4nTfyoKQXilLJlAO1mVWnwgP1iogY1XJWsTj9uUzS3cC+pEOV04l/HRqq7K4PM6s+ATQUuLVCUi9JWzc+Bj4FzKGIQ5Uru0X9xOzOroGZVSARxZyZOBC4O52V3Q34dUT8XtLfKNJQ5coO1GZm7dVQQHO5ABGxABjRTPpKijRU2YHazKpPY9dHhXCgNrOqVEmLMjlQm1l1cqA2M8uy7Cy4VAgHajOrPr4LuZlZ9rmP2sws6xyozcwyLIAGB2ozswzzxUQzs+xzoDYzy7AA6itnaqIDtZlVoYBwoDYzyzZ3fZiZZZhHfZiZVQC3qM3MMs6B2swswyKgvr6za1EwB2ozq05uUZuZZZwDtZlZloVHfZiZZVpAeMKLmVnGeQq5mVmGRUCDA7WZWbb5YqKZWbaFW9RmZlnmGweYmWWbF2UyM8u2AKKCppB3KVXGkg6X9KKk+ZLOLVU5ZmZtFumNAwrZWlGOWFeSQC2pK/BT4Ahgd+AESbuXoiwzs/aIhihoa0m5Yl2pWtT7AvMjYkFErAduBcaUqCwzs7YrTou6LLGuVH3Ug4HXc57XAvvl7iBpAjAhfbruobhjTonq0hb9gRWuA5CNemShDpCNemShDpCNeuza0QxW8db9D8Ud/QvcvUbSUznPp0TElPRxq7GuGEoVqNVM2ibfIdI3OgVA0lMRMapEdSlYFuqRhTpkpR5ZqENW6pGFOmSlHk2CZrtExOHFqAsFxLpiKFXXRy0wNOf5EGBxicoyM+ssZYl1pQrUfwOGSxomaQtgHDCjRGWZmXWWssS6knR9RESdpNOB+4GuwNSImNvCIVNaeK2cslCPLNQBslGPLNQBslGPLNQBslGPLNQBaFesaxdFBU2jNDOrRiWb8GJmZsXhQG1mlnGdHqg7Y6q5pKGSHpU0T9JcSWem6RdLekPSM+l2ZBnq8qqk59LynkrT+kp6UNLL6c8+JSx/15z3+4ykdyWdVY5zIWmqpGWS5uSk5X3vks5LPycvSjqshHX4oaQXJD0r6W5JvdP0HSW9n3NOfl6MOrRQj7y/gzKei9tyyn9V0jNpeknORQt/m2X9XGRORHTaRtL5/grwYWALYDawexnKHQTsnT7eGniJZPrnxcA5ZT4HrwL9m6T9ADg3fXwucHkZfx//AHYox7kADgb2Bua09t7T389soAcwLP3cdC1RHT4FdEsfX55Thx1z9yvDuWj2d1DOc9Hk9SuBi0p5Llr42yzr5yJrW2e3qDtlqnlELImIp9PHq4B5JDOMsmIMMC19PA04pkzlHgq8EhGvlaOwiPgT8GaT5HzvfQxwa0Ssi4iFwHySz0/R6xARD0REXfr0CZKxsSWV51zkU7Zz0UiSgLHALR0tp5U65PvbLOvnIms6O1A3N/2yrAFT0o7AXsDMNOn09Cvv1FJ2OeQI4AFJs9Jp9QADI2IJJB9cYNsy1AOSMaC5f4jlPheQ/7131mfly8Dvcp4Pk/R3SX+UdFAZym/ud9AZ5+IgYGlEvJyTVtJz0eRvM2ufi7Lq7EBdlumXeQuXtgLuBM6KiHeBnwE7ASOBJSRf9UrtgIjYm2T1rdMkHVyGMj8gHax/NHB7mtQZ56IlZf+sSLoAqANuTpOWANtHxF7AN4FfS9qmhFXI9zvojL+bE9j0n3hJz0Uzf5t5d20mbbMbc9zZgbrTpppL6k7yQbg5Iu4CiIilEVEfEQ3AdZThK1RELE5/LgPuTstcKmlQWs9BwLJS14PkH8XTEbE0rU/Zz0Uq33sv62dF0njgKODzkXaGpl+vV6aPZ5H0h+5Sqjq08Dso97noBhwL3JZTt5Kdi+b+NsnI56KzdHag7pSp5ml/2/XAvIi4Kid9UM5unwFKuqKfpF6Stm58THIRaw7JORif7jYeuKeU9Uht0mIq97nIke+9zwDGSeohaRgwHHiyFBWQdDjwbeDoiHgvJ32AkvWHkfThtA4LSlGHtIx8v4OynYvUfwAvRERtTt1Kci7y/W2Sgc9Fp+rsq5nAkSRXdl8BLihTmQeSfD16Fngm3Y4Efgk8l6bPAAaVuB4fJrliPRuY2/j+gX7Aw8DL6c++Ja5HT2Al8KGctJKfC5J/DEuADSQto1Naeu/ABenn5EXgiBLWYT5Jv2fjZ+Pn6b6fTX9Ps4GngdElPhd5fwflOhdp+o3AV5vsW5Jz0cLfZlk/F1nbPIXczCzjOrvrw8zMWuFAbWaWcQ7UZmYZ50BtZpZxDtRmZhnnQG1mlnEO1GZmGff/AcyDi46bL2QAAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50,]\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center')\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"Single Threshold of 50\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's try a single threshold of 150, which will give us two features on the left side of the image." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [150,]\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center')\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"Single Threshold of 150\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This gives us two detected features with minimum values >150. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Multiple Thresholds\n", + "Now let's say that you want to detect all three maxima within this feature. You may want to do this, if, for example, you were trying to detect overhshooting tops within a cirrus shield. You could pick a single threshold, but if you pick 100, you won't separate out the two features on the left. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [100, ]\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center')\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"Single Threshold of 100\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the power of having multiple thresholds. We can set thresholds of 50, 100, 150, 200 and capture both:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50, 100, 150, 200]\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center')\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"Thresholds: [50, 100, 150, 200]\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:tobac_stable]", + "language": "python", + "name": "conda-env-tobac_stable-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.5" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "25a19fbe0a9132dfb9279d48d161753c6352f8f9478c2e74383d340069b907c3" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/feature_detection/notebooks/n_min_threshold_example.ipynb b/doc/feature_detection/notebooks/n_min_threshold_example.ipynb new file mode 100644 index 00000000..3fc6b41c --- /dev/null +++ b/doc/feature_detection/notebooks/n_min_threshold_example.ipynb @@ -0,0 +1,288 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How `n_min_threshold` changes what features are detected" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import tobac\n", + "import xarray as xr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Generate Feature Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we will generate some simple feature data with a variety of features, large and small. " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Dimensions here are time, y, x.\n", + "input_field_arr = np.zeros((1,80,80))\n", + "# small 5x5 feature, area of 25 points\n", + "input_field_arr[0, 15:20, 10:15]=50\n", + "# larger 30x30 feature, area of 900\n", + "input_field_arr[0, 40:70, 10:30]=50\n", + "# small 2x2 feature within larger 30x30 feature, area of 4 points\n", + "input_field_arr[0, 52:54, 22:24]=100\n", + "# small 4x4 feature within larger 30x30 feature, area of 16 points\n", + "input_field_arr[0, 60:64, 15:19]=100\n", + "\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "plt.title(\"Base data\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "# We now need to generate an Iris DataCube out of this dataset to run tobac feature detection. \n", + "# One can use xarray to generate a DataArray and then convert it to Iris, as done here. \n", + "input_field_iris = xr.DataArray(input_field_arr, dims=['time', 'Y', 'X'], coords={'time': [np.datetime64('2019-01-01T00:00:00')]}).to_iris()\n", + "# Version 2.0 of tobac (currently in development) will allow the use of xarray directly with tobac. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### No `n_min_threshold`\n", + "If we keep `n_min_threshold` at the default value of `0`, all three features will be detected with the appropriate thresholds used." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50, 100]\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature.\n", + "# All filtering is off in this example, although that is not usually recommended.\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center', sigma_threshold=0)\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"n_min_threshold=0\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Increasing `n_min_threshold`\n", + "As we increase `n_min_threshold`, fewer of these separate features are detected. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWoAAAEICAYAAAB25L6yAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAAsTAAALEwEAmpwYAAAkf0lEQVR4nO3deZRcVbn+8e+TwXQGYibIDUmAAAGZTIAIXEFEcckgIYgCAYeAXINLQFC4SkAJKBFQBnFADZchKAJh+hEUZUYRJUiQQEIYAmFoEjNjQsjU3e/vj3M6VpKu7urqqu5Tqeez1lldteucvXedrn571z5776OIwMzMsqtTR1fAzMya50BtZpZxDtRmZhnnQG1mlnEO1GZmGedAbWaWcQ7UGSfpC5IeLFFej0v6n1Lk1R75NlHOTZIuKfLYvHWUtIOkkNSliHy7S7pP0r8l3VFM3cxa4kCdAZIOkvS39I99maQnJX0EICJuiYhPd2DdPibpvXRblQa093K27TqqbhnxeWAg0D8ijtv0RUl7SnpA0hJJm01aSP+BrMk5ny9v8vqhkl6S9L6kxyRtX763YlnlQN3BJPUGfg/8DOgHDAYuBtZ2ZL0aRcQTEdErInoBe6TJfRrTIuKt1uRXTKs147YHXomIujyvrwemAqc2k8cZOedz18ZESQOAu4HvkXw2ngFuL021rZI4UHe8XQAi4taIqI+I1RHxYEQ8DyDpZEl/bdw5bdF+TdKrkpZL+oUkpa91lnRl2nqbJ+mM5r7SS/qKpDlpPg+0sbW2ffpNYKWkB9Mgk9utcKqkt4BHmytbiaslLUq/YTwvac+ccvpK+kNaznRJO+W8n49K+kd63D8kfTTP++4s6Yr0PL0OfKa5NyZpt7Tl+66k2ZKOTtMvBi4ETkhbw5sF44h4OSKuB2a35mSmjgVmR8QdEbEGuAgYIelDReRlFcyBuuO9AtRLmiLpCEl9CzjmKOAjwAjgeOCwNP2rwBHASGAf4Jh8GUg6BjifJBhsDTwB3FrUO0icBJwCbAN8ADh3k9c/DuwGHNZC2Z8GDib5B9YHOAFYmpPPiSTfOPoCc4FJ6fvpB/wB+CnQH7gK+IOk/k3U9ask53BvYBRJ90WTJHUF7gMeTN/bmcAtknaNiInAD4Hb09bw9fnyacGl6T+NJyUdkpO+BzCz8UlErAJe4z/fbKxKOFB3sIhYARwEBHAdsFjSNEkDmznssoh4N+12eIwkMEMStK+JiNqIWA5c1kwepwGXRsSc9Gv7D4GRbWhV3xgRr0TEapKv+iM3ef2iiFiVvt5c2euBrYAPAUr3WZCTz90R8XR63C055XwGeDUifhMRdRFxK/ASMLqJuh4P/CQi3o6IZcClzbyvA4BeJOd8XUQ8StJVdWJhp6VF3wF2JOnymgzcl/MtoRfw7032/zfJ+bEq4kCdAWkwOjkihgB7AtsCP2nmkH/lPH6f5A+a9Li3c17Lfbyp7YFr0q/z7wLLAJEEjGLkq1NTdclbdhoIfw78AlgoaXLaj99SOdsCb25S5ps0/X42PU+bHrfZvhHRUEC+rRYR0yNiZUSsjYgpwJPAkenL7wG9NzmkN7CyFGVb5XCgzpiIeAm4iSRgt9YCYEjO86HN7Ps2cFpE9MnZukfE34ootxC5Ix6aLTsifhoR+5J8xd8F+N8C8p9P8g8g13bAO03su4CNz01zI1fmA0Ml5f6t5Mu3FILknxYk/dojGl+Q1BPYieL6u62COVB3MEkfknSOpCHp86EkX6ufKiK7qcBZkgZL6kPytTqfXwETJO2RlvtBSZsNLyuTvGVL+oik/dO+4VXAGqC+gDzvB3aRdJKkLpJOAHYn6abY1FTgG5KGpNcEzmsm3+lpPb4tqWvahzwauK2QN5peHK0h6bdHUo2kbunjPpIOS9O6SPoCSf/8A+nh9wB7SvpcmseFwPPpP3OrIg7UHW8lsD8wXdIqkgA9CziniLyuI7no9TzwT5LgVUcTgS4i7gEuB26TtCIt84hi3kBrtVB2b5L3sZyki2EpcEUBeS4luUB4TnrMt4GjImJJE7tfRxIMZwLPkgyBy5fvOuDotH5LgGuBL7ciWG4PrOY/reDVQONY6a7AJcDiNO8zgWMi4uW07MXA50gumC4n+ZyMLbBc24LINw7Yckk6AvhVRHiShFkFc4t6C6JkOvOR6dfowcBEkq/PZlbBWgzUkm5IJx/MyknrJ+khJZMuHsod+ytpgqS5kl6WdFjTuVqZiGSM8XKSro85JP2aZlYm6TWGpyXNTCdEXZymlyxOttj1IelgkmFCN0fEnmnaj4BlEXGZpPOAvhHxHUm7k0xc2I9kWNPDwC4RUcjFIDOziiNJQM+IeC+9CP5X4CySCV0liZMttqgj4i8k41xzjQGmpI+n8J8ZcGOA29IxofNIZo7tV9C7NTOrQJF4L33aNd2CEsbJYhfIGdg4WywiFkjaJk0fzMbDymrJMzFA0nhgPEBnOu/bY7Nx/WZmm1vJ8iURsXVb8jjsEz1j6bLCvujPeH7tbJJhoo0mR8Tk3H0kdQZmADsDv4iI6ZLaHCcblXolMzWR1mTfSvpGJwP0Vr/YX4eWuCpmtiV6OO5sbiZpQZYsq2f6A0Na3hHoOui1NRExqrl90m6Lken8hXu08UJimyo4TjYqdtTHQkmDANKfi9L0Wjae8TWEZGaXmVmGBPXRUNDWqlwj3gUeBw6nhHGy2EA9DRiXPh4H3JuTPlZSN0nDgOHA00WWYWZWFgE0EAVtLZG0ddqSRlJ34FMkC4KVLE622PUh6VbgEGCApFqSsbmXAVOVrL/7FnAcQETMljQVeJFkRtzpHvFhZlnUQOtay80YBExJ+6k7AVMj4veS/k6J4mSLgToi8i3n2GSnckRMIl0j2KytevbtwfETRzNo561Rp6a69mxLFA3BgrmLmXrxfaxa/n7p8ydY38pujbx5JTf52LuJ9KWUKE5uabdFsi3M8RNHs8d+H6KmSw1q8hqMbYmCoF+//hw/EW48u/R3HwugvoBujaxwoLZMG7Tz1g7SVUiImi41DNq5TaPwmlVI/3NWOFBbpqmTHKSrlFDZursCqK+gBekcqM2sKpXsUmI78Op5Zi3Ybf9dGHPSaD5z/OEcfdJR3HjL9TQ0NP9nXju/lvv+NK3oMu++7y4WLl7YqmNq59dy1AmbLyleO7+WDx+0B2NOGr1hW7d+XbvUKauCoL7ALQvcojZrQU23Gu793X0ALF22lHO++01WvreSb5x2dt5j3llQy+8fuI/Rhx9dVJn3/P4uhu+0CwO3bu4ex4XbbvB2G95DsYqpU11dHV26ZC/MRMD6bMTggmTvDJq1wVZ/nMaAa6+gy8IF1A0cxJKvn8vKI4oLlk3p368/Pzj/Ej5/8rGcOf4sGhoauOLnP+bpGdNZt34dXzjui4w99kSu/PmPeW3ea4w5aTSfPeqzfOmEcU3uB3DdzZOZdv//Q506cfB/H8yeu+/FrDmzOPd736KmWw2333AHc+fN5bKrJ/H+6vfp26cvl078EdsM2IZZc2Zx/g/Oo3tNDfuMaHaW82b++tQT/GzyNaxbt46hQ7bj0gsvp2ePnvz8up/x2BOPsnbtGvb+8D58//xLeODRP21WpyOPP4w7b76Hfn368cKLL/Cjay7lN7/+HT+bfA2LFi/inQW19O3TjwvO+S4TL72Q+f9KJt+df8532XfEvjw9YzqTrrwEAAl+O/lWevXc9J7I5SLqK+jahwO1bTG2+uM0Bv7wfDqtSdbP6fqv+Qz84fkAJQ3WQ4dsR0NDA0uXLeWRPz/MVr224q6b72HdurWM/Z8TOHD/gzjnjP/lht9ez6+vvg6A2+++rcn9Xn/jdR55/CGm3nQX3Wu68+6/36XPB/twy9Tf8O2zJrDX7nuxvm49l/z4Yq698lf069uf+x/8A1dfexWXXngZE77/Hb537oXst+/+XH7NZXnr/NY7bzHmpNEA7DNiH8487Sx+ecO13PiLm+nRvQeTp/yaG2+5gTO+eiZfPP5LnPHVMwH43wvP4bEnHuXwQ4/YqE4tmf3SLH533e3U1NRwzne/ybiTTmHUyFHM/9d8Tj3zFP54xwPc8Nv/48LvXMS+I/Zl1fur6PaBbiX47RQmgAa3qM3a34Brr9gQpBt1WrOGAddeUdJADdC4jvuT05/g5bkv88AjfwJg5aqVvPn2G3Tt2nWj/fPt9/enn+TY0Z+je013APp8sM9mZc17Yx6vvP4Kp5x+MgANDfVsPWBrVr63kpUrV7DfvvsDMObIY3jib39usr6bdn089sSjzH19LieeegIA6+vWMXKvZM7G9BlP8X83X8eaNat5d8W/Gb7jcD55cOsWTfvkwYdSU1MDwN+efpK5r8/d8Np7q97jvVXvsc+Ifbns6h8y+vCj+fQnPk3PgYNaVUZbuUVt1gG6LFzQqvRivV37Fp07d6Z/v/5EwHfPvZCP/ffBG+0zfcbGN5HPt98Tf/8Lybrz+QXB8B2Hc/sNd26UvmLlihaPzZtnBAfufyBXTfrJRulr167l4ssncteUexj0X9vys8nXsHbd2ibz6Ny5M5E2Szfdp3tNjw2PGxqC22+4Y0PgbjT+5K/x8YM+wZ+ffJzjv/J5bvzFzey0w05FvZ/WSia8VE6g9qgP22LU5WmR5UsvxrLlS5l42ff4wnFfRBIHHfAxbr3rd6yvWw/AvDfn8f7q9+nZoxerVr234bh8+x24/0HcNe1OVq9ZDcC7/34XgJ49erLq/eT4YdsPY9nyZfzz+WcBWF+3nldfe4XeW/WmV6+teOa5ZwBaNcpk5F4jeXbmDN58+w0AVq9Zzbw3520IuH379GPV+6s2fAPYtE4AgwcNYdac5A59Dz76n/02ddABB/HbO36z4fmcl18E4K3aN9l1510ZP+409txtL+a98XrB9W+rANZHp4K2LHCL2rYYS75+7kZ91AANNTUs+fq5bcp3zdo1jDlpNHV16+ncpQtjjjiGU77wFQCOO+Z43llQy7FfHENE0LdvP6694lfsOnxXOnfuwtEnHcWxRx3Ll8ee3OR+B3/047z0yhw+9+Vj6NrlA3z8wI/zrdPP5bOjP8fESy/ccOHup5f9nEuu/AEr31tJfV0d4048meE77cKlF16+4WLiQQd8rOD31K9vfy6d+CO+dcE3NwzVO/tr32TY9sM47pgTGH3ikQweNIS9dv/whmM2rdMZXz2TCy6ZwK9v+iUj9hiRt6wLzv0e37/8Ikaf+Bnq6+sYtfd+fH/CD5hy601Mf+YpOnXuzM7Ddubgjx6cN49SC0R9BbVTW7xnYnvwjQMsnwvuP5NtBzR784uNlHvUh7Wv+UveYdKRP9so7eG4c0ZLC/m3ZLcPd4ub7tu2oH0P2OGNNpfXVm5R2xZl5RFHOzBbiyqtj9qB2syqkKjPSP9zIRyoLdOiIQjCCzNVoSA2jCopfd7QUEF91A7UlmkL5i6mX7/+Xuq0ygTBmro1LJi7uDz5h1gXncuSdzk4UFumTb34Po6fiO/wUmVy7/BSLg0V9I/fgdoybdXy98tyhw+rbsnFRHd9mJllmC8mmpllmi8mmplVgPpwH7WZWWYFYn1UTvirnJqamZWILyaamWVcIHd9mJllnS8mmpllWAQentduDsi/Bq6ZZcRTMzu6BptJLiaWZgq5pKHAzcB/AQ3A5Ii4RtJFwFeBxnnw50fE/ekxE4BTgXrgGxHxQHNlVHagNjMrUgkvJtYB50TEs5K2AmZIeih97eqIuCJ3Z0m7A2OBPYBtgYcl7RIR9fkKcKA2s6oTiIYSXUyMiAXAgvTxSklzgObudjEGuC0i1gLzJM0F9gP+nu+AyumkMTMroXo6FbS1hqQdgL2B6WnSGZKel3SDpL5p2mDg7ZzDamk+sDtQm1n1CaAhOhW0AQMkPZOzjW8qT0m9gLuAsyNiBfBLYCdgJEmL+8rGXfNUKS93fZhZFVJrbsW1pKV7JkrqShKkb4mIuwEiYmHO69cBv0+f1gJDcw4fAsxvLn+3qM2s6gSwPjoXtLVEkoDrgTkRcVVO+qCc3T4LzEofTwPGSuomaRgwHHi6uTLcojazqhOhxm6NUjgQ+BLwgqTn0rTzgRMljST5v/AGcFpSdsyWNBV4kWTEyOnNjfiANgZqSd8E/ietyAvAKUAP4HZgh7Ryx0fE8raUY2ZWaqWa8BIRf6Xpfuf7mzlmEjCp0DKKrqmkwcA3gFERsSfQmWRs4HnAIxExHHgkfW5mlhnJetQqaMuCtv5L6QJ0l9SFpCU9n2SM4JT09SnAMW0sw8ysxJI7vBSyZUHRXR8R8Y6kK4C3gNXAgxHxoKSB6QBwImKBpG2aOj4d4jIeoIYexVaj1ZaO6NluZW2J+s9c1dFVMGuzZHheNlrLhSg6UKeDt8cAw4B3gTskfbHQ4yNiMjAZoLf6NTuG0MyslEq51kd7aMvFxE8B8yJiMYCku4GPAgslDUpb04OARSWop5lZSVXSMqdtqelbwAGSeqTjCA8F5pCMERyX7jMOuLdtVTQzK61kmVMVtGVBW/qop0u6E3iWZCzgP0m6MnoBUyWdShLMjytFRc3MSqkq+qgBImIiMHGT5LUkrWszs0xKVs+rnK4Pz0w0s6qTTCF3oDYzyzC3qM3MMi8rsw4L4UBtZlWncdRHpXCgNrOq5K4PK1qXMYtb3qkd1N27dUdXwaxsSnnPxPbgQG1mVSeAOreozcyyzV0fZmZZFu76MDPLtMYbB1QKB2ozq0puUZuZZVjV3DjAzKxSBaKuwRcTzcwyzX3UZmZZFu76MDPLNPdRW9k8NfLOkuZ3wHOfL2l+ZpXEgdrMLMMCUe+LiWZm2eaLiWZmGRYVdjGxctr+ZmYlFKGCtpZIGirpMUlzJM2WdFaa3k/SQ5JeTX/2zTlmgqS5kl6WdFhLZThQm1kVShZlKmQrQB1wTkTsBhwAnC5pd+A84JGIGA48kj4nfW0ssAdwOHCtpM7NFeBAbWZVqVQt6ohYEBHPpo9XAnOAwcAYYEq62xTgmPTxGOC2iFgbEfOAucB+zZXhPmozqzoRUN9QcB/1AEnP5DyfHBGTm9pR0g7A3sB0YGBELEjKiwWStkl3Gww8lXNYbZqWlwO1mVWlVoz6WBIRo1raSVIv4C7g7IhYIeXNv6kXorm83fVhZlUnKF3XB4CkriRB+paIuDtNXihpUPr6IGBRml4LDM05fAgwv7n8HajNrAqV7mKikqbz9cCciLgq56VpwLj08Tjg3pz0sZK6SRoGDAeebq4Md32YWVWKZjsbWuVA4EvAC5KeS9POBy4Dpko6FXgLOC4pN2ZLmgq8SDJi5PSIqG+uAAdqM6tKhXZrtJxP/JWm+50BDs1zzCRgUqFlOFCbWdVJRn1UTs9v5dTUmnf3CvSReWjbV9FH5sHdKzq6RmaZFlHYlgVuUW8J7l6Bzl2EVqefqto6OHdRMt7n2N4dWTOzzCpV10d7cIt6C6BLl/4nSDemrQ506dIOqpFZtgWFDc3LSjB3i3pL8E5d69LNrPkZJhnTpha1pD6S7pT0Urpy1H83t2KUlcngPP9v86WbVbuAaFBBWxa0tevjGuBPEfEhYATJYiRNrhhl5RMT+hPdN/5ARXcRE/p3UI3Msq+Suj6KDtSSegMHk8zIISLWRcS75F8xysrl2N7EFdsQQ7oQIvl5xTa+kGjWjGoZ9bEjsBi4UdIIYAZwFvlXjNqIpPHAeIAaerShGgYkwdqBuSyWjujZ0VVoV/1nruroKpRd41oflaItXR9dgH2AX0bE3sAqWtHNERGTI2JURIzqSrc2VMPMrJUCkq+fBWwZ0JZAXQvURsT09PmdJIE734pRZmaZUUldH0UH6oj4F/C2pF3TpENJFhnJt2KUmVlGFDbiIyujPto6futM4BZJHwBeB04hCf6brRhlZpYpGWktF6JNgToingOauvNBkytGmZllQlTWxUTPiDCz6lQtLWozs8rlFrWZWbY1dHQFCudAbWbVp3EcdYVwoDazqpSVMdKFcKCuIAc89/mOroLZlsOB2sws49z1YWaWbXKL2swsw0KQkenhhXCgNrPq5Ba1mVnGOVCbmWWcA7WZWYZV2ISXtt7c1sysIikK21rMR7pB0iJJs3LSLpL0jqTn0u3InNcmSJor6WVJhxVSVwdqM6tOUeDWspuAw5tIvzoiRqbb/QCSdgfGAnukx1wrqXNLBThQm1lVKlWLOiL+AiwrsNgxwG0RsTYi5gFzgf1aOsh91BlTd+/WHV0Fs+pQeB/1AEnP5DyfHBGTCzjuDElfBp4BzomI5cBg4KmcfWrTtGa5RW1m1afQbo+kRb0kIkblbIUE6V8COwEjgQXAlWl6U/8dWmy3O1CbWXUqXR/15llHLIyI+ohoAK7jP90btcDQnF2HAPNbys+B2syqkhoK24rKWxqU8/SzQOOIkGnAWEndJA0DhgNPt5Sf+6jNrDqVaMKLpFuBQ0j6smuBicAhkkampbwBnAYQEbMlTQVeBOqA0yOivqUyHKjNrOoUOqKjEBFxYhPJ1zez/yRgUmvKcKA2s+pUQTMTHajNrDp5rQ8zs2zzjQPMzLIsih/R0REcqM2sOrlFbWaWcQ7UZmbZVkl91J6ZaGaWcW5Rm1l1qqAWtQO1mVUfj/owM6sAblGbmWWXqKyLiQ7UZladKihQt3nUh6TOkv4p6ffp836SHpL0avqzb9uraWZWQgXeLzErre5SDM87C5iT8/w84JGIGA48kj43M8uWhgK3DGhToJY0BPgM8H85yWOAKenjKcAxbSnDzKwcKqlF3dY+6p8A3wa2ykkbGBELACJigaRtmjpQ0nhgPEANPdpYjfLrMmZxq4/xHcW3DP1nruroKlg5ZCQIF6LoFrWko4BFETGjmOMjYnLjXX270q3YapiZtV7r7kLe4drSoj4QOFrSkUAN0FvSb4GFkgalrelBwKJSVNTMrJSy0q1RiKJb1BExISKGRMQOwFjg0Yj4Islddselu40D7m1zLc3MSq1KWtT5XAZMlXQq8BZwXBnKMDNrk6qbQh4RjwOPp4+XAoeWIl8zs7LIUGu5EJ6ZaGZVR+lWKRyozaw6uUVtZpZtlTTqw4G6SIc9OpuvT3mcgYtXsHDr3lw77hAe+OQeHV0tMyuUA/WW7bBHZ3P+T++n+9o6AAYtWsH5P70fwMHarBJU2I0DfM/EInx9yuMbgnSj7mvr+PqUxzumQmbWeiUaRy3pBkmLJM3KScu7iqikCZLmSnpZ0mGFVNWBuggDF69oVbqZZU8JF2W6CTh8k7QmVxGVtDvJBME90mOuldS5pQIcqIuwcOverUo3swwqUYs6Iv4CLNskOd8qomOA2yJibUTMA+YC+7VUhgN1Ea4ddwiru23cvb+6WxeuHXdIx1TIzFqtFS3qAZKeydnGF5D9RquIAo2riA4G3s7ZrzZNa5YvJhah8YKhR32YVaigNTcFWBIRo0pUclPzbFpstztQF+mBT+7hwGxWodrh5rb5VhGtBYbm7DcEmN9SZu76MLPqVN7V8/KtIjoNGCupm6RhwHDg6ZYyc4vazKqSojRNakm3AoeQ9GXXAhPJs4poRMyWNBV4EagDTo+I+pbKcKA2s+pTwtXzIuLEPC81uYpoREwCJrWmDAdqM6tKXuvDzCzjKmkKuQN1gXxHcbMtjFvUZmYZVvj08ExwoDaz6uRAbWaWXe0w4aWkHKjNrCqpoXIitQO1mVUf34XczCz7PDzPzCzr3KI2M8s2X0w0M8uyAEq0KFN7cKA2s6rkPmozswzzOGozs6yLcNeHmVnWuUVtZpZ1DtRmZtnmFrWZWZYFUF85kdqB2syqUiW1qDsVe6CkoZIekzRH0mxJZ6Xp/SQ9JOnV9Gff0lXXzKxEGkd+tLRlQNGBmuRW5+dExG7AAcDpknYHzgMeiYjhwCPpczOzTFEUtmVB0YE6IhZExLPp45XAHGAwMAaYku42BTimjXU0MyutaMWWASXpo5a0A7A3MB0YGBELIAnmkrbJc8x4YDxADT1KUY2C9J+5qt3KMrNsEqBqupgoqRdwF3B2RKyQVNBxETEZmAzQW/0q54yZ2RZBGel/LkRb+qiR1JUkSN8SEXenyQslDUpfHwQsalsVzcxKrMK6Ptoy6kPA9cCciLgq56VpwLj08Tjg3uKrZ2ZWDgWO+MhIq7stXR8HAl8CXpD0XJp2PnAZMFXSqcBbwHFtqqGZWRmUckSHpDeAlUA9UBcRoyT1A24HdgDeAI6PiOXF5F90oI6Iv5L0yTfl0GLzNTNrF6VvLX8iIpbkPG8cqnyZpPPS598pJuM29VGbmVWkSEZ9FLK1QcmGKjtQm1l1Ku3FxAAelDQjHXoMmwxVBpocqlwIr/VhZlWpFcPzBkh6Juf55HR4ca4DI2J+Om/kIUkvlaSSKQdqM6tOhQfqJRExqvmsYn76c5Gke4D9SIcqpxP/2jRU2V0fZlZ9AmgocGuBpJ6Stmp8DHwamEUJhypXdov6qZkdXQMzq0AiSjkzcSBwTzoruwvwu4j4k6R/UKKhypUdqM3MitVQQHO5ABHxOjCiifSllGiosgO1mVWfxq6PCuFAbWZVqZIWZXKgNrPq5EBtZpZl2VlwqRAO1GZWfXwXcjOz7HMftZlZ1jlQm5llWAANDtRmZhnmi4lmZtnnQG1mlmEB1FfO1EQHajOrQgHhQG1mlm3u+jAzyzCP+jAzqwBuUZuZZZwDtZlZhkVAfX1H16JgDtRmVp3cojYzyzgHajOzLAuP+jAzy7SA8IQXM7OM8xRyM7MMi4AGB2ozs2zzxUQzs2wLt6jNzLLMNw4wM8s2L8pkZpZtAUQFTSHvVK6MJR0u6WVJcyWdV65yzMxaLdIbBxSytaA9Yl1ZArWkzsAvgCOA3YETJe1ejrLMzIoRDVHQ1pz2inXlalHvB8yNiNcjYh1wGzCmTGWZmbVeaVrU7RLrytVHPRh4O+d5LbB/7g6SxgPj06drH447Z5WpLq0xAFjiOgDZqEcW6gDZqEcW6gDZqMeubc1gJcsfeDjuHFDg7jWSnsl5PjkiJqePW4x1pVCuQK0m0jb6DpG+0ckAkp6JiFFlqkvBslCPLNQhK/XIQh2yUo8s1CEr9dgkaBYlIg4vRV0oINaVQrm6PmqBoTnPhwDzy1SWmVlHaZdYV65A/Q9guKRhkj4AjAWmlaksM7OO0i6xrixdHxFRJ+kM4AGgM3BDRMxu5pDJzbzWnrJQjyzUAbJRjyzUAbJRjyzUAbJRjyzUASgq1hVFUUHTKM3MqlHZJryYmVlpOFCbmWVchwfqjphqLmmopMckzZE0W9JZafpFkt6R9Fy6HdkOdXlD0gtpec+kaf0kPSTp1fRn3zKWv2vO+31O0gpJZ7fHuZB0g6RFkmblpOV975ImpJ+TlyUdVsY6/FjSS5Kel3SPpD5p+g6SVueck1+Vog7N1CPv76Adz8XtOeW/Iem5NL0s56KZv812/VxkTkR02EbS+f4asCPwAWAmsHs7lDsI2Cd9vBXwCsn0z4uAc9v5HLwBDNgk7UfAeenj84DL2/H38S9g+/Y4F8DBwD7ArJbee/r7mQl0A4aln5vOZarDp4Eu6ePLc+qwQ+5+7XAumvwdtOe52OT1K4ELy3kumvnbbNfPRda2jm5Rd8hU84hYEBHPpo9XAnNIZhhlxRhgSvp4CnBMO5V7KPBaRLzZHoVFxF+AZZsk53vvY4DbImJtRMwD5pJ8fkpeh4h4MCLq0qdPkYyNLas85yKfdjsXjSQJOB64ta3ltFCHfH+b7fq5yJqODtRNTb9s14ApaQdgb2B6mnRG+pX3hnJ2OeQI4EFJM9Jp9QADI2IBJB9cYJt2qAckY0Bz/xDb+1xA/vfeUZ+VrwB/zHk+TNI/Jf1Z0sfaofymfgcdcS4+BiyMiFdz0sp6Ljb528za56JddXSgbpfpl3kLl3oBdwFnR8QK4JfATsBIYAHJV71yOzAi9iFZfet0SQe3Q5mbSQfrHw3ckSZ1xLloTrt/ViRdANQBt6RJC4DtImJv4FvA7yT1LmMV8v0OOuLv5kQ2/ide1nPRxN9m3l2bSNvixhx3dKDusKnmkrqSfBBuiYi7ASJiYUTUR0QDcB3t8BUqIuanPxcB96RlLpQ0KK3nIGBRuetB8o/i2YhYmNan3c9FKt97b9fPiqRxwFHAFyLtDE2/Xi9NH88g6Q/dpVx1aOZ30N7nogtwLHB7Tt3Kdi6a+tskI5+LjtLRgbpDppqn/W3XA3Mi4qqc9EE5u30WKOuKfpJ6Stqq8THJRaxZJOdgXLrbOODectYjtVGLqb3PRY58730aMFZSN0nDgOHA0+WogKTDge8AR0fE+znpWytZfxhJO6Z1eL0cdUjLyPc7aLdzkfoU8FJE1ObUrSznIt/fJhn4XHSojr6aCRxJcmX3NeCCdirzIJKvR88Dz6XbkcBvgBfS9GnAoDLXY0eSK9YzgdmN7x/oDzwCvJr+7FfmevQAlgIfzEkr+7kg+cewAFhP0jI6tbn3DlyQfk5eBo4oYx3mkvR7Nn42fpXu+7n09zQTeBYYXeZzkfd30F7nIk2/CfjaJvuW5Vw087fZrp+LrG2eQm5mlnEd3fVhZmYtcKA2M8s4B2ozs4xzoDYzyzgHajOzjHOgNjPLOAdqM7OM+/8EWPgSvae55QAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50, 100]\n", + "n_min_threshold = 4\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature.\n", + "# All filtering is off in this example, although that is not usually recommended.\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center', sigma_threshold=0,\n", + " n_min_threshold=n_min_threshold)\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"n_min_threshold={0}\".format(n_min_threshold))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This gives us two detected features with minimum values >150. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Multiple Thresholds\n", + "Now let's say that you want to detect all three maxima within this feature. You may want to do this, if, for example, you were trying to detect overhshooting tops within a cirrus shield. You could pick a single threshold, but if you pick 100, you won't separate out the two features on the left. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [100, ]\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center')\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"Single Threshold of 100\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the power of having multiple thresholds. We can set thresholds of 50, 100, 150, 200 and capture both:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50, 100, 150, 200]\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center')\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"Thresholds: [50, 100, 150, 200]\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:tobac_stable]", + "language": "python", + "name": "conda-env-tobac_stable-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.5" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "25a19fbe0a9132dfb9279d48d161753c6352f8f9478c2e74383d340069b907c3" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/feature_detection.rst b/doc/feature_detection_overview.rst similarity index 100% rename from doc/feature_detection.rst rename to doc/feature_detection_overview.rst diff --git a/doc/index.rst b/doc/index.rst index 5ce18234..4c677640 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -8,7 +8,7 @@ tobac - Tracking and Object-Based Analysis of Clouds The software is set up in a modular way to include different algorithms for feature identification, tracking, and analyses. **tobac** is also input variable agnostic and doesn't rely on specific input variables, nor a specific grid to work. -In the current implementation, individual features are identified as either maxima or minima in a two-dimensional time-varying field (see :doc:`feature_detection`). An associated volume can then be determined using these features with a separate (or identical) time-varying 2D or 3D field and a threshold value (see :doc:`segmentation`). The identified objects are linked into consistent trajectories representing the cloud over its lifecycle in the tracking step. Analysis and visualization methods provide a convenient way to use and display the tracking results. +In the current implementation, individual features are identified as either maxima or minima in a two-dimensional time-varying field (see :doc:`feature_detection_overview`). An associated volume can then be determined using these features with a separate (or identical) time-varying 2D or 3D field and a threshold value (see :doc:`segmentation`). The identified objects are linked into consistent trajectories representing the cloud over its lifecycle in the tracking step. Analysis and visualization methods provide a convenient way to use and display the tracking results. Version 1.2 of tobac and some example applications are described in a manuscript in Geoscientific Model Development as: @@ -30,7 +30,9 @@ The project is currently being extended by several contributors to include addit :caption: Feature Detection :maxdepth: 2 - feature_detection + feature_detection_overview + threshold_detection_parameters + feature_detection/index feature_detection_output .. toctree:: diff --git a/doc/threshold_detection_parameters.rst b/doc/threshold_detection_parameters.rst new file mode 100644 index 00000000..0250a8e0 --- /dev/null +++ b/doc/threshold_detection_parameters.rst @@ -0,0 +1,38 @@ +Threshold Feature Detection Parameters +-------------------------------------- + +The proper selection of parameters used to detect features with the *tobac* multiple threshold feature detection is a critical first step in using *tobac*. This page describes the various parameters available and provides broad comments on the usage of each parameter. + +A full list of parameters and descriptions can be found in the API Reference: :py:meth:`tobac.feature_detection.feature_detection_multithreshold` + +========================= +Basic Operating Procedure +========================= +The *tobac* multiple threshold algorithm searches the input data (`field_in`) for contiguous regions of data greater than (with `target='maximum'`, see `Target`_) or less than (with `target='minimum'`) the selected thresholds (see `Thresholds`_). Contiguous regions (see `Minimum Threshold Number`_) are then identified as individual features, with a single point representing their location in the output (see `Position Threshold`_). Using this output (see :doc:`feature_detection_output`), segmentation (:doc:`segmentation`) and tracking (:doc:`linking`) can be run. + +.. _Target: +====== +Target +====== +First, you must determine whether you want to feature detect on maxima or minima in your dataset. For example, if you are trying to detect clouds in IR satellite data, where clouds have relatively lower brightness temperatures than the background, you would set `target='minimum'`. If, instead, you are trying to detect clouds by cloud water in model data, where an increase in mixing ratio indicates the presence of a cloud, you would set `target='maximum'`. The `target` parameter will determine the selection of many of the following parameters. + +.. _Thresholds: +========== +Thresholds +========== +You can select to feature detect on either one or multiple thresholds. The first threshold (or the single threshold) sets the minimum magnitude (either lowest value for `target='maximum'` or highest value for `target='minimum'`) that a feature can be detected on. For example, if you have a field made up of values lower than `10`, and you set `target='maximum', threshold=[10,]`, *tobac* will detect no features. + +Including *multiple* thresholds will allow *tobac* to refine the detection of features and detect multiple features that are connected through a contiguous region of less restrictive threshold values. You can see a conceptual diagram of that here: :doc:`feature_detection_overview`. To examine how setting different thresholds can change the number of features detected, see the example in this notebook: :doc:`feature_detection/notebooks/multiple_thresholds_example`. + + +.. _Minimum Threshold Number: +======================== +Minimum Threshold Number +======================== +The minimum number of points per threshold, set by `n_min_threshold`, determines how many contiguous pixels are required to be above the threshold for the feature to be detected. Setting this point very low can allow extraneous points to be detected as erroneous features, while setting this value too high will cause some real features to be missed. The default value for this parameter is `0`, which will cause any values greater than the threshold after filtering to be identified as a feature. You can see a demonstration of the affect of increasing `n_min_threshold` at: :doc:`feature_detection/notebooks/n_min_threshold_example`. + +.. _Position Threshold: +================ +Feature Position +================ +There are several ways of calculating \ No newline at end of file From 9c1e1d21a382e3438d634dc3db5977fe69d0502a Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sun, 3 Jul 2022 17:07:47 -0600 Subject: [PATCH 076/187] Cleaned up formatting Note that restructuredtext requires code to be marked with :code: --- .../notebooks/n_min_threshold_example.ipynb | 52 +++++++++---------- doc/installation.rst | 11 +--- doc/threshold_detection_parameters.rst | 8 +-- 3 files changed, 30 insertions(+), 41 deletions(-) diff --git a/doc/feature_detection/notebooks/n_min_threshold_example.ipynb b/doc/feature_detection/notebooks/n_min_threshold_example.ipynb index 3fc6b41c..6bf13f5b 100644 --- a/doc/feature_detection/notebooks/n_min_threshold_example.ipynb +++ b/doc/feature_detection/notebooks/n_min_threshold_example.ipynb @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -79,7 +79,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -99,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -134,7 +134,7 @@ "metadata": {}, "source": [ "#### Increasing `n_min_threshold`\n", - "As we increase `n_min_threshold`, fewer of these separate features are detected. " + "As we increase `n_min_threshold`, fewer of these separate features are detected. In this example, if we set `n_min_threshold` to 5, the smallest detected feature goes away. " ] }, { @@ -144,7 +144,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -157,7 +157,7 @@ ], "source": [ "thresholds = [50, 100]\n", - "n_min_threshold = 4\n", + "n_min_threshold = 5\n", "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature.\n", "# All filtering is off in this example, although that is not usually recommended.\n", "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center', sigma_threshold=0,\n", @@ -175,15 +175,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This gives us two detected features with minimum values >150. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Multiple Thresholds\n", - "Now let's say that you want to detect all three maxima within this feature. You may want to do this, if, for example, you were trying to detect overhshooting tops within a cirrus shield. You could pick a single threshold, but if you pick 100, you won't separate out the two features on the left. For example:" + "If we increase `n_min_threshold` to 20, only the large 50-valued feature is detected, rather than the two higher-valued squares." ] }, { @@ -193,7 +185,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -205,16 +197,18 @@ } ], "source": [ - "thresholds = [100, ]\n", - "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature\n", - "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center')\n", + "thresholds = [50, 100]\n", + "n_min_threshold = 20\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature.\n", + "# All filtering is off in this example, although that is not usually recommended.\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center', sigma_threshold=0,\n", + " n_min_threshold=n_min_threshold)\n", "plt.pcolormesh(input_field_arr[0])\n", "plt.colorbar()\n", - "\n", "# Plot all features detected\n", "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", "plt.legend()\n", - "plt.title(\"Single Threshold of 100\")\n", + "plt.title(\"n_min_threshold={0}\".format(n_min_threshold))\n", "plt.show()" ] }, @@ -222,7 +216,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This is the power of having multiple thresholds. We can set thresholds of 50, 100, 150, 200 and capture both:" + "If we set `n_min_threshold` to 100, only the largest feature is detected." ] }, { @@ -232,7 +226,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -244,16 +238,18 @@ } ], "source": [ - "thresholds = [50, 100, 150, 200]\n", - "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature\n", - "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center')\n", + "thresholds = [50, 100]\n", + "n_min_threshold = 100\n", + "# Using 'center' here outputs the feature location as the arithmetic center of the detected feature.\n", + "# All filtering is off in this example, although that is not usually recommended.\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold='center', sigma_threshold=0,\n", + " n_min_threshold=n_min_threshold)\n", "plt.pcolormesh(input_field_arr[0])\n", "plt.colorbar()\n", - "\n", "# Plot all features detected\n", "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", "plt.legend()\n", - "plt.title(\"Thresholds: [50, 100, 150, 200]\")\n", + "plt.title(\"n_min_threshold={0}\".format(n_min_threshold))\n", "plt.show()" ] } diff --git a/doc/installation.rst b/doc/installation.rst index 6ea63479..92f0d098 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -4,18 +4,11 @@ tobac works with Python 3 installations. The easiest way is to install the most recent version of tobac via conda or mamba and the conda-forge channel: - ``conda install -c conda-forge tobac`` - -or - ``mamba install -c conda-forge tobac`` +:code:`conda install -c conda-forge tobac` or :code:`mamba install -c conda-forge tobac` This will take care of all necessary dependencies and should do the job for most users. It also allows for an easy update of the installation by - ``conda update -c conda-forge tobac`` - -or - - ``mamba update -c conda-forge tobac`` +:code:`conda update -c conda-forge tobac` :code:`mamba update -c conda-forge tobac` You can also install conda via pip, which is mainly interesting for development purposes or using specific development branches for the Github repository. diff --git a/doc/threshold_detection_parameters.rst b/doc/threshold_detection_parameters.rst index 0250a8e0..b032ef47 100644 --- a/doc/threshold_detection_parameters.rst +++ b/doc/threshold_detection_parameters.rst @@ -14,13 +14,13 @@ The *tobac* multiple threshold algorithm searches the input data (`field_in`) fo ====== Target ====== -First, you must determine whether you want to feature detect on maxima or minima in your dataset. For example, if you are trying to detect clouds in IR satellite data, where clouds have relatively lower brightness temperatures than the background, you would set `target='minimum'`. If, instead, you are trying to detect clouds by cloud water in model data, where an increase in mixing ratio indicates the presence of a cloud, you would set `target='maximum'`. The `target` parameter will determine the selection of many of the following parameters. +First, you must determine whether you want to feature detect on maxima or minima in your dataset. For example, if you are trying to detect clouds in IR satellite data, where clouds have relatively lower brightness temperatures than the background, you would set :code:`target='minimum'`. If, instead, you are trying to detect clouds by cloud water in model data, where an increase in mixing ratio indicates the presence of a cloud, you would set :code:`target='maximum'`. The :code:`target` parameter will determine the selection of many of the following parameters. .. _Thresholds: ========== Thresholds ========== -You can select to feature detect on either one or multiple thresholds. The first threshold (or the single threshold) sets the minimum magnitude (either lowest value for `target='maximum'` or highest value for `target='minimum'`) that a feature can be detected on. For example, if you have a field made up of values lower than `10`, and you set `target='maximum', threshold=[10,]`, *tobac* will detect no features. +You can select to feature detect on either one or multiple thresholds. The first threshold (or the single threshold) sets the minimum magnitude (either lowest value for :code:`target='maximum'` or highest value for :code:`target='minimum'`) that a feature can be detected on. For example, if you have a field made up of values lower than :code:`10`, and you set :code:`target='maximum', threshold=[10,]`, *tobac* will detect no features. Including *multiple* thresholds will allow *tobac* to refine the detection of features and detect multiple features that are connected through a contiguous region of less restrictive threshold values. You can see a conceptual diagram of that here: :doc:`feature_detection_overview`. To examine how setting different thresholds can change the number of features detected, see the example in this notebook: :doc:`feature_detection/notebooks/multiple_thresholds_example`. @@ -29,10 +29,10 @@ Including *multiple* thresholds will allow *tobac* to refine the detection of fe ======================== Minimum Threshold Number ======================== -The minimum number of points per threshold, set by `n_min_threshold`, determines how many contiguous pixels are required to be above the threshold for the feature to be detected. Setting this point very low can allow extraneous points to be detected as erroneous features, while setting this value too high will cause some real features to be missed. The default value for this parameter is `0`, which will cause any values greater than the threshold after filtering to be identified as a feature. You can see a demonstration of the affect of increasing `n_min_threshold` at: :doc:`feature_detection/notebooks/n_min_threshold_example`. +The minimum number of points per threshold, set by :code:`n_min_threshold`, determines how many contiguous pixels are required to be above the threshold for the feature to be detected. Setting this point very low can allow extraneous points to be detected as erroneous features, while setting this value too high will cause some real features to be missed. The default value for this parameter is :code:`0`, which will cause any values greater than the threshold after filtering to be identified as a feature. You can see a demonstration of the affect of increasing :code:`n_min_threshold` at: :doc:`feature_detection/notebooks/n_min_threshold_example`. .. _Position Threshold: ================ Feature Position ================ -There are several ways of calculating \ No newline at end of file +There are four ways of calculating the single point used to represent feature center: arithmetic center, extreme point, weighted differencing, and weighted absolute differencing. \ No newline at end of file From 98271d870f8f93f9f69cce2b923efe39dd5c1797 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sun, 3 Jul 2022 17:52:12 -0600 Subject: [PATCH 077/187] New position_threshold notebook --- .../position_threshold_example.ipynb | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 doc/feature_detection/notebooks/position_threshold_example.ipynb diff --git a/doc/feature_detection/notebooks/position_threshold_example.ipynb b/doc/feature_detection/notebooks/position_threshold_example.ipynb new file mode 100644 index 00000000..558a51a0 --- /dev/null +++ b/doc/feature_detection/notebooks/position_threshold_example.ipynb @@ -0,0 +1,274 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Different `threshold_position` options" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import tobac\n", + "import xarray as xr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Generate Feature Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we will generate some simple feature data where the features that we want to detect are *higher* values than the surrounding (0)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Dimensions here are time, y, x.\n", + "input_field_arr = np.zeros((1,100,200))\n", + "input_field_arr[0, 15:85, 10:185]=50\n", + "input_field_arr[0, 20:80, 20:80]=100\n", + "input_field_arr[0, 40:60, 125:170] = 100\n", + "input_field_arr[0, 30:40, 30:40]=200\n", + "input_field_arr[0, 50:75, 50:75]=200\n", + "input_field_arr[0, 55:70, 55:70]=300\n", + "\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "plt.title(\"Base data\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# We now need to generate an Iris DataCube out of this dataset to run tobac feature detection. \n", + "# One can use xarray to generate a DataArray and then convert it to Iris, as done here. \n", + "input_field_iris = xr.DataArray(input_field_arr, dims=['time', 'Y', 'X'], coords={'time': [np.datetime64('2019-01-01T00:00:00')]}).to_iris()\n", + "# Version 2.0 of tobac (currently in development) will allow the use of xarray directly with tobac. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `position_threshold='center'`\n", + "This option will choose the arithmetic center of the area above the threshold. This is typically not recommended for most data. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50,]\n", + "position_threshold = 'center'\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold=position_threshold)\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"position_threshold \"+ position_threshold)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `position_threshold='extreme'`\n", + "This option will choose the most extreme point of our data. For `target='maximum'`, this will be the largest value in the feature area." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWoAAAEICAYAAAB25L6yAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAAsTAAALEwEAmpwYAAAk70lEQVR4nO3deZhcVZ3/8fcnC2kSgtkgE5IgAQIDiIRlAAdEHBxZJARRMEE0IBL5CQgKDgSUoIIssgyDAgZZgqxhG4KDsqugEoZggISABBJISJOQAJIEsnT39/fHvZ2ptFXd1d1V1bdSn9fz3Kfrnrucc29Vf+vUueeeq4jAzMyyq1tXF8DMzFrnQG1mlnEO1GZmGedAbWaWcQ7UZmYZ50BtZpZxDtRVRNK1kn7YyvKzJf2qwmU6T9ItFcjnWElPdXDbVssoab6kz3W8dGbl5UBdRSLixIj4CYCk/SUtbLH8pxHxzXLlny9Py68zXyxmLTlQW8VI6tHVZcgSSd27ugxWHRyoyyT9OT1R0kuS3pN0o6S6nOUnSJor6V1J0yRtkaZL0hWSlkj6u6QXJH0iXXaTpPMl9QF+C2whaUU6bdHyJ76kwyTNlvS+pN9L2qFF+c5I9/93SXfmli/P8eTNM128kaSbJS1P89ujRT5nSnoBWCmph6S9Jf05LdfzkvbPWf9YSa+n+5on6astynFpej7nSTo4J32L9Dy+m57XE1o5lq9JekPSMknnFFovXbdXmuebkhanzU8bp8selHRZzrp3SrohPc/XAp9Kz9P76fKbJF2TbrcS+Gxa7nskvZMe03dy9neepLsk3ZKejxclbZd+rpZIWiDp8znrf0zS9ZLqJb2Vflb8ZbAhiAhPZZiA+cAsYDgwAPgTcH667N+ApcBuQC/gKuCP6bIDgRlAP0DADsCQdNlNOfvYH1jYIs/zgFvS19sBK4F/B3oC/wHMBTbKKd8zwBZp+eYAJ7ZxTIXyXAUcAnQHLgSebnEeZqbnYWNgKLAsXb9bWr5lwGZAH+ADYPt02yHATunrY4G1wAlpPv8PWAQoXf4H4GqgDhgFvAMckOe87AisAPZLz/3lQAPwuQLH/J/AtPQc9QUeAC5Ml/0TsCR9P78KvA70zSnvUy32dRPwd2Cf9Nh7p+/1ucBGwNbpPg5scW4PBHoANwPzgHPS9/QEYF7O/v8b+GV6HjdP399vdfX/gqfOT11egA11SgPUiTnzhwCvpa+vBy7JWbZJGoS2Sv/p/wbsDXRrsc+bKD5Q/xCYmrOsG/AWsH9O+Y7JWX4JcG0bx1Qoz0dz5ncEPmpxHr6RM38m8OsW+3gIGJ8GmPeBLwEbt1jnWGBuznxvINJgORxobA6S6fILgZvynJdzgTty1usDrCFPoCb5olwJbJOT9qkWwfEIYAHJF+++LcqbL1DfnDO/F/Bmi3UmAjfmlPuRnGWjSb5kuqfzfdNz0A8YDKzOPW/AOOCJrv5f8NT5yU0f5bUg5/UbJLVX0r9vNC+IiBUktcqhEfE48HPgF8BiSZMlbdqBvFvm0ZSWZ2jOOm/nvP6Q5AujI1rup65Fe3Tuefg4cGTa7PF+2iywL8mvhpXAV4ATgXpJ/yPpn/PlExEfpi83ITnWdyNiec66b7D+sTbbIrc8aZ7LChzXZqS13pyy/i5Nb/Ybkhr+KxFRzMXDludiixbn4mySoNtscc7rj4ClEdGYMw/JOfg4SS27PmdfvySpWVuVc6Aur+E5r7ck+alO+vfjzQvS9t+BJDVeIuK/ImJ3YCeSJozv59l3W8MetsxDaXneat8htCvPYrZbQFKj7pcz9YmIiwAi4qGI+HeSZo+XgeuK2P8iYICkvjlpW5L/WOvJeV8k9SY59/ksJQmGO+WU9WMRkfuFdgFJs9EQSeMKHDMF0heQ1M5zz0XfiDikwLatWUBSox6Us69NI2KnDuzLMsaBurxOkjRM0gCSmtKdafptwHGSRknqBfwUmB4R8yX9i6S9JPUk+dm9iuRnfUuLgYGSPlYg76nAFyQdkO7rdJJ/5D934njayrMYtwCjJR0oqbukOiXd/oZJGpxeAO2TlnUF+Y99PRGxgOS4Lkz390ngeODWPKvfDRwqaV9JGwE/psD/Qfor5DrgCkmbA0gaKunA9PV+wHHA19PpKknNtfjFwLA0j0KeAT5IL7ZunJ6PT0j6l7aOOU9Z64GHgcskbSqpm6RtJH2mvfuy7HGgLq/bSP55Xk+n8wEi4jGSNuR7SGp42wBj0202JQkO75H8fF8GXNpyxxHxMnA78Hr6U3eLFstfAY4huVC5lKR9c3RErOnowbSVZ5H7WACMIfnieoekJvh9ks9iN5IvlEXAu8BngG8XuetxJG38i4D7gEkR8Uie/GcDJ5G8N/Uk57m1vuFnklyEfVrSB8CjwPZpc9TNwMkR8Vba7HE9cGP66+VxYDbwtqSlBc5FI8n7MorkIuFS4FdAR78Iv05yUfKl9LjuJvllYlWu+Yq5lZik+cA3I+LRri6LmVU316jNzDKuzUCdduBfImlWTtoASY9IejX92z9n2cT0hoNXmtvyrHooGS9kRZ7pt11dNrMsSq+LPKPk5q3Zkn6UppcsTrbZ9JFeMFlB0v+z+Q65S0i6Q10k6Sygf0ScKWlHkjbMPUm6QT0KbJfTncjMbIOSXpPoExEr0gv3TwGnkvSxL0mcbLNGHRF/JLmwk2sMMCV9PQU4PCf9johYHRHzSC7C7FnU0ZqZVaFIrEhne6ZTUMI42dFBcgan3YGIiPrmrkskNxg8nbPeQvLfdICkCcAEgO503703Hbmnw8xqzXLeWxoRm7W9ZmEHfrZPLHu3uB/6M15YPZukm2yzyRExOXeddEyVGcC2wC8iYrqkTsfJZqUezUx50vK2raQHOhlgUw2IvXRAiYtiZhuiR+PuN9peq3VL321k+kPDilq355DXVkXEHq2tkzZbjJLUD7hP6UBqBRQdJ5t1tNfHYklDANK/S9L0hax/N94w/u9uPDOzjAgao6moqV17jXgf+D1wECWMkx0N1NNIBtEh/Xt/TvpYJUNDjgBGktx9ZWaWGQE0EUVNbZG0WVqTRskQuJ8jGf6gZHGyzaYPSbeTjJo2SMnTPSYBFwFTJR0PvAkcCcldX5KmktwZ1QCc5B4fZpZFTbSvttyKIcCUtJ26G8molb+R9BdKFCfbDNQRMa7AoryNyhFxAclANWad1qd/b46aNJoh226GuuVr2rMNUTQF9XPfYeqPHmDlex+2vUF790+wtp3NGgX3FfECsGue9GWUKE760UiWaUdNGs1Oe/4zdT3qUN5rMLYhCoIBAwZy1CS48bQ7296g3fuHxg4PBll5DtSWaUO23cxBugYJUdejjiHbdqoXXquKaX/OCgdqyzR1k4N0jRIqW3NXAI1VNCCdA7WZ1aSSXUqsAI+eZ9aGHfbajjFHj+YLRx3EYUcfyo23Xk9TU+v/5gsXLeSB303rcJ73PnAPi99Z3PaKLfI89CsH503/5L47Mebo0eumNWvbPyx5R8qUVUHQWOSUBa5Rm7Whrlcd99/2AADL3l3G6T/4LstXLOc73zqt4DZv1S/kNw89wOiDDutQnvf95h5GbrMdgzcb3PbKRdhy6JbrjqGjOlKmhoYGevTIXpiJgLXZiMFFyd4ZNOuEvr+dxqCrL6XH4noaBg9h6bfPYPnBHQuW+QwcMJCfnH0+Xz72CE6ZcCpNTU1c+vOf8cyM6axZu4avHnkMY48Yx2U//xmvzXuNMUeP5ouHfpGvfWV83vUArrt5MtMe/G/UrRv7fWo/PrHjzsyaM4szfvg96nrVcecNdzF33lwuuuICPvzoQ/r368+Fky5h80GbM2vOLM7+yVlsXFfHbru0epfzP3jq6Se5avKVrFmzhuHDtuTCcy+mT+8+/Py6q3jiycdZvXoVu35yN3589vk89Pjv/qFMhxx1IHfffB8D+g3gxZde5JIrL+TXv7yNqyZfyZJ3lvBW/UL69xvAOaf/gEkXnsuit5Ob784+/QfsvsvuPDNjOhdcdj4AEtwy+XY26dPR5yu3l2isomsfDtS2wej722kM/unZdFuVjJ/T8+1FDP7p2QAlDdbDh21JU1MTy95dxmN/eJS+m/TlnpvvY82a1Yz95lfYZ699Of3k73PDLdfzyyuSZ/Peee8dedd7ff7rPPb7R5h60z1sXLcx7//9ffp9rB+3Tv01/3HqRHbecWfWNqzl/J/9iKsvu5YB/Qfy4MP/wxVXX86F517ExB+fyQ/POJc9d9+Li6+8qGCZ33zrTcYcPRqA3XbZjVO+dSrX3HA1N/7iZnpv3JvJU37JjbfewMknnMIxR32Nk084BYDvn3s6Tzz5OAcdcPB6ZWrL7Jdncdt1d1JXV8fpP/gu448+jj1G7cGitxdx/CnH8du7HuKGW37FuWeex+677M7KD1fSa6NeJXh3ihNAk2vUZpU36OpL1wXpZt1WrWLQ1ZeWNFADNI/j/qfpT/LK3Fd46LHfAbB85XLeWDCfnj17rrd+ofX+8syfOGL0l9i4bmMA+n2s3z/kNW/+PP72+t847qRjAWhqamSzQZuxfMVyli//gD133wuAMYcczpN//kPe8rZs+njiyceZ+/pcxh3/FQDWNqxh1M7JPRvTZzzNr26+jlWrPuL9D/7OyK1H8m/7tW/QtH/b7wDq6uoA+PMzf2Lu63PXLVuxcgUrVq5gt11256Irfsrogw7j85/9PH0GV/bxjq5Rm3WBHovr25XeUQsWvkn37t0ZOGAgEfCDM87l05/ab711ps94er35Qus9+Zc/kow7X1gQjNx6JHfecPd66R8s/6DNbQvuM4J99tqHyy/4z/XSV69ezY8unsQ9U+5jyD9twVWTr2T1mtV599G9e3cirZa2XGfjut7rXjc1BXfecNe6wN1swrEn8pl9P8sf/vR7jvrGl7nxFzezzVbbdOh42iu54aV6ArV7fdgGo6FAjaxQeke8+94yJl30Q7565DFIYt+9P83t99zG2oa1AMx7Yx4ffvQhfXpvwsqVK9ZtV2i9ffbal3um3c1Hqz4C4P2/vw9An959WPlhsv2Ij4/g3ffe5a8vPAfA2oa1vPra39i076Zssklfnp35LEC7epmM2nkUzz0/gzcWzAfgo1UfMe+NeesCbv9+A1j54cp1vwBalglg6JBhzJqTPKHv4cf/b72W9t17X26569fr5ue88hIAby58g+233Z4J47/FJ3bYmXnzXy+6/J0VwNroVtSUBa5R2wZj6bfPWK+NGqCpro6l3z6jU/tdtXoVY44eTUPDWrr36MGYgw/nuK9+A4AjDz+Kt+oXcsQxY4gI+vcfwNWXXsv2I7ene/ceHHb0oRxx6BF8feyxedfb718/w8t/m8OXvn44PXtsxGf2+QzfO+kMvjj6S0y68Nx1F+7+66Kfc/5lP2H5iuU0NjQwftyxjNxmOy489+J1FxP33fvTRR/TgP4DuXDSJXzvnO+u66p32onfZcTHR3Dk4V9h9LhDGDpkGDvv+Ml127Qs08knnMI550/klzddwy477VIwr3PO+CE/vvg8Ro/7Ao2NDeyx6578eOJPmHL7TUx/9mm6de/OtiO2Zb9/3a/gPkotEI1VVE9t85mJleAHB1gh5zx4ClsMavXhF+spd68Pq6xFS9/igkOuWi/t0bh7RlsD+bdlh0/2ipse2KKodffean6n8+ss16htg7L84MMcmK1N1dZG7UBtZjVINGak/bkYDtSWadEUBOGBmWpQEOt6lZR+39BURW3UDtSWafVz32HAgIEe6rTGBMGqhlXUz32nPPsPsSa6l2Xf5eBAbZk29UcPcNQk/ISXGpP7hJdyaaqiL34Hasu0le99WJYnfFhtSy4muunDzCzDfDHRzCzTfDHRzKwKNIbbqM3MMisQa6N6wl/1lNTMrER8MdHMLOMCuenDzCzrfDHRzCzDInD3vIrZu/AYuGaWEU8/39Ul+AfJxcTS3EIuaThwM/BPQBMwOSKulHQecALQfB/82RHxYLrNROB4oBH4TkQ81Foe1R2ozcw6qIQXExuA0yPiOUl9gRmSHkmXXRERl+auLGlHYCywE7AF8Kik7SKisVAGDtRmVnMC0VSii4kRUQ/Up6+XS5oDtPa0izHAHRGxGpgnaS6wJ/CXQhtUTyONmVkJNdKtqKk9JG0F7ApMT5NOlvSCpBsk9U/ThgILcjZbSOuB3YHazGpPAE3RragJGCTp2ZxpQr59StoEuAc4LSI+AK4BtgFGkdS4L2tetUCRCnLTh5nVILXnUVxL23pmoqSeJEH61oi4FyAiFucsvw74TTq7EBies/kwYFFr+3eN2sxqTgBro3tRU1skCbgemBMRl+ekD8lZ7YvArPT1NGCspF6SRgAjgWday8M1ajOrORFqbtYohX2ArwEvSpqZpp0NjJM0iuR7YT7wrSTvmC1pKvASSY+Rk1rr8QGdDNSSvgt8My3Ii8BxQG/gTmCrtHBHRcR7ncnHzKzUSnXDS0Q8Rf525wdb2eYC4IJi8+hwSSUNBb4D7BERnwC6k/QNPAt4LCJGAo+l82ZmmZGMR62ipizo7FdKD2BjST1IatKLSPoITkmXTwEO72QeZmYlljzhpZgpCzrc9BERb0m6FHgT+Ah4OCIeljQ47QBORNRL2jzf9mkXlwkAdfTuaDHabdkufSqW14Zo4PMru7oIZp2WdM/LRm25GB0O1Gnn7THACOB94C5JxxS7fURMBiYDbKoBrfYhNDMrpVKO9VEJnbmY+DlgXkS8AyDpXuBfgcWShqS16SHAkhKU08yspKppmNPOlPRNYG9JvdN+hAcAc0j6CI5P1xkP3N+5IpqZlVYyzKmKmrKgM23U0yXdDTxH0hfwryRNGZsAUyUdTxLMjyxFQc3MSqkm2qgBImISMKlF8mqS2rWZWSYlo+dVT9OH70w0s5qT3ELuQG1mlmGuUZuZZV5W7joshgO1mdWc5l4f1cKB2sxqkps+rMN6jHmn7ZUqoOH+zbq6CGZlU8pnJlaCA7WZ1ZwAGlyjNjPLNjd9mJllWbjpw8ws05ofHFAtHKjNrCa5Rm1mlmE18+AAM7NqFYiGJl9MNDPLNLdRm5llWbjpw8ws09xGbWXz9Ki7S7q/vWd+uaT7M6smDtRmZhkWiEZfTDQzyzZfTDQzy7CosouJ1VP3NzMroQgVNbVF0nBJT0iaI2m2pFPT9AGSHpH0avq3f842EyXNlfSKpAPbysOB2sxqUDIoUzFTERqA0yNiB2Bv4CRJOwJnAY9FxEjgsXSedNlYYCfgIOBqSd1by8CB2sxqUqlq1BFRHxHPpa+XA3OAocAYYEq62hTg8PT1GOCOiFgdEfOAucCereXhNmozqzkR0NhUdBv1IEnP5sxPjojJ+VaUtBWwKzAdGBwR9Ul+US9p83S1ocDTOZstTNMKcqA2s5rUjl4fSyNij7ZWkrQJcA9wWkR8IBXcf74F0dq+3fRhZjUnKF3TB4CkniRB+taIuDdNXixpSLp8CLAkTV8IDM/ZfBiwqLX9O1CbWQ0q3cVEJVXn64E5EXF5zqJpwPj09Xjg/pz0sZJ6SRoBjASeaS0PN32YWU2KVhsb2mUf4GvAi5JmpmlnAxcBUyUdD7wJHJnkG7MlTQVeIukxclJENLaWgQO1mdWkYps12t5PPEX+dmeAAwpscwFwQbF5OFCbWc1Jen1UT8uvA7WZ1aQSNn2UnQO1mdWkUjV9VIIDtZnVnKD4rndZ4EBtZjWpilo+OtePWlI/SXdLejkdOepTrY0YZWaWCQHRpKKmLOjsZc8rgd9FxD8Du5AMRpJ3xCgzsywp5Z2J5dbhQC1pU2A/kjtyiIg1EfE+hUeMMjPLjIjipizoTBv11sA7wI2SdgFmAKdSeMSo9UiaAEwAqKN3J4phVl7LdunT1UWoqIHPr+zqIpRd81gf1aIzTR89gN2AayJiV2Al7WjmiIjJEbFHROzRk16dKIaZWTsFECpuyoDOBOqFwMKImJ7O300SuAuNGGVmlhnV1PTR4UAdEW8DCyRtnyYdQDLISKERo8zMMqK4Hh9Z6fXR2X7UpwC3StoIeB04jiT4/8OIUVYi936ALlwGbzXA0B7ExIFwxKZdXSqz6pOR2nIxOhWoI2ImkO/JB3lHjLJOuvcDdMYS9FH6CVvYAGcsST5vDtZmxYvauZhoFaYLl/1fkG5O+yiSGraZtU8UOWWAbyGvJm81tC/dzFrhGrWVw9AC36uF0s2ssKYipwxwoK4iMXEgsfH6tYDYWMkFRTMrXpX1o3ZVrJocsWnSZOZeH2adlpU+0sVwoK4ie8/8cnLj/nUtFszsgsKYVTsHajOzjMtIs0YxHKjNrCbJNWozswwLQUZuDy+GA7WZ1SbXqM3MMs6B2sws4xyozcwyrPmGlyrhOxPNrCYpipva3I90g6QlkmblpJ0n6S1JM9PpkJxlEyXNlfSKpAOLKasDtZnVptKNnncTcFCe9CsiYlQ6PQggaUdgLLBTus3Vkrq3lYEDtZnVpFLVqCPij8C7RWY7BrgjIlZHxDxgLrBnWxu5jTpjGu7frKuLYFYbim+jHiTp2Zz5yRExuYjtTpb0deBZ4PSIeA8YCjyds87CNK1VrlGbWe0pttkjqVEvjYg9cqZigvQ1wDbAKKAeuCxNz/ft0Ga93YHazGpTGZ/wEhGLI6IxIppIhlFrbt5YCAzPWXUYsKit/TlQm1lNUlNxU4f2LQ3Jmf0i0NwjZBowVlIvSSOAkcAzbe3PbdRmVptKdMOLpNuB/UnashcCk4D9JY1Kc5kPfAsgImZLmgq8BDQAJ0VEY1t5OFCbWc0ptkdHMSJiXJ7k61tZ/wLggvbk4UBtZrWpiu5MdKA2s9rksT7MzLLNDw4wM8uy6HiPjq7gQG1mtck1ajOzjHOgNjPLtmpqo/adiWZmGecatZnVpiqqUTtQm1ntca8PM7Mq4Bq1mVl2ieq6mOhAbWa1qYoCdad7fUjqLumvkn6Tzg+Q9IikV9O//TtfTDOzEiryeYlZqXWXonveqcCcnPmzgMciYiTwWDpvZpYtTUVOGdCpQC1pGPAF4Fc5yWOAKenrKcDhncnDzKwcqqlG3dk26v8E/gPom5M2OCLqASKiXtLm+TaUNAGYAFBH704Wo/x6jHmn3dv4ieIbhoHPr+zqIlg5ZCQIF6PDNWpJhwJLImJGR7aPiMnNT/XtSa+OFsPMrP3a9xTyLteZGvU+wGGSDgHqgE0l3QIsljQkrU0PAZaUoqBmZqWUlWaNYnS4Rh0REyNiWERsBYwFHo+IY0iesjs+XW08cH+nS2lmVmo1UqMu5CJgqqTjgTeBI8uQh5lZp9TcLeQR8Xvg9+nrZcABpdivmVlZZKi2XAzfmWhmNUfpVC0cqM2sNrlGbWaWbdXU68OB2sxqkwO1mVmGVdmDA/zMRDOrTSXqRy3pBklLJM3KSSs4iqikiZLmSnpF0oHFFNWB2sxqUgkHZboJOKhFWt5RRCXtSHKD4E7pNldL6t5WBg7UZlabSlSjjog/Au+2SC40iugY4I6IWB0R84C5wJ5t5eFAbWY1qR016kGSns2ZJhSx+/VGEQWaRxEdCizIWW9hmtYqX0w0s9oTtOehAEsjYo8S5ZzvPps26+2uUZtZzWl+uG0ZHxywOB09lBajiC4EhuesNwxY1NbOHKjNrDaVd/S8QqOITgPGSuolaQQwEnimrZ256cPMapKiNHe8SLod2J+kLXshMIkCo4hGxGxJU4GXgAbgpIhobCsPB2ozqz0lHD0vIsYVWJR3FNGIuAC4oD15OFCbWU3yWB9mZhlXTbeQO1AXyU8UN9vAuEZtZpZhnet6V3EO1GZWmxyozcyyq/mGl2rhQG1mNUlN1ROpHajNrPb4KeRmZtnn7nlmZlnnGrWZWbb5YqKZWZYFUKJBmSrBgdrMapLbqM3MMsz9qM3Msi7CTR9mZlnnGrWZWdY5UJuZZZtr1GZmWRZAY/VEagdqM6tJ1VSj7tbRDSUNl/SEpDmSZks6NU0fIOkRSa+mf/uXrrhmZiXS3POjrSkDOhyoSR51fnpE7ADsDZwkaUfgLOCxiBgJPJbOm5lliqK4KQs6HKgjoj4inktfLwfmAEOBMcCUdLUpwOGdLKOZWWlFO6YMKEkbtaStgF2B6cDgiKiHJJhL2rzANhOACQB19C5FMYoy8PmVFcvLzLJJgGrpYqKkTYB7gNMi4gNJRW0XEZOByQCbakD1nDEz2yAoI+3PxehMGzWSepIE6Vsj4t40ebGkIenyIcCSzhXRzKzEqqzpozO9PgRcD8yJiMtzFk0DxqevxwP3d7x4ZmblUGSPj4zUujvT9LEP8DXgRUkz07SzgYuAqZKOB94EjuxUCc3MyqCUPTokzQeWA41AQ0TsIWkAcCewFTAfOCoi3uvI/jscqCPiKZI2+XwO6Oh+zcwqovS15c9GxNKc+eauyhdJOiudP7MjO+5UG7WZWVWKpNdHMVMnlKyrsgO1mdWm0l5MDOBhSTPSrsfQoqsykLercjE81oeZ1aR2dM8bJOnZnPnJaffiXPtExKL0vpFHJL1ckkKmHKjNrDYVH6iXRsQere8qFqV/l0i6D9iTtKtyeuNfp7oqu+nDzGpPAE1FTm2Q1EdS3+bXwOeBWZSwq3J116iffr6rS2BmVUhEKe9MHAzcl96V3QO4LSJ+J+l/KVFX5eoO1GZmHdVURHW5CBHxOrBLnvRllKirsgO1mdWe5qaPKuFAbWY1qZoGZXKgNrPa5EBtZpZl2RlwqRgO1GZWe/wUcjOz7HMbtZlZ1jlQm5llWABNDtRmZhnmi4lmZtnnQG1mlmEBNFbPrYkO1GZWgwLCgdrMLNvc9GFmlmHu9WFmVgVcozYzyzgHajOzDIuAxsauLkXRHKjNrDa5Rm1mlnEO1GZmWRbu9WFmlmkB4RtezMwyzreQm5llWAQ0OVCbmWWbLyaamWVbuEZtZpZlfnCAmVm2eVAmM7NsCyCq6BbybuXasaSDJL0iaa6ks8qVj5lZu0X64IBipjZUItaVJVBL6g78AjgY2BEYJ2nHcuRlZtYR0RRFTa2pVKwrV416T2BuRLweEWuAO4AxZcrLzKz9SlOjrkisK1cb9VBgQc78QmCv3BUkTQAmpLOrH427Z5WpLO0xCFjqMgDZKEcWygDZKEcWygDZKMf2nd3Bct576NG4e1CRq9dJejZnfnJETE5ftxnrSqFcgVp50tb7DZEe6GQASc9GxB5lKkvRslCOLJQhK+XIQhmyUo4slCEr5WgRNDskIg4qRVkoItaVQrmaPhYCw3PmhwGLypSXmVlXqUisK1eg/l9gpKQRkjYCxgLTypSXmVlXqUisK0vTR0Q0SDoZeAjoDtwQEbNb2WRyK8sqKQvlyEIZIBvlyEIZIBvlyEIZIBvlyEIZgA7Fug5RVNFtlGZmtahsN7yYmVlpOFCbmWVclwfqrrjVXNJwSU9ImiNptqRT0/TzJL0laWY6HVKBssyX9GKa37Np2gBJj0h6Nf3bv4z5b59zvDMlfSDptEqcC0k3SFoiaVZOWsFjlzQx/Zy8IunAMpbhZ5JelvSCpPsk9UvTt5L0Uc45ubYUZWilHAXfgwqeiztz8p8vaWaaXpZz0cr/ZkU/F5kTEV02kTS+vwZsDWwEPA/sWIF8hwC7pa/7An8juf3zPOCMCp+D+cCgFmmXAGelr88CLq7g+/E28PFKnAtgP2A3YFZbx56+P88DvYAR6eeme5nK8HmgR/r64pwybJW7XgXORd73oJLnosXyy4Bzy3kuWvnfrOjnImtTV9eou+RW84ioj4jn0tfLgTkkdxhlxRhgSvp6CnB4hfI9AHgtIt6oRGYR8Ufg3RbJhY59DHBHRKyOiHnAXJLPT8nLEBEPR0RDOvs0Sd/YsipwLgqp2LloJknAUcDtnc2njTIU+t+s6Ocia7o6UOe7/bKiAVPSVsCuwPQ06eT0J+8N5WxyyBHAw5JmpLfVAwyOiHpIPrjA5hUoByR9QHP/ESt9LqDwsXfVZ+UbwG9z5kdI+qukP0j6dAXyz/cedMW5+DSwOCJezUkr67lo8b+Ztc9FRXV1oK7I7ZcFM5c2Ae4BTouID4BrgG2AUUA9yU+9ctsnInYjGX3rJEn7VSDPf5B21j8MuCtN6opz0ZqKf1YknQM0ALemSfXAlhGxK/A94DZJm5axCIXeg674vxnH+l/iZT0Xef43C66aJ22D63Pc1YG6y241l9ST5INwa0TcCxARiyOiMSKagOuowE+oiFiU/l0C3JfmuVjSkLScQ4Al5S4HyRfFcxGxOC1Pxc9FqtCxV/SzImk8cCjw1UgbQ9Of18vS1zNI2kO3K1cZWnkPKn0uegBHAHfmlK1s5yLf/yYZ+Vx0la4O1F1yq3na3nY9MCciLs9JH5Kz2heBso7oJ6mPpL7Nr0kuYs0iOQfj09XGA/eXsxyp9WpMlT4XOQod+zRgrKRekkYAI4FnylEASQcBZwKHRcSHOembKRl/GElbp2V4vRxlSPMo9B5U7FykPge8HBELc8pWlnNR6H+TDHwuulRXX80EDiG5svsacE6F8tyX5OfRC8DMdDoE+DXwYpo+DRhS5nJsTXLF+nlgdvPxAwOBx4BX078DylyO3sAy4GM5aWU/FyRfDPXAWpKa0fGtHTtwTvo5eQU4uIxlmEvS7tn82bg2XfdL6fv0PPAcMLrM56Lge1Cpc5Gm3wSc2GLdspyLVv43K/q5yNrkW8jNzDKuq5s+zMysDQ7UZmYZ50BtZpZxDtRmZhnnQG1mlnEO1GZmGedAbWaWcf8fe3OVuuPGxLUAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50,]\n", + "position_threshold = 'extreme'\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold=position_threshold)\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"position_threshold \"+ position_threshold)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `position_threshold='weighted_diff'`\n", + "This option will choose the center of the region weighted by the distance from the threshold value." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50,]\n", + "position_threshold = 'weighted_diff'\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold=position_threshold)\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"position_threshold \"+ position_threshold)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `position_threshold='weighted_abs'`\n", + "This option will choose the center of the region weighted by the absolute values of the field." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50,]\n", + "position_threshold = 'weighted_abs'\n", + "single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold=position_threshold)\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "# Plot all features detected\n", + "plt.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + "plt.legend()\n", + "plt.title(\"position_threshold \"+ position_threshold)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:tobac_dev_installed]", + "language": "python", + "name": "conda-env-tobac_dev_installed-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.5" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "25a19fbe0a9132dfb9279d48d161753c6352f8f9478c2e74383d340069b907c3" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 8174ffea534537b0dc2a7a5d57ae0144730417d4 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sun, 3 Jul 2022 21:50:06 -0600 Subject: [PATCH 078/187] Explanation of threshold parameters --- doc/feature_detection/index.rst | 5 +- .../position_threshold_example.ipynb | 52 ++++++++++++++++-- doc/feature_detection_output.rst | 2 +- doc/images/position_thresholds.png | Bin 0 -> 133261 bytes doc/threshold_detection_parameters.rst | 6 +- 5 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 doc/images/position_thresholds.png diff --git a/doc/feature_detection/index.rst b/doc/feature_detection/index.rst index a347198d..4f9df1a5 100644 --- a/doc/feature_detection/index.rst +++ b/doc/feature_detection/index.rst @@ -1,9 +1,10 @@ -#################### +###################################### Feature Detection Parameter Examples -#################### +###################################### .. toctree:: :maxdepth: 2 notebooks/multiple_thresholds_example notebooks/n_min_threshold_example + notebooks/position_threshold_example diff --git a/doc/feature_detection/notebooks/position_threshold_example.ipynb b/doc/feature_detection/notebooks/position_threshold_example.ipynb index 558a51a0..271e6e16 100644 --- a/doc/feature_detection/notebooks/position_threshold_example.ipynb +++ b/doc/feature_detection/notebooks/position_threshold_example.ipynb @@ -97,12 +97,12 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -136,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -175,7 +175,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -214,7 +214,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -242,6 +242,48 @@ "plt.title(\"position_threshold \"+ position_threshold)\n", "plt.show()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### All four methods together" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50,]\n", + "fig, axarr = plt.subplots(2,2, figsize=(10,6))\n", + "testing_thresholds = ['center', 'extreme', 'weighted_diff', 'weighted_abs']\n", + "for position_threshold, ax in zip(testing_thresholds, axarr.flatten()):\n", + "\n", + " single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', position_threshold=position_threshold)\n", + " color_mesh = ax.pcolormesh(input_field_arr[0])\n", + " plt.colorbar(color_mesh, ax=ax)\n", + " # Plot all features detected\n", + " ax.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + " ax.legend()\n", + " ax.set_title(\"position_threshold \"+ position_threshold)\n", + "plt.tight_layout()\n", + "plt.show()" + ] } ], "metadata": { diff --git a/doc/feature_detection_output.rst b/doc/feature_detection_output.rst index bf4cddd4..1c15b525 100644 --- a/doc/feature_detection_output.rst +++ b/doc/feature_detection_output.rst @@ -1,4 +1,4 @@ -Feature detection output +Feature Detection Output ------------------------- Feature detection outputs a `pandas` dataframe with several variables. The variables, (with column names listed in the `Variable Name` column), are described below, with units. Note that while these variables come initially from the feature detection step, segmentation and tracking also share some of these variables. See :doc:`tracking_output` for the additional columns added by tracking. diff --git a/doc/images/position_thresholds.png b/doc/images/position_thresholds.png new file mode 100644 index 0000000000000000000000000000000000000000..8838117a10968760060c4385ed7d33582002aa1b GIT binary patch literal 133261 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 Date: Sun, 3 Jul 2022 21:57:45 -0600 Subject: [PATCH 079/187] Addition of readthedocs requirements --- .readthedocs.yml | 7 +++++++ doc/requirements.txt | 2 ++ 2 files changed, 9 insertions(+) create mode 100644 .readthedocs.yml create mode 100644 doc/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..a5ea561c --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,7 @@ +version: 2 +formats: all +python: + version: 3 + install: + - requirements: doc/requirements.txt + system_packages: true diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..82f69f75 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,2 @@ +ipykernel +nbsphinx From 6413646d43965c0e2042adf9c1227206ea7adf76 Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Mon, 4 Jul 2022 09:37:43 -0600 Subject: [PATCH 080/187] Cleaning up some simple text --- doc/feature_detection_output.rst | 2 +- doc/tracking_output.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/feature_detection_output.rst b/doc/feature_detection_output.rst index 1c15b525..273dfa21 100644 --- a/doc/feature_detection_output.rst +++ b/doc/feature_detection_output.rst @@ -1,7 +1,7 @@ Feature Detection Output ------------------------- -Feature detection outputs a `pandas` dataframe with several variables. The variables, (with column names listed in the `Variable Name` column), are described below, with units. Note that while these variables come initially from the feature detection step, segmentation and tracking also share some of these variables. See :doc:`tracking_output` for the additional columns added by tracking. +Feature detection outputs a `pandas` dataframe with several variables. The variables, (with column names listed in the `Variable Name` column), are described below with units. Note that while these variables come initially from the feature detection step, segmentation and tracking also share some of these variables. See :doc:`tracking_output` for the additional columns added by tracking. Variables that are common to all feature detection files: diff --git a/doc/tracking_output.rst b/doc/tracking_output.rst index 9aa9ad39..26d6501e 100644 --- a/doc/tracking_output.rst +++ b/doc/tracking_output.rst @@ -1,4 +1,4 @@ -Tracking output +Tracking Output ------------------------- Tracking outputs a `pandas` dataframe with additional variables in addition to the variables output by Feature Detection (see :doc:`feature_detection_output`). The additional variables added by tracking, with column names listed in the `Variable Name` column, are described below with units. From 3322c0ee2df65ce312a120a6d5a06bb85498e6e5 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Wed, 6 Jul 2022 09:52:56 +0200 Subject: [PATCH 081/187] docstrings for some functions --- tobac/plotting.py | 494 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 477 insertions(+), 17 deletions(-) diff --git a/tobac/plotting.py b/tobac/plotting.py index de3c87be..613083a3 100644 --- a/tobac/plotting.py +++ b/tobac/plotting.py @@ -1,3 +1,23 @@ +"""Provide methods for plotting analyzed data. + +Plotting routines including both visualizations of the entire cloud field +and detailed visualizations for individual convective cells and their +properties. [3]_ + +Notes +----- +many short summaries are the same + +References +---------- +.. Heikenfeld, M., Marinescu, P. J., Christensen, M., + Watson-Parris, D., Senf, F., van den Heever, S. C. + & Stier, P. (2019). tobac 1.2: towards a flexible + framework for tracking and analysis of clouds in + diverse datasets. Geoscientific Model Development, + 12(11), 4551-4570. +""" + import matplotlib as mpl import warnings import logging @@ -23,6 +43,65 @@ def plot_tracks_mask_field_loop( margin_top=0.05, **kwargs ): + """Plot data features and segments of all timeframes onto a map + projection and save as pngs. For the function to work it is + necessary to pass vmin, vmax and axis_extent as kwargs + + Parameters + ---------- + track : pandas.DataFrame + Output of linking_trackpy + + field : iris.cube.Cube + Original input data + + mask : iris.cube.Cube + Cube containing mask (int id for tacked volumes 0 everywhere + else). + + features : pandas.DataFrame + Output of the feature detection. + + axes : cartopy.mpl.geoaxes.GeoAxesSubplot, optional + Not used. Default is None. + + name : str, optional + First part of the filename. Same for all pngs. If None, + the name of the field is used. Default is None. + + plot_dir : str, optional + Path where the plot will be saved. Default is './'. + + figsize : tupel of float, optional + Width, height of the plot in inches. + Default is (10/2.54, 10/2.54). + + dpi : int, optional + Plot resolution. Default is 300. + + margin_left : float, optional + The position of the left edge of the axes, as a + fraction of the figure width. Default is 0.05. + + margin_right : float, optional + The position of the right edge of the axes, as a + fraction of the figure width. Default is 0.05. + + margin_bottom : float, optional + The position of the bottom edge of the axes, as a + fraction of the figure width. Default is 0.05. + + margin_top : float, optional + The position of the top edge of the axes, as a + fraction of the figure width. Default is 0.05. + + **kwargs + + Returns + ------- + none + """ + mpl_backend = mpl.get_backend() if mpl_backend != "agg": warnings.warn( @@ -103,6 +182,135 @@ def plot_tracks_mask_field( rasterized=True, linewidth_contour=1, ): + """Plot data and segments of a timeframe and all tracks onto + a map projection. For the function to work it is necessary + to set vmin, vmax and axis_extent even though these are optional. + + Parameters + ---------- + track : pandas.DataFrame + Output of linking_trackpy + + field : iris.cube.Cube + One frame of the original input data + + mask : iris.cube.Cube + One frame of the Cube containing mask (int id for tacked + volumes 0 everywhere else), output of the segmentation. + + features : pandas.DataFrame + Output of the feature detection. + + axes : cartopy.mpl.geoaxes.GeoAxesSubplot, optional + GeoAxesSubplot to use for plotting. Default is None. + + axis_extent : ndarray, optional + Array containing the bounds of the longitude and latitude + values. The structure is + [long_min, long_max, lat_min, lat_max]. Default is None. + + plot_outline : bool, optional + Boolean defining wether the outlines of the segments are + plotted. Default is True. + + plot_marker : bool, optional + Boolean defining wether the positions of the features from + the track dataframe are plotted. Default is True. + + marker_track : str, optional + String defining the shape of the marker for the feature + positions from the track dataframe. Default is 'x'. + + markersize_track : int, optional + Int defining the size of the marker for the feature + positions from the track dataframe. Default is 4. + + plot_number : bool, optional + Boolean defining wether the index of the cells + is plotted next to the individual feature positions. + Default is True. + + plot_features : bool, optional + Boolean defining wether the positions of the features from + the features dataframe are plotted. Default is True. + + marker_feature : optional + String defining the shape of the marker for the feature + positions from the features dataframe. Default is None. + + markersize_feature : optional + Int defining the size of the marker for the feature + positions from the features dataframe. Default is None. + + title : str, optional + Flag determining the title of the plot. 'datestr' uses + date and time of the field. None sets not title. + Default is None. + + title_str : str, optional + Additional string added to the beginning of the title. + Default is None. + + vmin : float, optional + Lower bound of the colorbar. Default is None. + + vmax : float, optional + Upper bound of the colorbar. Default is None. + + n_levels : int, optional + Number of levels of the contour plot of the field. + Default is 50. + + cmap : {'viridis',...}, optional + Colormap of the countour plot of the field. + matplotlib.colors. Default is 'viridis'. + + extend : str, optional + Determines the coloring of values that are + outside the levels range. If 'neither', values outside + the levels range are not colored. If 'min', 'max' or + 'both', color the values below, above or below and above + the levels range. Values below min(levels) and above + max(levels) are mapped to the under/over values of the + Colormap. Default is 'neither'. + + orientation_colorbar : str, optional + Orientation of the colorbar, 'horizontal' or 'vertical' + Default is 'horizontal'. + + pad_colorbar : float, optional + Fraction of original axes between colorbar and new + image axes. Default is 0.05. + + label_colorbar : str, optional + Label of the colorbar. If none, name and unit of + the field are used. Default is None. + + fraction_colorbar : float, optional + Fraction of original axes to use for colorbar. + Default is 0.046. + + rasterized : bool, optional + True enables, False disables rasterization. + Default is True. + + linewidth_contour : int, optional + Linewidth of the contour plot of the segments. + Default is 1. + + Returns + ------- + axes : cartopy.mpl.geoaxes.GeoAxesSubplot + Axes with the plot. + + Raises + ------ + ValueError + If axes are not cartopy.mpl.geoaxes.GeoAxesSubplot. + + If mask.ndim is neither 2 nor 3. + """ + import matplotlib.pyplot as plt import cartopy @@ -267,6 +475,40 @@ def plot_tracks_mask_field( def animation_mask_field( track, features, field, mask, interval=500, figsize=(10, 10), **kwargs ): + """Create animation of data, features and segments of + all timeframes. + + Parameters + ---------- + track : pandas.DataFrame + Output of linking_trackpy + + features : pandas.DataFrame + Output of the feature detection. + + field : iris.cube.Cube + Original input data + + mask : iris.cube.Cube + Cube containing mask (int id for tacked volumes 0 + everywhere else), output of the segmentation. + + interval : int, optional + Delay between frames in milliseconds. + Default is 500. + + figsize : tupel of float, optional + Width, height of the plot in inches. + Default is (10, 10). + + **kwargs + + Returns + ------- + animation : matplotlib.animation.FuncAnimation + Created animation as object. + """ + mpl_backend = mpl.get_backend() if mpl_backend != "agg": warnings.warn( @@ -321,9 +563,54 @@ def plot_mask_cell_track_follow( dpi=300, **kwargs ): - """Make plots for all cells centred around cell and with one background field as filling and one background field as contrours - Input: - Output: + """Make plots for all cells centered around cell. + + With one background field as filling and one background field as + contours. + + Parameters + ---------- + cell : int + Integer id of cell to create masked cube for. + + track + + cog + + features : pandas.DataFrame + Output from trackpy/maketrack. + + mask_total : iris.cube.Cube + + field_contour + + field_filled + + width : int, optional + Default is 10000. + + name : str, optional + Default is 'test'. + + plotdir : str, optional + Path where the plot will be saved. Default is './'. + + file_format : {['png'], ['pdf']}, optional + Default is ['png']. + + dpi : int, optional + Plot resolution. Default is 300. + + **kwargs + + Returns + ------- + none + + Notes + ----- + unsure about features, mask_total + needs more descriptions """ mpl_backend = mpl.get_backend() @@ -1733,30 +2020,38 @@ def plot_mask_cell_track_static_timeseries( def map_tracks( track, axis_extent=None, figsize=None, axes=None, untracked_cell_value=-1 ): - """Maps the tracks of all tracked cells + """Plot the trajectories of the cells. Parameters ---------- - track: pandas dataframe - Tracks from tobac - axis_extent: array-like, length 4 - Extent of the map, as required by `cartopy` - figsize: depreciated - Depreciated parameter - axes: Matplotlib axes or geoaxes from cartopy - Axes to plot the tracks onto - untracked_cell_value: int or np.nan - Untracked cell value from tobac. + track : pandas.DataFrame + Dataframe conatining the linked features with a + column 'cell'. + + axis_extent : matplotlib.axes, optional + Array containing the bounds of the longitude + and latitude values. The structure is + [long_min, long_max, lat_min, lat_max]. + Default is None. + + figsize : tupel of float, optional + Width, height of the plot in inches. + Default is (10, 10). + + axes : cartopy.mpl.geoaxes.GeoAxesSubplot, optional + GeoAxesSubplot to use for plotting. Default is None. Returns ------- - Returns `axes` with the tracks plotted onto it. + axes : cartopy.mpl.geoaxes.GeoAxesSubplot + Axes with the plotted trajectories. Raises ------ - Raises a `ValueError` if `axes` is not passed in. - + ValueError + If no axes is passed. """ + if figsize is not None: warnings.warn( "figsize is depreciated as this function does not create its own figure.", @@ -1778,6 +2073,19 @@ def map_tracks( def make_map(axes): + """Configure the parameters of cartopy for plotting. + + Parameters + ---------- + axes : cartopy.mpl.geoaxes.GeoAxesSubplot + cartopy.mpl.geoaxes.GeoAxesSubplot to configure + + Returns + ------- + axes : cartopy.mpl.geoaxes.GeoAxesSubplot + Cartopy axes to configure + """ + import matplotlib.ticker as mticker import cartopy.crs as ccrs from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER @@ -1806,6 +2114,40 @@ def make_map(axes): def plot_lifetime_histogram( track, axes=None, bin_edges=np.arange(0, 200, 20), density=False, **kwargs ): + """Plot the liftetime histogram of a the cells. + + Parameters + ---------- + track : pandas.DataFrame + DataFrame of the features containing the variable as + column and a column 'cell' + + axes : matplotlib.axes.Axes, optional + Matplotlib axes to plot on. Default is None. + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is + a sequence, it defines a monotonically increasing + array of bin edges, including the rightmost edge. + Default is np.arange(0, 200, 20). + + density : bool, optional + If False, the result will contain the number of + samples in each bin. If True, the result is the + value of the probability density function at the + bin, normalized such that the integral over the + range is 1. Default is False. + + **kwargs + + Returns + ------- + plot_hist : list + List containing the matplotlib.lines.Line2D instance + of the histogram + """ + hist, bin_edges, bin_centers = lifetime_histogram( track, bin_edges=bin_edges, density=density ) @@ -1822,6 +2164,46 @@ def plot_lifetime_histogram_bar( shift=0.5, **kwargs ): + """Plot the liftetime histogram of a the cells as bar plot. + + Parameters + ---------- + track : pandas.DataFrame + DataFrame of the features containing the variable as + column and a column 'cell' + + axes : matplotlib.axes.Axes, optional + Matplotlib axes to plot on. Default is None. + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is + a sequence, it defines a monotonically increasing + array of bin edges, including the rightmost edge. + + density : bool, optional + If False, the result will contain the number of + samples in each bin. If True, the result is the + value of the probability density function at the + bin, normalized such that the integral over the + range is 1. Default is False. + + width_bar : float + Didth of the bars. Default is 1. + + shift : float + Value to shift the bin centers to the right. + Default is 0.5. + + **kwargs + + Returns + ------- + plot_hist : matplotlib.container.BarContainer + matplotlib.container.BarContainer instance + of the histogram + """ + hist, bin_edges, bin_centers = lifetime_histogram( track, bin_edges=bin_edges, density=density ) @@ -1832,6 +2214,48 @@ def plot_lifetime_histogram_bar( def plot_histogram_cellwise( track, bin_edges, variable, quantity, axes=None, density=False, **kwargs ): + """Plot the histogram of a variable based on the cells. + + Parameters + ---------- + track : pandas.DataFrame + DataFrame of the features containing the variable as + column and a column 'cell' + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is + a sequence, it defines a monotonically increasing + array of bin edges, including the rightmost edge. + + variable : string + Column of the DataFrame with the variable on which the + histogram is to be based on. Default is None. + + quantity : {'max', 'min', 'mean'}, optional + Flag determining wether to use maximum, minimum or mean + of a variable from all timeframes the cell covers. + Default is 'max'. + + axes : matplotlib.axes.Axes, optional + Matplotlib axes to plot on. Default is None. + + density : bool, optional + If False, the result will contain the number of + samples in each bin. If True, the result is the + value of the probability density function at the + bin, normalized such that the integral over the + range is 1. Default is False. + + **kwargs + + Returns + ------- + plot_hist : list + List containing the matplotlib.lines.Line2D instance + of the histogram + """ + hist, bin_edges, bin_centers = histogram_cellwise( track, bin_edges=bin_edges, @@ -1846,6 +2270,42 @@ def plot_histogram_cellwise( def plot_histogram_featurewise( Track, bin_edges, variable, axes=None, density=False, **kwargs ): + """Plot the histogram of a variable based on the features. + + Parameters + ---------- + Track : pandas.DataFrame + DataFrame of the features containing the variable as column + + bin_edges : int or ndarray, optional + If bin_edges is an int, it defines the number of + equal-width bins in the given range. If bins is + a sequence, it defines a monotonically increasing + array of bin edges, including the rightmost edge. + + variable : string + Column of the DataFrame with the variable on which the + histogram is to be based on. Default is None. + + axes : matplotlib.axes.Axes, optional + Matplotlib axes to plot on. Default is None. + + density : bool, optional + If False, the result will contain the number of + samples in each bin. If True, the result is the + value of the probability density function at the + bin, normalized such that the integral over the + range is 1. Default is False. + + **kwargs + + Returns + ------- + plot_hist : list + List containing the matplotlib.lines.Line2D instance + of the histogram + """ + hist, bin_edges, bin_centers = histogram_featurewise( Track, bin_edges=bin_edges, variable=variable, density=density ) From 327e27721b162fb77ea942bb52900c1b90c163e2 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Wed, 6 Jul 2022 15:41:29 +0200 Subject: [PATCH 082/187] delete dublicate --- tobac/plotting.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tobac/plotting.py b/tobac/plotting.py index 613083a3..ad1d7209 100644 --- a/tobac/plotting.py +++ b/tobac/plotting.py @@ -1829,10 +1829,6 @@ def plot_mask_cell_track_static_timeseries( Input: Output: """ - """Make plots for all cells with fixed frame including entire development of the cell and with one background field as filling and one background field as contrours - Input: - Output: - """ mpl_backend = mpl.get_backend() if mpl_backend != "agg": From ab04f8fb60cda837d230faeeb8070acbd20af04f Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Wed, 6 Jul 2022 16:54:59 +0200 Subject: [PATCH 083/187] cleaning up --- tobac/plotting.py | 87 +++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/tobac/plotting.py b/tobac/plotting.py index ad1d7209..8bc4d723 100644 --- a/tobac/plotting.py +++ b/tobac/plotting.py @@ -2,11 +2,7 @@ Plotting routines including both visualizations of the entire cloud field and detailed visualizations for individual convective cells and their -properties. [3]_ - -Notes ------ -many short summaries are the same +properties. References ---------- @@ -43,21 +39,22 @@ def plot_tracks_mask_field_loop( margin_top=0.05, **kwargs ): - """Plot data features and segments of all timeframes onto a map - projection and save as pngs. For the function to work it is - necessary to pass vmin, vmax and axis_extent as kwargs + """Plot field, feature positions and segments + onto individual maps for all timeframes and + save them as pngs. It is required to pass vmin, + vmax and axis_extent as kwargs. Parameters ---------- track : pandas.DataFrame - Output of linking_trackpy + Output of linking_trackpy. field : iris.cube.Cube - Original input data + Original input data. mask : iris.cube.Cube - Cube containing mask (int id for tacked volumes 0 everywhere - else). + Cube containing mask (int id for tacked volumes, 0 + everywhere else). Ouput of the segmentation step. features : pandas.DataFrame Output of the feature detection. @@ -70,7 +67,7 @@ def plot_tracks_mask_field_loop( the name of the field is used. Default is None. plot_dir : str, optional - Path where the plot will be saved. Default is './'. + Path where the plots will be saved. Default is './'. figsize : tupel of float, optional Width, height of the plot in inches. @@ -99,7 +96,7 @@ def plot_tracks_mask_field_loop( Returns ------- - none + None """ mpl_backend = mpl.get_backend() @@ -182,24 +179,25 @@ def plot_tracks_mask_field( rasterized=True, linewidth_contour=1, ): - """Plot data and segments of a timeframe and all tracks onto - a map projection. For the function to work it is necessary - to set vmin, vmax and axis_extent even though these are optional. + """Plot field, features and segments of a timeframe and + on a map projection. It is required to pass vmin, vmax + and axis_extent. Parameters ---------- track : pandas.DataFrame - Output of linking_trackpy + One ore more framesof the output of linking_trackpy. field : iris.cube.Cube - One frame of the original input data + One frame of the original input data. mask : iris.cube.Cube One frame of the Cube containing mask (int id for tacked - volumes 0 everywhere else), output of the segmentation. + volumes 0 everywhere else), output of the segmentation + step. features : pandas.DataFrame - Output of the feature detection. + Output of the feature detection, one or more frames. axes : cartopy.mpl.geoaxes.GeoAxesSubplot, optional GeoAxesSubplot to use for plotting. Default is None. @@ -227,7 +225,7 @@ def plot_tracks_mask_field( plot_number : bool, optional Boolean defining wether the index of the cells - is plotted next to the individual feature positions. + is plotted next to the individual feature position. Default is True. plot_features : bool, optional @@ -266,13 +264,13 @@ def plot_tracks_mask_field( matplotlib.colors. Default is 'viridis'. extend : str, optional - Determines the coloring of values that are - outside the levels range. If 'neither', values outside - the levels range are not colored. If 'min', 'max' or - 'both', color the values below, above or below and above - the levels range. Values below min(levels) and above - max(levels) are mapped to the under/over values of the - Colormap. Default is 'neither'. + Determines the coloring of values that are + outside the levels range. If 'neither', values outside + the levels range are not colored. If 'min', 'max' or + 'both', color the values below, above or below and above + the levels range. Values below min(levels) and above + max(levels) are mapped to the under/over values of the + Colormap. Default is 'neither'. orientation_colorbar : str, optional Orientation of the colorbar, 'horizontal' or 'vertical' @@ -475,23 +473,23 @@ def plot_tracks_mask_field( def animation_mask_field( track, features, field, mask, interval=500, figsize=(10, 10), **kwargs ): - """Create animation of data, features and segments of + """Create animation of field, features and segments of all timeframes. Parameters ---------- track : pandas.DataFrame - Output of linking_trackpy + Output of linking_trackpy. features : pandas.DataFrame Output of the feature detection. field : iris.cube.Cube - Original input data + Original input data. mask : iris.cube.Cube Cube containing mask (int id for tacked volumes 0 - everywhere else), output of the segmentation. + everywhere else), output of the segmentation step. interval : int, optional Delay between frames in milliseconds. @@ -2016,7 +2014,7 @@ def plot_mask_cell_track_static_timeseries( def map_tracks( track, axis_extent=None, figsize=None, axes=None, untracked_cell_value=-1 ): - """Plot the trajectories of the cells. + """Plot the trajectories of the cells on a map. Parameters ---------- @@ -2074,7 +2072,7 @@ def make_map(axes): Parameters ---------- axes : cartopy.mpl.geoaxes.GeoAxesSubplot - cartopy.mpl.geoaxes.GeoAxesSubplot to configure + GeoAxesSubplot to configure. Returns ------- @@ -2110,13 +2108,13 @@ def make_map(axes): def plot_lifetime_histogram( track, axes=None, bin_edges=np.arange(0, 200, 20), density=False, **kwargs ): - """Plot the liftetime histogram of a the cells. + """Plot the liftetime histogram of the cells. Parameters ---------- track : pandas.DataFrame - DataFrame of the features containing the variable as - column and a column 'cell' + DataFrame of the features containing the columns + 'cell' and 'time_cell'. axes : matplotlib.axes.Axes, optional Matplotlib axes to plot on. Default is None. @@ -2165,8 +2163,8 @@ def plot_lifetime_histogram_bar( Parameters ---------- track : pandas.DataFrame - DataFrame of the features containing the variable as - column and a column 'cell' + DataFrame of the features containing the columns + 'cell' and 'time_cell'. axes : matplotlib.axes.Axes, optional Matplotlib axes to plot on. Default is None. @@ -2185,7 +2183,7 @@ def plot_lifetime_histogram_bar( range is 1. Default is False. width_bar : float - Didth of the bars. Default is 1. + Width of the bars. Default is 1. shift : float Value to shift the bin centers to the right. @@ -2215,8 +2213,8 @@ def plot_histogram_cellwise( Parameters ---------- track : pandas.DataFrame - DataFrame of the features containing the variable as - column and a column 'cell' + DataFrame of the features containing the variable + as column and a column 'cell'. bin_edges : int or ndarray, optional If bin_edges is an int, it defines the number of @@ -2271,7 +2269,8 @@ def plot_histogram_featurewise( Parameters ---------- Track : pandas.DataFrame - DataFrame of the features containing the variable as column + DataFrame of the features containing the variable + as column. bin_edges : int or ndarray, optional If bin_edges is an int, it defines the number of From d3aa8ea7322fef19452fe9f3831c26b5f2b56df0 Mon Sep 17 00:00:00 2001 From: kelcyno <88055123+kelcyno@users.noreply.github.com> Date: Wed, 6 Jul 2022 19:10:02 -0600 Subject: [PATCH 084/187] Ran Black formatting on file. Formatted using the Black python package. --- tobac/merge_split.py | 367 ++++++++++++++++++++++++------------------- 1 file changed, 205 insertions(+), 162 deletions(-) diff --git a/tobac/merge_split.py b/tobac/merge_split.py index 60e929be..c58be5aa 100644 --- a/tobac/merge_split.py +++ b/tobac/merge_split.py @@ -1,4 +1,4 @@ -''' +""" Tobac merge and split v0.1 This submodule is a post processing step to address tracked cells which merge/split. The first iteration of this module is to combine the cells which are merging but receive @@ -6,7 +6,7 @@ the largest cell number of those within the merged/split cell ids. -''' +""" from geopy.distance import geodesic @@ -15,8 +15,9 @@ from pandas.core.common import flatten import xarray as xr + def compress_all(nc_grids, min_dims=2): - ''' + """ The purpose of this subroutine is to compress the netcdf variables as they are saved this does not change the data, but sets netcdf encoding parameters. We allocate a minimum number of dimensions as variables with dimensions @@ -26,19 +27,18 @@ def compress_all(nc_grids, min_dims=2): ---------- nc_grids : xarray.core.dataset.Dataset Xarray dataset that is intended to be exported as netcdf - + min_dims : integer - The minimum number of dimesnions, in integer value, a variable must have in order + The minimum number of dimesnions, in integer value, a variable must have in order set the netcdf compression encoding. - + Returns ------- nc_grids : xarray.core.dataset.Dataset Xarray dataset with netcdf compression encoding for variables with two (2) or more dimensions - - ''' - + """ + for var in nc_grids: if len(nc_grids[var].dims) >= min_dims: # print("Compressing ", var) @@ -46,10 +46,10 @@ def compress_all(nc_grids, min_dims=2): nc_grids[var].encoding["complevel"] = 4 nc_grids[var].encoding["contiguous"] = False return nc_grids - -def standardize_track_dataset(TrackedFeatures, Mask, Projection = None): - ''' + +def standardize_track_dataset(TrackedFeatures, Mask, Projection=None): + """ Combine a feature mask with the feature data table into a common dataset. returned by tobac.themes.tobac_v1.segmentation @@ -64,19 +64,19 @@ def standardize_track_dataset(TrackedFeatures, Mask, Projection = None): Projection is an xarray DataArray TODO: Add metadata attributes - + Parameters ---------- TrackedFeatures : xarray.core.dataset.Dataset xarray dataset of tobac Track information, the xarray dataset returned by tobac.tracking.linking_trackpy - + Mask: xarray.core.dataset.Dataset xarray dataset of tobac segmentation mask information, the xarray dataset returned by tobac.segmentation.segmentation - - + + Projection : xarray.core.dataarray.DataArray, default = None - array.DataArray of the original input dataset (gridded nexrad data for example). + array.DataArray of the original input dataset (gridded nexrad data for example). If using gridded nexrad data, this can be input as: data['ProjectionCoordinateSystem'] An example of the type of information in the dataarray includes the following attributes: latitude_of_projection_origin :29.471900939941406 @@ -90,116 +90,153 @@ def standardize_track_dataset(TrackedFeatures, Mask, Projection = None): longitude_of_prime_meridian :0.0 false_easting :0.0 false_northing :0.0 - + Returns - ------- - + ------- + ds : xarray.core.dataset.Dataset xarray dataset of merged Track and Segmentation Mask datasets with renamed variables. - - ''' + + """ feature_standard_names = { # new variable name, and long description for the NetCDF attribute - 'frame':('feature_time_index', - 'positional index of the feature along the time dimension of the mask, from 0 to N-1'), - 'hdim_1':('feature_hdim1_coordinate', - 'position of the feature along the first horizontal dimension in grid point space; a north-south coordinate for dim order (time, y, x).' - 'The numbering is consistent with positional indexing of the coordinate, but can be' - 'fractional, to account for a centroid not aligned to the grid.'), - 'hdim_2':('feature_hdim2_coordinate', - 'position of the feature along the second horizontal dimension in grid point space; an east-west coordinate for dim order (time, y, x)' - 'The numbering is consistent with positional indexing of the coordinate, but can be' - 'fractional, to account for a centroid not aligned to the grid.'), - 'idx':('feature_id_this_frame',), - 'num':('feature_grid_cell_count', - 'Number of grid points that are within the threshold of this feature'), - 'threshold_value':('feature_threshold_max', - "Feature number within that frame; starts at 1, increments by 1 to the number of features for each frame, and resets to 1 when the frame increments"), - 'feature':('feature_id', - "Unique number of the feature; starts from 1 and increments by 1 to the number of features"), - 'time':('feature_time','time of the feature, consistent with feature_time_index'), - 'timestr':('feature_time_str','String representation of the feature time, YYYY-MM-DD HH:MM:SS'), - 'projection_y_coordinate':('feature_projection_y_coordinate','y position of the feature in the projection given by ProjectionCoordinateSystem'), - 'projection_x_coordinate':('feature_projection_x_coordinate','x position of the feature in the projection given by ProjectionCoordinateSystem'), - 'lat':('feature_latitude','latitude of the feature'), - 'lon':('feature_longitude','longitude of the feature'), - 'ncells':('feature_ncells','number of grid cells for this feature (meaning uncertain)'), - 'areas':('feature_area',), - 'isolated':('feature_isolation_flag',), - 'num_objects':('number_of_feature_neighbors',), - 'cell':('feature_parent_cell_id',), - 'time_cell':('feature_parent_cell_elapsed_time',), - 'segmentation_mask':('2d segmentation mask',) + "frame": ( + "feature_time_index", + "positional index of the feature along the time dimension of the mask, from 0 to N-1", + ), + "hdim_1": ( + "feature_hdim1_coordinate", + "position of the feature along the first horizontal dimension in grid point space; a north-south coordinate for dim order (time, y, x)." + "The numbering is consistent with positional indexing of the coordinate, but can be" + "fractional, to account for a centroid not aligned to the grid.", + ), + "hdim_2": ( + "feature_hdim2_coordinate", + "position of the feature along the second horizontal dimension in grid point space; an east-west coordinate for dim order (time, y, x)" + "The numbering is consistent with positional indexing of the coordinate, but can be" + "fractional, to account for a centroid not aligned to the grid.", + ), + "idx": ("feature_id_this_frame",), + "num": ( + "feature_grid_cell_count", + "Number of grid points that are within the threshold of this feature", + ), + "threshold_value": ( + "feature_threshold_max", + "Feature number within that frame; starts at 1, increments by 1 to the number of features for each frame, and resets to 1 when the frame increments", + ), + "feature": ( + "feature_id", + "Unique number of the feature; starts from 1 and increments by 1 to the number of features", + ), + "time": ( + "feature_time", + "time of the feature, consistent with feature_time_index", + ), + "timestr": ( + "feature_time_str", + "String representation of the feature time, YYYY-MM-DD HH:MM:SS", + ), + "projection_y_coordinate": ( + "feature_projection_y_coordinate", + "y position of the feature in the projection given by ProjectionCoordinateSystem", + ), + "projection_x_coordinate": ( + "feature_projection_x_coordinate", + "x position of the feature in the projection given by ProjectionCoordinateSystem", + ), + "lat": ("feature_latitude", "latitude of the feature"), + "lon": ("feature_longitude", "longitude of the feature"), + "ncells": ( + "feature_ncells", + "number of grid cells for this feature (meaning uncertain)", + ), + "areas": ("feature_area",), + "isolated": ("feature_isolation_flag",), + "num_objects": ("number_of_feature_neighbors",), + "cell": ("feature_parent_cell_id",), + "time_cell": ("feature_parent_cell_elapsed_time",), + "segmentation_mask": ("2d segmentation mask",), + } + new_feature_var_names = { + k: feature_standard_names[k][0] + for k in feature_standard_names.keys() + if k in TrackedFeatures.variables.keys() } - new_feature_var_names = {k:feature_standard_names[k][0] for k in feature_standard_names.keys() - if k in TrackedFeatures.variables.keys()} - TrackedFeatures = TrackedFeatures.drop(['cell_parent_track_id']) + TrackedFeatures = TrackedFeatures.drop(["cell_parent_track_id"]) # Combine Track and Mask variables. Use the 'feature' variable as the coordinate variable instead of # the 'index' variable and call the dimension 'feature' - ds = xr.merge([TrackedFeatures.swap_dims({'index':'feature'}).drop('index').rename_vars(new_feature_var_names), - Mask]) + ds = xr.merge( + [ + TrackedFeatures.swap_dims({"index": "feature"}) + .drop("index") + .rename_vars(new_feature_var_names), + Mask, + ] + ) # Add the projection data back in if not None in Projection: - ds['ProjectionCoordinateSystem']=Projection + ds["ProjectionCoordinateSystem"] = Projection # Convert the cell ID variable from float to integer - if 'int' not in str(TrackedFeatures.cell.dtype): + if "int" not in str(TrackedFeatures.cell.dtype): # The raw output from the tracking is actually an object array # array([nan, 2, 3], dtype=object) # (and is cast to a float array when saved as NetCDF, I think). # Cast to float. - int_cell = xr.zeros_like(TrackedFeatures.cell, dtype='int64') + int_cell = xr.zeros_like(TrackedFeatures.cell, dtype="int64") - cell_id_data = TrackedFeatures.cell.astype('float64') + cell_id_data = TrackedFeatures.cell.astype("float64") valid_cell = np.isfinite(cell_id_data) valid_cell_ids = cell_id_data[valid_cell] if not (np.unique(valid_cell_ids) > 0).all(): - raise AssertionError('Lowest cell ID cell is less than one, conflicting with use of zero to indicate an untracked cell') - int_cell[valid_cell] = valid_cell_ids.astype('int64') - #ds['feature_parent_cell_id'] = int_cell + raise AssertionError( + "Lowest cell ID cell is less than one, conflicting with use of zero to indicate an untracked cell" + ) + int_cell[valid_cell] = valid_cell_ids.astype("int64") + # ds['feature_parent_cell_id'] = int_cell return ds - -def merge_split(TRACK,distance = 25000,frame_len = 5): - ''' +def merge_split(TRACK, distance=25000, frame_len=5): + """ function to postprocess tobac track data for merge/split cells - - + + Parameters ---------- TRACK : xarray.core.dataset.Dataset xarray dataset of tobac Track information - - distance : float, optional - Distance threshold prior to adding a pair of points into the minimum spanning tree. + + distance : float, optional + Distance threshold prior to adding a pair of points into the minimum spanning tree. Default is 25000 meters. - + frame_len : float, optional - Threshold for the spanning length within which two points can be separated. + Threshold for the spanning length within which two points can be separated. Default is five (5) frames. Returns - ------- + ------- d : xarray.core.dataset.Dataset xarray dataset of tobac merge/split cells with parent and child designations. - - + + Example usage: d = merge_split(Track) ds = standardize_track_dataset(Track, refl_mask) both_ds = xr.merge([ds, d],compat ='override') both_ds = compress_all(both_ds) both_ds.to_netcdf(os.path.join(savedir,'Track_features_merges.nc')) - - ''' - - print('this is an update 3') - track_groups = TRACK.groupby('cell') - cell_ids = {cid:len(v) for cid, v in track_groups.groups.items()} + + """ + + print("this is an update 3") + track_groups = TRACK.groupby("cell") + cell_ids = {cid: len(v) for cid, v in track_groups.groups.items()} id_data = np.fromiter(cell_ids.keys(), dtype=int) count_data = np.fromiter(cell_ids.values(), dtype=int) all_frames = np.sort(np.unique(TRACK.frame)) @@ -208,42 +245,43 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): a_names = list() b_names = list() dist = list() - - if hasattr(TRACK, 'grid_longitude'): - print('is in lat/lonx') + + if hasattr(TRACK, "longitude"): + print("is in lat/lon") for i in id_data: a_pointx = track_groups[i].grid_longitude[-1].values a_pointy = track_groups[i].grid_latitude[-1].values for j in id_data: b_pointx = track_groups[j].grid_longitude[0].values b_pointy = track_groups[j].grid_latitude[0].values - d = geodesic((a_pointy,a_pointx),(b_pointy,b_pointx)).m + d = geodesic((a_pointy, a_pointx), (b_pointy, b_pointx)).m if d <= distance: - a_points.append([a_pointx,a_pointy]) + a_points.append([a_pointx, a_pointy]) b_points.append([b_pointx, b_pointy]) dist.append(d) a_names.append(i) b_names.append(j) else: - + for i in id_data: - #print(i) + # print(i) a_pointx = track_groups[i].projection_x_coordinate[-1].values a_pointy = track_groups[i].projection_y_coordinate[-1].values for j in id_data: b_pointx = track_groups[j].projection_x_coordinate[0].values b_pointy = track_groups[j].projection_y_coordinate[0].values - d = np.linalg.norm(np.array((a_pointx, a_pointy)) - np.array((b_pointx, b_pointy))) + d = np.linalg.norm( + np.array((a_pointx, a_pointy)) - np.array((b_pointx, b_pointy)) + ) if d <= distance: - a_points.append([a_pointx,a_pointy]) + a_points.append([a_pointx, a_pointy]) b_points.append([b_pointx, b_pointy]) dist.append(d) a_names.append(i) b_names.append(j) - id = [] - for i in range(len(dist)-1, -1, -1): + for i in range(len(dist) - 1, -1, -1): if a_names[i] == b_names[i]: id.append(i) a_points.pop(i) @@ -253,25 +291,27 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): b_names.pop(i) else: continue - + g = Graph() for i in np.arange(len(dist)): - g.add_edge(a_names[i], b_names[i],weight=dist[i]) + g.add_edge(a_names[i], b_names[i], weight=dist[i]) tree = minimum_spanning_edges(g) tree_list = list(minimum_spanning_edges(g)) new_tree = [] - for i,j in enumerate(tree_list): + for i, j in enumerate(tree_list): frame_a = np.nanmax(track_groups[j[0]].frame.values) frame_b = np.nanmin(track_groups[j[1]].frame.values) if np.abs(frame_a - frame_b) <= frame_len: new_tree.append(tree_list[i][0:2]) new_tree_arr = np.array(new_tree) - TRACK['cell_parent_track_id'] = np.zeros(len(TRACK['cell'].values)) - cell_id = np.unique(TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))]) - track_id = dict() #same size as number of total merged tracks + TRACK["cell_parent_track_id"] = np.zeros(len(TRACK["cell"].values)) + cell_id = np.unique( + TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))] + ) + track_id = dict() # same size as number of total merged tracks arr = np.array([0]) for p in cell_id: @@ -282,7 +322,7 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): k = np.where(new_tree_arr == p) if len(k[0]) == 0: track_id[p] = [p] - arr = np.append(arr,p) + arr = np.append(arr, p) else: temp1 = list(np.unique(new_tree_arr[k[0]])) temp = list(np.unique(new_tree_arr[k[0]])) @@ -297,112 +337,113 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): if len(temp1) == len(temp): break temp1 = np.array(temp) - + for i in temp1: k2 = np.where(new_tree_arr == i) temp.append(list(np.unique(new_tree_arr[k2[0]]).squeeze())) temp = list(flatten(temp)) temp = list(np.unique(temp)) - arr = np.append(arr,np.unique(temp)) - - track_id[np.nanmax(np.unique(temp))] = list(np.unique(temp)) - - + arr = np.append(arr, np.unique(temp)) - storm_id = [0] #default because we don't track larger storm systems *yet* - print('found storm id') + track_id[np.nanmax(np.unique(temp))] = list(np.unique(temp)) + storm_id = [0] # default because we don't track larger storm systems *yet* + print("found storm id") - track_parent_storm_id = np.repeat(0, len(track_id)) #This will always be zero when we don't track larger storm systems *yet* - print('found track parent storm ids') + track_parent_storm_id = np.repeat( + 0, len(track_id) + ) # This will always be zero when we don't track larger storm systems *yet* + print("found track parent storm ids") track_ids = np.array(list(track_id.keys())) - print('found track ids') - + print("found track ids") - cell_id = list(np.unique(TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))])) - print('found cell ids') + cell_id = list( + np.unique( + TRACK.cell.values.astype(float)[~np.isnan(TRACK.cell.values.astype(float))] + ) + ) + print("found cell ids") cell_parent_track_id = [] for i, id in enumerate(track_id): - - if len(track_id[int(id)]) == 1: + + if len(track_id[int(id)]) == 1: cell_parent_track_id.append(int(id)) else: - cell_parent_track_id.append(np.repeat(int(id),len(track_id[int(id)]))) - + cell_parent_track_id.append(np.repeat(int(id), len(track_id[int(id)]))) cell_parent_track_id = list(flatten(cell_parent_track_id)) - print('found cell parent track ids') + print("found cell parent track ids") feature_parent_cell_id = list(TRACK.cell.values.astype(float)) - print('found feature parent cell ids') + print("found feature parent cell ids") - #This version includes all the feature regardless of if they are used in cells or not. + # This version includes all the feature regardless of if they are used in cells or not. feature_id = list(TRACK.feature.values.astype(int)) - print('found feature ids') + print("found feature ids") - feature_parent_storm_id = np.repeat(0,len(feature_id)) #we don't do storms atm - print('found feature parent storm ids') + feature_parent_storm_id = np.repeat(0, len(feature_id)) # we don't do storms atm + print("found feature parent storm ids") - feature_parent_track_id = [] + feature_parent_track_id = [] feature_parent_track_id = np.zeros(len(feature_id)) for i, id in enumerate(feature_id): cellid = feature_parent_cell_id[i] if np.isnan(cellid): - feature_parent_track_id[i] = -1 + feature_parent_track_id[i] = -1 else: j = np.where(cell_id == cellid) j = np.squeeze(j) trackid = cell_parent_track_id[j] feature_parent_track_id[i] = trackid - - print('found feature parent track ids') + print("found feature parent track ids") storm_child_track_count = [len(track_id)] - print('found storm child track count') + print("found storm child track count") track_child_cell_count = [] - for i,id in enumerate(track_id): + for i, id in enumerate(track_id): track_child_cell_count.append(len(track_id[int(id)])) - print('found track child cell count') - + print("found track child cell count") cell_child_feature_count = [] - for i,id in enumerate(cell_id): + for i, id in enumerate(cell_id): cell_child_feature_count.append(len(track_groups[id].feature.values)) - print('found cell child feature count') + print("found cell child feature count") - storm_child_cell_count = [len(cell_id)] + storm_child_cell_count = [len(cell_id)] storm_child_feature_count = [len(feature_id)] - - storm_dim = 'nstorms' - track_dim = 'ntracks' - cell_dim = 'ncells' - feature_dim = 'nfeatures' - - d = xr.Dataset({ - 'storm_id': (storm_dim, storm_id), - 'track_id': (track_dim, track_ids), - 'track_parent_storm_id': (track_dim, track_parent_storm_id), - 'cell_id': (cell_dim, cell_id), - 'cell_parent_track_id': (cell_dim, cell_parent_track_id), - 'feature_id': (feature_dim, feature_id), - 'feature_parent_cell_id': (feature_dim, feature_parent_cell_id), - 'feature_parent_track_id': (feature_dim, feature_parent_track_id), - 'feature_parent_storm_id': (feature_dim, feature_parent_storm_id), - 'storm_child_track_count': (storm_dim, storm_child_track_count), - 'storm_child_cell_count': (storm_dim, storm_child_cell_count), - 'storm_child_feature_count': (storm_dim, storm_child_feature_count), - 'track_child_cell_count': (track_dim, track_child_cell_count), - 'cell_child_feature_count': (cell_dim, cell_child_feature_count), - }) - d = d.set_coords(['feature_id','cell_id', 'track_id', 'storm_id']) + + storm_dim = "nstorms" + track_dim = "ntracks" + cell_dim = "ncells" + feature_dim = "nfeatures" + + d = xr.Dataset( + { + "storm_id": (storm_dim, storm_id), + "track_id": (track_dim, track_ids), + "track_parent_storm_id": (track_dim, track_parent_storm_id), + "cell_id": (cell_dim, cell_id), + "cell_parent_track_id": (cell_dim, cell_parent_track_id), + "feature_id": (feature_dim, feature_id), + "feature_parent_cell_id": (feature_dim, feature_parent_cell_id), + "feature_parent_track_id": (feature_dim, feature_parent_track_id), + "feature_parent_storm_id": (feature_dim, feature_parent_storm_id), + "storm_child_track_count": (storm_dim, storm_child_track_count), + "storm_child_cell_count": (storm_dim, storm_child_cell_count), + "storm_child_feature_count": (storm_dim, storm_child_feature_count), + "track_child_cell_count": (track_dim, track_child_cell_count), + "cell_child_feature_count": (cell_dim, cell_child_feature_count), + } + ) + d = d.set_coords(["feature_id", "cell_id", "track_id", "storm_id"]) assert len(track_id) == len(track_parent_storm_id) assert len(cell_id) == len(cell_parent_track_id) @@ -411,6 +452,8 @@ def merge_split(TRACK,distance = 25000,frame_len = 5): assert sum(storm_child_cell_count) == len(cell_id) assert sum(storm_child_feature_count) == len(feature_id) assert sum(track_child_cell_count) == len(cell_id) - assert sum([sum(cell_child_feature_count),(len(np.where(feature_parent_track_id < 0)[0]))]) == len(feature_id) - - return d \ No newline at end of file + assert sum( + [sum(cell_child_feature_count), (len(np.where(feature_parent_track_id < 0)[0]))] + ) == len(feature_id) + + return d From cbc80f8faa88d1301b995968769d48d5a9fb11df Mon Sep 17 00:00:00 2001 From: kelcyno <88055123+kelcyno@users.noreply.github.com> Date: Wed, 6 Jul 2022 19:35:35 -0600 Subject: [PATCH 085/187] Allowed optional import of additional packages: geopy, and networkx. Allowed optional import of additional packages: geopy, and networkx. Now a message will print if the package is not available to import. --- tobac/merge_split.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tobac/merge_split.py b/tobac/merge_split.py index c58be5aa..f157852c 100644 --- a/tobac/merge_split.py +++ b/tobac/merge_split.py @@ -8,9 +8,27 @@ """ - -from geopy.distance import geodesic -from networkx import * +try: + import geopy +except ImportError: + geopy = None + +if geopy: + from geopy.distance import geodesic +else: + print("You could be merge/splitting in lat/lons if you had geopy.") + +try: + import networkx +except ImportError: + networkx = None + +if networkx: + from networkx import * +else: + print("Cannot Merge/Split. Please install networkx.") +# from geopy.distance import geodesic +# from networkx import * import numpy as np from pandas.core.common import flatten import xarray as xr From 2c28420708fd15fac36c1e497d1edb3f21e719e3 Mon Sep 17 00:00:00 2001 From: Nils Pfeifer Date: Fri, 8 Jul 2022 11:38:43 +0200 Subject: [PATCH 086/187] fixing typos, rephrase module description, working in suggestions --- tobac/plotting.py | 98 ++++++++++++++--------------------------------- 1 file changed, 29 insertions(+), 69 deletions(-) diff --git a/tobac/plotting.py b/tobac/plotting.py index 8bc4d723..63a3bf61 100644 --- a/tobac/plotting.py +++ b/tobac/plotting.py @@ -1,8 +1,8 @@ """Provide methods for plotting analyzed data. -Plotting routines including both visualizations of the entire cloud field -and detailed visualizations for individual convective cells and their -properties. +Plotting routines including both visualizations for +the entire dataset including all tracks, and detailed +visualizations for individual cells and their properties. References ---------- @@ -54,7 +54,7 @@ def plot_tracks_mask_field_loop( mask : iris.cube.Cube Cube containing mask (int id for tacked volumes, 0 - everywhere else). Ouput of the segmentation step. + everywhere else). Output of the segmentation step. features : pandas.DataFrame Output of the feature detection. @@ -63,13 +63,13 @@ def plot_tracks_mask_field_loop( Not used. Default is None. name : str, optional - First part of the filename. Same for all pngs. If None, + Filename without file extension. Same for all pngs. If None, the name of the field is used. Default is None. plot_dir : str, optional Path where the plots will be saved. Default is './'. - figsize : tupel of float, optional + figsize : tuple of floats, optional Width, height of the plot in inches. Default is (10/2.54, 10/2.54). @@ -186,18 +186,19 @@ def plot_tracks_mask_field( Parameters ---------- track : pandas.DataFrame - One ore more framesof the output of linking_trackpy. + One or more timeframes of a dataframe generated by + linking_trackpy. field : iris.cube.Cube - One frame of the original input data. + One frame/time step of the original input data. mask : iris.cube.Cube - One frame of the Cube containing mask (int id for tacked - volumes 0 everywhere else), output of the segmentation - step. + One frame/time step of the Cube containing mask (int id + for tracked volumes 0 everywhere else), output of the + segmentation step. features : pandas.DataFrame - Output of the feature detection, one or more frames. + Output of the feature detection, one or more frames/time steps. axes : cartopy.mpl.geoaxes.GeoAxesSubplot, optional GeoAxesSubplot to use for plotting. Default is None. @@ -208,11 +209,11 @@ def plot_tracks_mask_field( [long_min, long_max, lat_min, lat_max]. Default is None. plot_outline : bool, optional - Boolean defining wether the outlines of the segments are + Boolean defining whether the outlines of the segments are plotted. Default is True. plot_marker : bool, optional - Boolean defining wether the positions of the features from + Boolean defining whether the positions of the features from the track dataframe are plotted. Default is True. marker_track : str, optional @@ -561,54 +562,9 @@ def plot_mask_cell_track_follow( dpi=300, **kwargs ): - """Make plots for all cells centered around cell. - - With one background field as filling and one background field as - contours. - - Parameters - ---------- - cell : int - Integer id of cell to create masked cube for. - - track - - cog - - features : pandas.DataFrame - Output from trackpy/maketrack. - - mask_total : iris.cube.Cube - - field_contour - - field_filled - - width : int, optional - Default is 10000. - - name : str, optional - Default is 'test'. - - plotdir : str, optional - Path where the plot will be saved. Default is './'. - - file_format : {['png'], ['pdf']}, optional - Default is ['png']. - - dpi : int, optional - Plot resolution. Default is 300. - - **kwargs - - Returns - ------- - none - - Notes - ----- - unsure about features, mask_total - needs more descriptions + """Make plots for all cells centred around cell and with one background field as filling and one background field as contrours + Input: + Output: """ mpl_backend = mpl.get_backend() @@ -2019,7 +1975,7 @@ def map_tracks( Parameters ---------- track : pandas.DataFrame - Dataframe conatining the linked features with a + Dataframe containing the linked features with a column 'cell'. axis_extent : matplotlib.axes, optional @@ -2028,13 +1984,17 @@ def map_tracks( [long_min, long_max, lat_min, lat_max]. Default is None. - figsize : tupel of float, optional + figsize : tuple of floats, optional Width, height of the plot in inches. Default is (10, 10). axes : cartopy.mpl.geoaxes.GeoAxesSubplot, optional GeoAxesSubplot to use for plotting. Default is None. + untracked_cell_value : int or np.nan, optional + Value of untracked cells in track['cell']. + Default is -1. + Returns ------- axes : cartopy.mpl.geoaxes.GeoAxesSubplot @@ -2158,7 +2118,7 @@ def plot_lifetime_histogram_bar( shift=0.5, **kwargs ): - """Plot the liftetime histogram of a the cells as bar plot. + """Plot the liftetime histogram of the cells as bar plot. Parameters ---------- @@ -2216,7 +2176,7 @@ def plot_histogram_cellwise( DataFrame of the features containing the variable as column and a column 'cell'. - bin_edges : int or ndarray, optional + bin_edges : int or ndarray If bin_edges is an int, it defines the number of equal-width bins in the given range. If bins is a sequence, it defines a monotonically increasing @@ -2272,15 +2232,15 @@ def plot_histogram_featurewise( DataFrame of the features containing the variable as column. - bin_edges : int or ndarray, optional + bin_edges : int or ndarray If bin_edges is an int, it defines the number of equal-width bins in the given range. If bins is a sequence, it defines a monotonically increasing array of bin edges, including the rightmost edge. - variable : string + variable : str Column of the DataFrame with the variable on which the - histogram is to be based on. Default is None. + histogram is to be based on. axes : matplotlib.axes.Axes, optional Matplotlib axes to plot on. Default is None. From 653e3d0b36c636d2ed29c2be32f1cef328941ccd Mon Sep 17 00:00:00 2001 From: Sean Freeman Date: Sat, 9 Jul 2022 22:30:59 -0600 Subject: [PATCH 087/187] Added the remainder of the feature detection discussion --- .../feature_detection_filtering.ipynb | 226 ++++++++++++++++++ doc/images/erosion_example.png | Bin 0 -> 101860 bytes doc/images/sigma_threshold_example.png | Bin 0 -> 160101 bytes doc/threshold_detection_parameters.rst | 20 +- 4 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 doc/feature_detection/notebooks/feature_detection_filtering.ipynb create mode 100644 doc/images/erosion_example.png create mode 100644 doc/images/sigma_threshold_example.png diff --git a/doc/feature_detection/notebooks/feature_detection_filtering.ipynb b/doc/feature_detection/notebooks/feature_detection_filtering.ipynb new file mode 100644 index 00000000..fb206537 --- /dev/null +++ b/doc/feature_detection/notebooks/feature_detection_filtering.ipynb @@ -0,0 +1,226 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## *tobac* Feature Detection Filtering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import tobac\n", + "import xarray as xr\n", + "import scipy.ndimage\n", + "import skimage.morphology" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Generate Feature Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we will generate some simple feature data where the features that we want to detect are *higher* values than the surrounding (0)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Dimensions here are time, y, x.\n", + "input_field_arr = np.zeros((1,100,200))\n", + "input_field_arr[0, 15:85, 10:185]=50\n", + "input_field_arr[0, 20:80, 20:80]=100\n", + "input_field_arr[0, 40:60, 125:170] = 100\n", + "input_field_arr[0, 30:40, 30:40]=200\n", + "input_field_arr[0, 50:75, 50:75]=200\n", + "input_field_arr[0, 55:70, 55:70]=300\n", + "\n", + "plt.pcolormesh(input_field_arr[0])\n", + "plt.colorbar()\n", + "plt.title(\"Base data\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# We now need to generate an Iris DataCube out of this dataset to run tobac feature detection. \n", + "# One can use xarray to generate a DataArray and then convert it to Iris, as done here. \n", + "input_field_iris = xr.DataArray(input_field_arr, dims=['time', 'Y', 'X'], coords={'time': [np.datetime64('2019-01-01T00:00:00')]}).to_iris()\n", + "# Version 2.0 of tobac (currently in development) will allow the use of xarray directly with tobac. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Gaussian Filtering (`sigma_threshold` parameter)\n", + "First, we will explore the use of Gaussian Filtering by varying the `sigma_threshold` parameter in *tobac*. Note that when we set the `sigma_threshold` high enough, the right feature isn't detected because it doesn't meet the higher `100` threshold; instead it is considered part of the larger parent feature that contains the high feature." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [50, 100, 150, 200]\n", + "fig, axarr = plt.subplots(2,2, figsize=(10,6))\n", + "sigma_values = [0, 1, 2, 5]\n", + "for sigma_value, ax in zip(sigma_values, axarr.flatten()):\n", + " single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', sigma_threshold=sigma_value)\n", + " \n", + " # This is what tobac sees\n", + " filtered_field = scipy.ndimage.gaussian_filter(input_field_arr[0], sigma=sigma_value)\n", + " color_mesh = ax.pcolormesh(filtered_field)\n", + " plt.colorbar(color_mesh, ax=ax)\n", + " # Plot all features detected\n", + " ax.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + " ax.legend()\n", + " if sigma_value == 0:\n", + " sigma_val_str = \"0 (off)\"\n", + " else:\n", + " sigma_val_str = \"{0}\".format(sigma_value)\n", + " ax.set_title(\"sigma_threshold= \"+ sigma_val_str)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Erosion (`n_erosion_threshold` parameter)\n", + "Next, we will explore the use of the erosion filtering by varying the `n_erosion_threshold` parameter in *tobac*. This erosion process only occurrs *after* masking the values greater than the threshold, so it's easiest to see this when detecting on a single threshold. As you can see, increasing the `n_erosion_threshold` parameter reduces the size of each of our features. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "thresholds = [100]\n", + "fig, axarr = plt.subplots(2,2, figsize=(10,6))\n", + "erosion_values = [0, 5, 10, 15]\n", + "for erosion, ax in zip(erosion_values, axarr.flatten()):\n", + " single_threshold_features = tobac.feature_detection_multithreshold(field_in = input_field_iris, dxy = 1000, threshold=thresholds, target='maximum', n_erosion_threshold=erosion)\n", + " \n", + " # Create our mask- this is what tobac does internally for each threshold.\n", + " tobac_mask = 1*(input_field_arr[0] >= thresholds[0])\n", + " \n", + " if erosion > 0:\n", + " # This is the parameter for erosion that gets passed to the scikit-image library. \n", + " footprint = np.ones((erosion, erosion))\n", + " # This is what tobac sees after erosion. \n", + " filtered_mask = skimage.morphology.binary_erosion(tobac_mask, selem).astype(np.int64)\n", + " else:\n", + " filtered_mask = tobac_mask\n", + "\n", + " color_mesh = ax.pcolormesh(filtered_mask)\n", + " # Plot all features detected\n", + " ax.scatter(x=single_threshold_features['hdim_2'].values, y=single_threshold_features['hdim_1'].values, color='r', label=\"Detected Features\")\n", + " ax.legend()\n", + " if erosion == 0:\n", + " sigma_val_str = \"0 (off)\"\n", + " else:\n", + " sigma_val_str = \"{0}\".format(erosion)\n", + " ax.set_title(\"n_erosion_threshold= \"+ sigma_val_str)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:tobac_stable]", + "language": "python", + "name": "conda-env-tobac_stable-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.5" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "25a19fbe0a9132dfb9279d48d161753c6352f8f9478c2e74383d340069b907c3" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/images/erosion_example.png b/doc/images/erosion_example.png new file mode 100644 index 0000000000000000000000000000000000000000..cb6ecfa46439ab6dfab9fd3e443021fad4ece7d3 GIT binary patch literal 101860 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

T6W-XHYj!CPGq;1)JJh(z{)3|&co9C)e$sm_ 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 literal 0 HcmV?d00001 diff --git a/doc/images/sigma_threshold_example.png b/doc/images/sigma_threshold_example.png new file mode 100644 index 0000000000000000000000000000000000000000..c1f83fa9be5349f6aa7ea80b57a4fe280117f279 GIT binary patch literal 160101 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