Skip to content

Implement SVOFilter type, make statistics generic #22

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
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

cgarling
Copy link
Member

@cgarling cgarling commented Jul 4, 2025

Following #21, which implemented the ability to query the SVO filter service, this PR implements an SVOFilter type that contains the transmission curve and the metadata returned by SVO. It is implemented as an encapsulated PhotometricFilter instance and a dictionary (2 total fields). A few dictionary methods are passed through to the SVOFilter type so you can do things like

julia> filt = get_filter("2MASS/2MASS.J", :Vega);
julia> filt["ZeroPoint"] # Can retrieve metadata directly

to retrieve metadata from the contained dictionary, but I had trouble finding the full dictionary API documented anywhere so I could be missing things. I tried to add the stuff I most commonly use (getindex, haskey, keys, values).

To better support this new type, I made many of the base methods of the package operate on AbstractFilter where before they required a concrete PhotometricFilter. This required two new accessor methods, name, and detector_type to retrieve the relevant information from subtypes of AbstractFilter.

A few unrelated changes:

  • I made Base.getindex(f::AbstractFilter, i::Int) = (wave(f)[i], throughput(f)[i]) rather than just returning the throughput as it was doing previously; not sure in what circumstance you would only want to iterate over the throughput rather than the wavelength and throughput together.
  • The wavelength field of PhotometricFilter is now a Unitful vector, rather than plain numbers, to ensure that wave(::AbstractFilter) does not need to make a copy -- otherwise the above method would make a copy on every access. Previously, calling wave would allocate a copy of the wavelength field because it had to add units to it.

Happy to have feedback on this! I tried to add as few additional methods as possible, but now that we have a filter type with metadata, maybe we should add a few more methods (e.g., zeropoint(::AbstractFilter)) to provide consistent access to this information (when it is available -- probably return missing when unavailable). Tagging @lucasvalenzuela since they were interested in #21.

cgarling added 2 commits July 4, 2025 11:06
Improve performance for accessor `wave(::PhotometricFilter)`, maybe? not sure if interpolator `etp` will work
Also add `detector_type` and `name` accessor methods to make a standard API for other filter types to follow.
Copy link

codecov bot commented Jul 4, 2025

Codecov Report

Attention: Patch coverage is 64.17910% with 24 lines in your changes missing coverage. Please review.

Project coverage is 77.60%. Comparing base (6e77fa5) to head (ec17029).

Files with missing lines Patch % Lines
src/core.jl 62.50% 21 Missing ⚠️
src/svo.jl 72.72% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #22      +/-   ##
==========================================
- Coverage   82.16%   77.60%   -4.57%     
==========================================
  Files           6        6              
  Lines         286      317      +31     
==========================================
+ Hits          235      246      +11     
- Misses         51       71      +20     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cgarling
Copy link
Member Author

cgarling commented Jul 5, 2025

Couldn't sleep so added a few more methods ...
integrate computes basically $\overline{f_\lambda}$ as defined, for example in pyphot's docs and many other places. Not sure about the method name, not attached to integrate at all. I can verify the values against the SVO blackbody synthetic photometry for both photon counters and energy counters -- using the Planck.jl package for blackbody radiation we have for a photon counting system (SDSS u filter)

using Planck: blackbody
using PhotometricFilters: wave, integrate, SDSS_u
using Unitful
filt = SDSS_u()
Flam = blackbody.(u"erg/s/cm^2/angstrom", wave(filt), 5000u"K")
integrate(filt, wave(filt), Flam)
648628.4038417398 erg Å^-1 cm^-2 s^-1

which can be verified against SVO's synthetic photometry which gives 648596.9309048

For an energy counting system (2MASS J filter)

using Planck: blackbody
using PhotometricFilters: wave, integrate, get_filter
using Unitful
filt = get_filter("2MASS/2MASS.J")
Flam = blackbody.(u"erg/s/cm^2/angstrom", wave(filt), 5000u"K")
integrate(filt, wave(filt), Flam)
445507.8175161778 erg Å^-1 cm^-2 s^-1

which can be compared against SVO's synthetic photometry which gives 446898.39843973

F_nu and F_lambda are supposed to convert between spectral flux density and spectral energy density units. I'm basing this off the SVO API paper which writes
image
which seems mostly the same as the "Jansky definition" given in pyphot's docs as well,
image
though pyphot uses the pivot wavelength and SVO uses the effective wavelength. It's been a long time since I had to think about these spectral units so please let me know if I made a mistake with these.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant