Skip to content

Adding velocity azimuth display plots#739

Open
bart1 wants to merge 26 commits intomasterfrom
vad
Open

Adding velocity azimuth display plots#739
bart1 wants to merge 26 commits intomasterfrom
vad

Conversation

@bart1
Copy link
Copy Markdown
Collaborator

@bart1 bart1 commented Sep 2, 2025

In this pull request i want to introduce a VAD plotting function. It helps to explain and asses the fit of velocities in a vp. There is considerable flexibility in formatting the plots. Here are some example plots:

devtools::load_all("~/bioRad")
#> ℹ Loading bioRad
#> Welcome to bioRad version 0.11.0.9000
#> 
#> using vol2birdR version 1.1.1 (MistNet not installed)
pvolfile <- system.file("extdata", "volume.h5", package = "bioRad")
example_pvol <- read_pvolfile(pvolfile)
vad(example_pvol,
  range = c(5000, 30000), height = c(200, 400),
  point_geom_args = list(size = 0.5)
)

vp <- calculate_vp(pvolfile)
#> Running vol2birdSetUp
#> Warning: radial velocities will be dealiased...
vad(example_pvol,
  vp = vp, height = c(400, 1200),
  point_geom = ggplot2::geom_bin2d,
  annotate = "sdvvp: {round(sd_vvp,2)} [m/s]",
) + ggplot2::scale_fill_viridis_c()

@adokter adokter added this to the 0.12.0 milestone Sep 4, 2025
@adokter
Copy link
Copy Markdown
Owner

adokter commented Sep 4, 2025

Thanks @bart1, this will be helpful.

@bart1
Copy link
Copy Markdown
Collaborator Author

bart1 commented Sep 15, 2025

I have updated the documentation with suggestions from @CeciliaNilsson709, thanks!

@bart1 bart1 marked this pull request as ready for review September 15, 2025 12:28
@bart1 bart1 requested a review from adokter September 15, 2025 12:28
Comment thread R/vad.R Outdated
Comment thread R/vad.R Outdated
Comment thread R/vad.R Outdated
Comment thread R/vad.R Outdated
Comment thread R/vad.R Outdated
max_bin_height = hgt + vp$attributes$where$interval,
height_bin = bin
)),
fun = function(x, v, a) cos((x - a) / 180 * pi) * v,
Copy link
Copy Markdown
Owner

@adokter adokter Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an issue with wanting to plot data from multiple elevation scans using the same cosine function as done here. Because the radar measures radial velocity, there should be cos(elevation_angle) factor in this plotting function. E.g. for the extreme case where the radar looks vertically up in a bird bath scan, you will have radial velocities all zero for any speed and direction. Therefore you cannot just plot the same curve to data from different elevation scans.

It seems more natural for this function to not act on a polar volume, but on a scan - in general it would be natural to have a method for a scan object as well.

If you really want a method that plots all data for a volume, you might be able to "correct" the raw radial velocity data for the reduction in radial velocity due to the elevation angle. Then you might be able to plot multiple scans in one graph - but we would have to very carefully document what is actually happening and that the user is no longer looking at pristine raw data, and I see some complications in doing such a correction for velocity data that is folded. For a 20 degree elevation angle scan you are looking at a correction of more than 5%. You probably want some upper/lower limits on elevation angle as well, for example to exclude extremes like the bird bath scan, or other very steep elevation angles that need a lot of correction.

Copy link
Copy Markdown
Owner

@adokter adokter Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the function we fit the radial velocity field to, with theta elevation angle, phi azimuth, and u,v the components of the horizontal speed and w the vertical speed:
VRAD = w sin(theta) + u cos(theta) sin(phi) + v cos(theta) cos(phi)
For birds you can usually assume w is zero (but we do estimate it), so it's just a simple extra cos(theta) factor that needs to be added when plotting a scan. Or alternatively, when aligning multiple scan into one plot, the VRAD values needed to be divided by cos(theta) - but would have to give folding into the Nyquist interval more thought

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'm thinking a bit more about this. Maybe we can add a vad.scan method to include these corrections in the vp curve? That would keep the flexibility to plot everything for a quick look at a pvol and do the correct thing at a scan level. I agree that correcting the VRAD values would be quite difficult to comprehend for users due to the nyquist folding. What do you think?

Comment thread R/vad.R
#' deviations from the sine function.
#'
#' @param x A polar volume from which range gates are extracted.
#' @param ... Currently not used.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need it if it is not used?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To prevent other arguments from being used unnamed. With this setup you are forced to make all other arguments explicit. I generally like this setup as it avoids error with the calling order of arguments

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok - we don't use this in other functions. Could you add an issue to discuss if we should use this consistently throughout the package, so others can weigh in as well

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made an issue: #746

Comment thread R/vad.R Outdated
Comment thread R/vad.R Outdated
Comment thread R/vad.R Outdated
Comment thread R/vad.R Outdated
@bart1
Copy link
Copy Markdown
Collaborator Author

bart1 commented Sep 18, 2025

@adokter I now included different ways to correct the cosine error (please check). It also warns for problems with the cosine (e.g. when large errors are likely to occur). It also uses a corrected vp curve for a single scan.

@bart1 bart1 requested a review from adokter September 18, 2025 10:21
Copy link
Copy Markdown
Owner

@adokter adokter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fully finished review, but these are the most important points, let me know what you think

Comment thread R/vad.R
#' deviations from the sine function.
#'
#' @param x A polar volume from which range gates are extracted.
#' @param ... Currently not used.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok - we don't use this in other functions. Could you add an issue to discuss if we should use this consistently throughout the package, so others can weigh in as well

Comment thread R/vad.R
Comment thread R/vad.R
Comment thread R/vad.R
#' Use `NULL` if no annotation is desired.
#' @param annotation_color The color used for the `vp` annotations and line.
#' @param annotation_size The text size used for the annotation.
#' @param cosine_correction A character option to select what approach should be taken to correct for the fact
Copy link
Copy Markdown
Owner

@adokter adokter Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the word cosine_correction is misleading - there is no ture correction, the cosine is simply part of the model fit, so I think we shouldn't confuse the reader with there being a correction applied. We should just plot the fit and the datapoints, nothing more.

The main sticking point remains how to plot multiple sweeps into one plot. You either need to adjust the positions of the points to "absorb" the part of the model variation with elevation angle (what you call "range_gates"), so you can keep one fitting line, or - alternatively - plot the points as they are but include multiple fit lines for each sweep (coloring points and fitting lines by sweep number).

To me the more natural user option would be to be able to toggle between those two. The user argument would then by align_scans = TRUE/FALSE, with the FALSE case plotting multiple fit lines (not requiring any adjustments of the points), and the TRUE case adjusting the points and showing only one line.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your probably right the current name is unfortunate. However I would prefer to also keep a version that does not do changes (for example as the corrections might be incorrect for nyquist folded data). So I need to think about the best way to do that with an align_scans argument.

Comment thread R/vad.R Outdated
vad.pvol <- function(x, vp = NULL, ...,
range_min = NULL, range_max = NULL,
alt_min = NULL, alt_max = NULL,
range_gate_filter =
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this argument is overly complicated. Why not give single arguments rho_hv (same name used in calculate_vp() and eta_max to set the two thresholds. I realize this gives you less flexibility, but I think it's better to keep things simple for the user.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you then you would still need several extra arguments like reflectivity_quantity and rho_quantity. Maybe a personal preference but I tend to want to keep the number of arguments limited. Using explicit thresholds would also reduce the option to use other filters (e.g. if you have a bird classifier or use differential reflectivity). I think for a function mostly useful for exploration this flexibility is valuable

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, but it's very hard to understand for users what this does and how to modify it - and therefore prone to errors if people do try to change this. Could this be included as a customizable filter function to be included in the package, for which you can change the reflectivity and rho quantities and thresholds? There should be a way to simplify this

dplyr::if_all(utils::head(dplyr::matches(c("^DBZ$", "^DBZH$", "^DBZV$", "^TH$", "^TV$")), 1),
                         \(dbz) dbz_to_eta(dbz, !!x$attributes$how$wavelength) < 36000
                       ) &
                         dplyr::if_any(dplyr::matches("RHOHV"), \(rhohv) rhohv < .95)

Comment thread R/vad.R
if (length(elangles) != 1 && cosine_correction == "vp") {
warning("The data to plot is based on multiple elevation angles. To correct the `vp` data one elevation angle is needed, it is therefore averaged resulting in a inperfect correction.")
}
mean_elangle_cos <- cospi(mean(elangles) / 180) # TODO should this be a weighted mean?
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of the mean, points should be corrected according to the elevation scan they belong to, so different cosine corrections for each of the elevation scans of the volume

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is done when you use cosine_correction="range_gates", then the points/range_gates are corrected. Here I adjust the sine curve. What could be improved here I now see is take the mean elevation angle per panel/height bin that we plot. Alternatively you would have to include a sine for each elevation angle that occurs in a height bin but that gets messy. For those small difference I would prefer one sine curve. Alternatively you use cosine_correction="range_gates" to avoid this averaging

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it's a little messy, I feel its more accurate than a mean, which we would have to explain carefully. You could try different colors for the points and sine curves of different sweeps, maybe that looks cleaner?

Comment thread R/vad.R Outdated
Comment thread R/vad.R Outdated
annotate = "{round(ff,1)} m/s, {round(dd)}\u00B0",
annotation_size = 4,
annotation_color = "red",
cosine_correction = c("none", "vp", "range_gates")) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep it simple for the user, why don't we use what you now have under "vp" when plotting a single scan (so taking account the cosine in the plotted sine curve), and when you plot a "pvol" what you now have under "range_gates". Alternatively, you could plot multiple sine curves in different colors, and highlight points in the same colors to indicate which scan they belong too.

Comment thread R/vad.R
#'
#' As for the radial velocity to plot the first of `VRAD`, `VRADH` or `VRADV` is used.
#'
#' For these plots no vertical velocity is assumed as aeroecology will predominantly move in the horizontal plane.
Copy link
Copy Markdown
Owner

@adokter adokter Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should take into account the fitted vertical velocity when plotting the model fit from the vp, it is part of the fit and easy to incorporate - available as quantity w in the profile. The full radial velocity functional form is

VRAD = w sin(theta) + u cos(theta) sin(phi) + v cos(theta) cos(phi)

so you only need to add a term w sin(theta)

Copy link
Copy Markdown
Collaborator Author

@bart1 bart1 Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not having looked into the vertical velocity estimates very much I do wonder if this would not add more noise then it resolves. I see the estimates are considered unreliable (?summary.vp) and sometimes are quite high (e.g. -15 m/s in example_vp). On the other hand even these high speeds will not change the sin very much for moderate elevation angles. I will look into this

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, because the angles are so low high vertical velocities have very little effect on the fit. I just think it would be cleaner to show the actual fit instead of something else. Or have an option to remove vertical velocity in the plot

Comment thread R/vad.R
Comment thread R/vad.R Outdated
#' @param ... Currently not used.
#' @param range_min,range_max Numeric. The minimum and maximum range to include, in m. Values are taken from the `vp`
#' object if provided.
#' @param alt_min,alt_max Numeric. The minimum and maximum altitude to include, in m. If only `alt_min` value is provided
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function errors with uninformative error message when selecting a layer outside the altitude range where profile data is available, e.g. alt_min=8000, alt_max=8500

Copy link
Copy Markdown
Collaborator Author

@bart1 bart1 Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, what to do when no range gate data gets selected. We could error however that can be annoying when using the function in a loop the alternative is to return an empty plot

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree - throwing a warning and returning an empty plot sounds good

Comment thread R/vad.R
#' @param range_min,range_max Numeric. The minimum and maximum range to include, in m. Values are taken from the `vp`
#' object if provided.
#' @param alt_min,alt_max Numeric. The minimum and maximum altitude to include, in m. Separate panels will be plot for each
#' altitude layer of the profile `vp`. If only `alt_min` is provided
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when providing a single alt_min value, the plot doesn't indicate the altitude bin, as it does when you plot multiple altitude bins. better to be consistent and always show the altitude range as a label on the top.

Copy link
Copy Markdown
Owner

@adokter adokter Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related: when I specify a narrow altitude range, like c(700,810), the panels are labeled as 600-800 and 800-1000, while it should be 700-800 and 800-810

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related: when I specify a narrow altitude range, like c(700,810), the panels are labeled as 600-800 and 800-1000, while it should be 700-800 and 800-810

As I'm in a workshop I don't have much time this week. A quick remark here is that the labeling in this case would not reflect the altitude range of the vp sin that is plotted, as is currently the case. I guess either way is imperfect but up to now I have been thinking about the altitude bins as those from the vp and not the height selection of the pvol data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants