@@ -1121,39 +1121,34 @@ def feature_detection_multithreshold_timestep(
1121
1121
elif i_threshold == 0 :
1122
1122
regions_old = regions_i
1123
1123
1124
- if statistic :
1125
- # reconstruct the labeled regions based on the regions dict
1126
- labels = np .zeros (track_data .shape )
1127
- labels = labels .astype (int )
1128
- for key in regions_old .keys ():
1129
- labels .ravel ()[regions_old [key ]] = key
1130
- # apply function to get statistics based on labeled regions and functions provided by the user
1131
- # the feature dataframe is updated by appending a column for each metric
1132
- if statistics_unsmoothed :
1133
- features_thresholds = get_statistics (
1134
- features_thresholds ,
1135
- labels ,
1136
- data_i .core_data (),
1137
- statistic = statistic ,
1138
- index = np .unique (labels [labels > 0 ]),
1139
- id_column = "idx" ,
1140
- )
1141
- else :
1142
- features_thresholds = get_statistics (
1143
- features_thresholds ,
1144
- labels ,
1145
- track_data ,
1146
- statistic = statistic ,
1147
- index = np .unique (labels [labels > 0 ]),
1148
- id_column = "idx" ,
1149
- )
1150
-
1151
1124
logging .debug (
1152
1125
"Finished feature detection for threshold "
1153
1126
+ str (i_threshold )
1154
1127
+ " : "
1155
1128
+ str (threshold_i )
1156
1129
)
1130
+
1131
+ if statistic :
1132
+ # reconstruct the labeled regions based on the regions dict
1133
+ labels = np .zeros (track_data .shape )
1134
+ labels = labels .astype (int )
1135
+ for key in regions_old .keys ():
1136
+ labels .ravel ()[regions_old [key ]] = key
1137
+ # apply function to get statistics based on labeled regions and functions provided by the user
1138
+ # the feature dataframe is updated by appending a column for each metric
1139
+
1140
+ # select which data to use according to statistics_unsmoothed option
1141
+ stats_data = data_i .core_data () if statistics_unsmoothed else track_data
1142
+
1143
+ features_thresholds = get_statistics (
1144
+ features_thresholds ,
1145
+ labels ,
1146
+ stats_data ,
1147
+ statistic = statistic ,
1148
+ index = np .unique (labels [labels > 0 ]),
1149
+ id_column = "idx" ,
1150
+ )
1151
+
1157
1152
return features_thresholds
1158
1153
1159
1154
@@ -1296,6 +1291,9 @@ def feature_detection_multithreshold(
1296
1291
if detect_subset is not None and ndim_time in detect_subset :
1297
1292
raise NotImplementedError ("Cannot subset on time" )
1298
1293
1294
+ # Remember if dz is set and not vertical coord for min distance filtering
1295
+ use_dz_for_filtering = dz is not None
1296
+
1299
1297
if is_3D :
1300
1298
# We need to determine the time axis so that we can determine the
1301
1299
# vertical axis in each timestep if vertical_axis is not none.
@@ -1395,30 +1393,7 @@ def feature_detection_multithreshold(
1395
1393
statistic = statistic ,
1396
1394
statistics_unsmoothed = statistics_unsmoothed ,
1397
1395
)
1398
- # check if list of features is not empty, then merge features from different threshold
1399
- # values into one DataFrame and append to list for individual timesteps:
1400
- if not features_thresholds .empty :
1401
- hdim1_ax , hdim2_ax = internal_utils .find_hdim_axes_3D (
1402
- field_in , vertical_coord = vertical_coord
1403
- )
1404
- hdim1_max = field_in .shape [hdim1_ax ] - 1
1405
- hdim2_max = field_in .shape [hdim2_ax ] - 1
1406
- # Loop over DataFrame to remove features that are closer than distance_min to each
1407
- # other:
1408
- if min_distance > 0 :
1409
- features_thresholds = filter_min_distance (
1410
- features_thresholds ,
1411
- dxy = dxy ,
1412
- dz = dz ,
1413
- min_distance = min_distance ,
1414
- z_coordinate_name = vertical_coord ,
1415
- target = target ,
1416
- PBC_flag = PBC_flag ,
1417
- min_h1 = 0 ,
1418
- max_h1 = hdim1_max ,
1419
- min_h2 = 0 ,
1420
- max_h2 = hdim2_max ,
1421
- )
1396
+
1422
1397
list_features_timesteps .append (features_thresholds )
1423
1398
1424
1399
logging .debug (
@@ -1440,9 +1415,41 @@ def feature_detection_multithreshold(
1440
1415
)
1441
1416
else :
1442
1417
features = add_coordinates (features , field_in )
1418
+
1419
+ # Loop over DataFrame to remove features that are closer than distance_min to each
1420
+ # other:
1421
+ filtered_features = []
1422
+ if min_distance > 0 :
1423
+ hdim1_ax , hdim2_ax = internal_utils .find_hdim_axes_3D (
1424
+ field_in , vertical_coord = vertical_coord
1425
+ )
1426
+ hdim1_max = field_in .shape [hdim1_ax ] - 1
1427
+ hdim2_max = field_in .shape [hdim2_ax ] - 1
1428
+
1429
+ for _ , features_frame in features .groupby ("frame" ):
1430
+ filtered_features .append (
1431
+ filter_min_distance (
1432
+ features_frame ,
1433
+ dxy = dxy ,
1434
+ dz = dz if use_dz_for_filtering else None ,
1435
+ min_distance = min_distance ,
1436
+ z_coordinate_name = (
1437
+ None if use_dz_for_filtering else vertical_coord
1438
+ ),
1439
+ target = target ,
1440
+ PBC_flag = PBC_flag ,
1441
+ min_h1 = 0 ,
1442
+ max_h1 = hdim1_max ,
1443
+ min_h2 = 0 ,
1444
+ max_h2 = hdim2_max ,
1445
+ )
1446
+ )
1447
+ features = pd .concat (filtered_features , ignore_index = True )
1448
+
1443
1449
else :
1444
1450
features = None
1445
1451
logging .debug ("No features detected" )
1452
+
1446
1453
logging .debug ("feature detection completed" )
1447
1454
return features
1448
1455
@@ -1512,47 +1519,47 @@ def filter_min_distance(
1512
1519
pandas DataFrame
1513
1520
features after filtering
1514
1521
"""
1522
+ # Optional coordinate names are not yet implemented, set to defaults here:
1515
1523
if dxy is None :
1516
1524
raise NotImplementedError ("dxy currently must be set." )
1517
1525
1518
- # if PBC_flag != "none":
1519
- # raise NotImplementedError("We haven't yet implemented PBCs into this.")
1520
-
1521
- # if we are 3D, the vertical dimension is in features. if we are 2D, there
1522
- # is no vertical dimension in features.
1523
- is_3D = "vdim" in features
1524
-
1525
- if is_3D and dz is None :
1526
- z_coordinate_name = internal_utils .find_dataframe_vertical_coord (
1527
- features , z_coordinate_name
1528
- )
1529
-
1530
1526
# Check if both dxy and their coordinate names are specified.
1531
1527
# If they are, warn that we will use dxy.
1532
- if dxy is not None and (
1533
- x_coordinate_name in features and y_coordinate_name in features
1534
- ):
1528
+ elif x_coordinate_name in features and y_coordinate_name in features :
1535
1529
warnings .warn (
1536
1530
"Both " + x_coordinate_name + "/" + y_coordinate_name + " and dxy "
1537
1531
"set. Using constant dxy. Set dxy to None if you want to use the "
1538
1532
"interpolated coordinates, or set `x_coordinate_name` and "
1539
1533
"`y_coordinate_name` to None to use a constant dxy."
1540
1534
)
1535
+ y_coordinate_name = "hdim_1"
1536
+ x_coordinate_name = "hdim_2"
1537
+ # If dxy only, use hdim_1, hdim_1 as default horizontal dimensions
1538
+ else :
1539
+ y_coordinate_name = "hdim_1"
1540
+ x_coordinate_name = "hdim_2"
1541
1541
1542
- # Check and if both dz is specified and altitude is available, warn that we will use dz.
1543
- if is_3D and (dz is not None and z_coordinate_name in features ):
1544
- warnings .warn (
1545
- "Both "
1546
- + z_coordinate_name
1547
- + " and dz available to filter_min_distance; using constant dz. "
1548
- "Set dz to none if you want to use altitude or set `z_coordinate_name` to None to use "
1549
- "constant dz."
1550
- )
1551
-
1552
- # As optional coordinate names are not yet implemented, set to defaults here:
1553
- z_coordinate_name = "vdim"
1554
- y_coordinate_name = "hdim_1"
1555
- x_coordinate_name = "hdim_2"
1542
+ # if we are 3D, the vertical dimension is in features
1543
+ is_3D = "vdim" in features
1544
+ if is_3D :
1545
+ if dz is None :
1546
+ # Find vertical coord name and set dz to 1
1547
+ z_coordinate_name = internal_utils .find_dataframe_vertical_coord (
1548
+ variable_dataframe = features , vertical_coord = z_coordinate_name
1549
+ )
1550
+ dz = 1
1551
+ else :
1552
+ # Use dz, warn if both are set
1553
+ if z_coordinate_name is not None :
1554
+ warnings .warn (
1555
+ "Both "
1556
+ + z_coordinate_name
1557
+ + " and dz available to filter_min_distance; using constant dz. "
1558
+ "Set dz to none if you want to use altitude or set `z_coordinate_name` to None to use "
1559
+ "constant dz." ,
1560
+ UserWarning ,
1561
+ )
1562
+ z_coordinate_name = "vdim"
1556
1563
1557
1564
if target not in ["minimum" , "maximum" ]:
1558
1565
raise ValueError (
0 commit comments