-
Notifications
You must be signed in to change notification settings - Fork 16
Adding feature tracking operator #2148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Adam Gainford (A-Gainford)
wants to merge
21
commits into
main
Choose a base branch
from
simple-track
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
f479cb9
added simple-track operator and tests
A-Gainford 4f56efb
added example tracking recipe
A-Gainford 7d89cbb
added temporary feature cbar definitons (limits to be set dynamically)
A-Gainford 7fa3a8d
Merge remote-tracking branch 'origin/main' into simple-track
A-Gainford 98f39d9
added simple-track dependency to pyproject and env.yml
A-Gainford b8b1f6a
removed unnecessary logging, added set_under option to feature plot
A-Gainford ca3eea6
Merge branch 'main' into simple-track
jfrost-mo ecac8cc
added restriction of xy grid spacing on input cube
A-Gainford 038d2c0
changed feature colorbar properties
A-Gainford 356696b
Update conda lockfiles
A-Gainford 9e4e484
Update src/CSET/operators/__init__.py
A-Gainford 4326eb8
Update src/CSET/operators/feature.py
A-Gainford e66ef41
Update .gitignore
A-Gainford 7308be8
updated docstrings
A-Gainford 1f22fcb
Update tests/operators/test_feature.py
A-Gainford 31bea32
Update src/CSET/recipes/example_recipes/example_feature_track.yaml
A-Gainford 21f3deb
Update tests/operators/test_feature.py
A-Gainford 782137c
added custom feature colorbar to _colormaps
A-Gainford b2e8b7f
replaced deprecated cmap setting method, moved to _colormaps
A-Gainford a2de8cf
added new line to gitignore
A-Gainford 080988f
removed feature entries from _colorbar_definition.json
A-Gainford File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,7 @@ dependencies = [ | |
| "scikit-image", | ||
| "scores", | ||
| "dask", | ||
| "simple-track", | ||
| "xarray", | ||
| ] | ||
|
|
||
|
|
||
|
A-Gainford marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
A-Gainford marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| # © Crown copyright, Met Office (2022-2025) and CSET contributors. | ||
|
A-Gainford marked this conversation as resolved.
Outdated
|
||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Operators for identifying and tracking features.""" | ||
|
|
||
| import logging | ||
| import os | ||
|
|
||
| import iris | ||
| import numpy as np | ||
| from simpletrack.track import Tracker | ||
|
|
||
|
|
||
| def track( | ||
| cube: iris.cube.Cube, | ||
| threshold: float, | ||
| under_threshold: bool = False, | ||
| min_size: int = 4, | ||
| retain_lifetime_on_split: bool = True, | ||
| tracking_nbhood: int = 5, | ||
| overlap_threshold: float = 0.3, | ||
| save_data: bool = False, | ||
| ): | ||
| """Track features between subsequent timesteps. | ||
|
|
||
| Parameters | ||
| ---------- | ||
|
A-Gainford marked this conversation as resolved.
|
||
| threshold: float | ||
| The threshold value for feature detection. | ||
| under_threshold: bool, optional | ||
| If set to True, features are identified where the data is below the threshold. | ||
| If set to False, features are identified where the data is above the threshold. | ||
| Default is False. | ||
| min_size: int, optional | ||
| The minimum number of contiguous grid points required for a feature to be tracked. | ||
| Default is 4. | ||
| retain_lifetime_on_split: bool, optional | ||
| If set to True, the lifetime of a feature is retained when it splits into | ||
| multiple features. If set to False, the lifetime is reset when a feature splits. | ||
| Default is True. | ||
| tracking_nbhood: int, optional | ||
| The size of the neighbourhood used for tracking features between timesteps. | ||
| This dictates the maximum pixel radius from a feature centroid at which new features could | ||
| reasonably be spawned. | ||
| Default is 5. | ||
| overlap_threshold: float, optional | ||
| The minimum overlap required between features in consecutive timesteps for | ||
| them to be considered the same feature. | ||
| Default is 0.3. | ||
| save_data: bool, optional | ||
| If set to True, all tracking data is saved to disk for further analysis (including csv | ||
| and txt files containing feature properties that are not returned in output cubes). | ||
| Default is False. | ||
|
|
||
| Returns | ||
| ------- | ||
| tracking_cubes: iris.cube.CubeList | ||
| A list of iris cubes containing tracking data, including feauture ID, lifetime, | ||
| and locations of initiating features. | ||
|
|
||
| Notes | ||
| ----- | ||
| This operator uses the Simple-Track package to track features between timesteps. Simple-Track is a | ||
| data-agnostic, threshold-based object tracking algorithm for 2D data. Features are tracked between | ||
| consecutive frames of data by projecting feature fields onto common timeframes and matching | ||
| between them based on the degree of overlap. Matched features retain the same identification | ||
| between all tracked fields, while new features are assigned a unique label. | ||
| Thus, Simple-Track compiles comprehensive information about feature merging, splitting, accretion, | ||
| initiation and dissipation. | ||
|
|
||
| Currently outputs three cubes containing the following data: | ||
| "feature_id": | ||
| A 2D field containing the unique label assigned to each feature, which is retained | ||
| if the feature is tracked across multiple timesteps. This cube can be used as a mask | ||
| to identify the location of the tracked feature throughout the evaluation period. | ||
| "feature_lifetime": | ||
| A 2D field containing the lifetime of each feature in terms of the number of | ||
| timesteps it has been tracked for. This cube can be used to distinguish between | ||
| mature and fresh features. | ||
| "feature_init": | ||
| A 2D binary field indicating the location of newly initiated features at each timestep. | ||
| These features are identified as having a lifetime of 1 AND have initiated sufficiently | ||
| far from other, existing features that they are not considered to have spawed from them. | ||
|
|
||
| Links | ||
| ---------- | ||
| .. https://github.com/ParaChute-UK/simple-track | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> tracking_cubes = feature.track(threshold=2) | ||
| >>> lifetime_cube = tracking_cubes.extract_cube("feature_lifetime") | ||
| # Plot the final timestep of lifetime cube. This will show | ||
| # the lifetime of features that have been tracked for multiple previous | ||
| # timesteps, as well as new features that have just been initiated. | ||
| >>> iplt.pcolormesh(lifetime_cube[-1,:,:],cmap=mpl.cm.bwr) | ||
| >>> plt.gca().coastlines('10m') | ||
| >>> plt.clim(-5,5) | ||
| >>> plt.colorbar() | ||
| >>> plt.show() | ||
|
|
||
| """ | ||
| # Setup config | ||
| tracker_config = { | ||
| "FEATURE": { | ||
| "threshold": threshold, | ||
| "under_threshold": under_threshold, | ||
| "min_size": min_size, | ||
| }, | ||
| "TRACKING": { | ||
| "retain_lifetime_on_split": retain_lifetime_on_split, | ||
| "overlap_nbhood": tracking_nbhood, | ||
| "overlap_threshold": overlap_threshold, | ||
| }, | ||
| "OUTPUT": { | ||
| "save_data": save_data, | ||
| "experiment_name": "feature_tracking", | ||
| "path": f"{os.getcwd()}/tracking_data", | ||
| }, | ||
| } | ||
| logging.debug(f"Tracker config: {tracker_config}") | ||
|
|
||
| # Get cube data into a dict to pass to Tracker | ||
| times = cube.coord("time").points | ||
| time_units = cube.coord("time").units | ||
| times_dt = [time_units.num2pydate(t) for t in times] | ||
| cube_dict = { | ||
| time: cube_slice.data | ||
| for time, cube_slice in zip(times_dt, cube.slices_over("time"), strict=True) | ||
| } | ||
|
|
||
| # Run tracking, returning Timeline object | ||
| timeline = Tracker(tracker_config).run(cube_dict) | ||
| logging.debug("Tracking completed") | ||
|
|
||
| # Use input cube as template to make returned cube | ||
| # By iterating over all cube times, this will ensure all data is present | ||
| # If a Frame at the given time is not contained in the timeline, error is raised | ||
| output_type_and_methods = { | ||
| "lifetime": { | ||
| "getter": "lifetime_field", | ||
| "cube_name": "feature_lifetime", | ||
| }, | ||
| "feature": { | ||
| "getter": "feature_field", | ||
| "cube_name": "feature_id", | ||
| }, | ||
| "init": { | ||
| "getter": "get_init_field", | ||
| "cube_name": "feature_init", | ||
| }, | ||
| } | ||
|
|
||
| tracking_cubelist = iris.cube.CubeList() | ||
| for output_type in output_type_and_methods: | ||
| tracking_data = [] | ||
| for time in times_dt: | ||
| frame = timeline.get_frame(time) | ||
| getter = getattr(frame, output_type_and_methods[output_type]["getter"]) | ||
| if callable(getter): | ||
| tracking_data.append(getter()) | ||
| else: | ||
| tracking_data.append(getter) | ||
|
|
||
| # Convert to numpy arrays | ||
| tracking_data = np.stack(tracking_data, axis=0) | ||
|
|
||
| # Create cubes | ||
| tracking_cube = cube.copy(data=tracking_data) | ||
| tracking_cube.long_name = output_type_and_methods[output_type]["cube_name"] | ||
| tracking_cube.standard_name = None | ||
| tracking_cube.var_name = None | ||
| tracking_cube.units = "1" | ||
| tracking_cubelist.append(tracking_cube) | ||
|
|
||
| return tracking_cubelist | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
src/CSET/recipes/example_recipes/example_feature_track.yaml
|
A-Gainford marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| category: Quick Look | ||
| title: Example running cell tracking and plotting spatial plots | ||
|
A-Gainford marked this conversation as resolved.
Outdated
|
||
| description: | | ||
| Uses the feature.track operator to identify and track features in a cube with "time" series coordinate, | ||
| and then plots the lifetime of the identified features as a spatial plot. | ||
|
|
||
| steps: | ||
| - operator: read.read_cubes | ||
| file_paths: $INPUT_PATHS | ||
|
|
||
| - operator: filters.filter_cubes | ||
| constraint: | ||
| operator: constraints.generate_var_constraint | ||
| varname: precipitation_flux | ||
|
|
||
| - operator: feature.track | ||
| threshold: 3 | ||
| save_data: False # Whether to save raw tracking data for further analysis | ||
|
|
||
| # Filter tracking cubelist to just one of "feature_lifetime", "feature_id" or "feature_init" | ||
| - operator: filters.filter_cubes | ||
| constraint: | ||
| operator: constraints.generate_var_constraint | ||
| varname: feature_lifetime | ||
|
|
||
| - operator: plot.spatial_pcolormesh_plot | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.