-
Notifications
You must be signed in to change notification settings - Fork 3
Unify XSpectrum types
#24
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
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #24 +/- ##
===========================================
+ Coverage 74.28% 90.00% +15.71%
===========================================
Files 8 7 -1
Lines 140 150 +10
===========================================
+ Hits 104 135 +31
+ Misses 36 15 -21 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
We did this in PhotometricFilters.jl, see here. Is it useful for getindex to return a Base.getindex(f::Spectrum, i::Int) = (wave(f)[i], flux(f)[i]) |
|
Thanks! Yea, I liked how this keeps things consistent with Actually... This may go nowhere, but I think I want to start investigating #25 (comment) now |
|
Ok, I think this might be going somewhere. Updated the parent comment with a summary |
|
If you want to cover specutils' case 2 (multi-dimensional flux, but same spectral axis along each) you may need to add a type parameter for the dimensionality of the wavelength array as well. I think case 2 is for IFU data (??) so maybe mutable struct Spectrum{W<:Number, F<:Number, N, M} <: AbstractSpectrum{W, F}
wave::AbstractArray{W, N}
flux::AbstractArray{F, M}
meta::Dict{Symbol,Any}
end
const SingleSpectrum = Spectrum{W, F, 1, 1} where {W, F}
const IFUSpectrum = Spectrum{W, F, 1, 2} where {W, F}
const EchelleSpectrum = Spectrum{W, F, 2, 2} where {W, F}We can also worry about this later if you want |
|
oh I like this a lot. Added some methods to start playing with this. There's something really satisfying about being able to do, say: julia> spec_IFU = spectrum([20, 40, 120, 160, 200], rand(10, 5))
IFUSpectrum(Int64, Float64)
# orders: 10
wave: (20, 200)
meta: Dict{Symbol, Any}()
julia> spec_IFU[3, begin:4]
SingleSpectrum(Int64, Float64)
wave: (20, 200)
flux: (0.12659667784419948, 0.3942549580490494)
meta: Dict{Symbol, Any}(:Order => 3)to get the first 4 flux measurements from the 3rd order of an |
|
Actually I think an IFU with a uniform wavelength (dispersion) sampling would be a 3-D cube (2 spatial axes across the IFU field, one wavelength axis) so I think it's a good idea, not sure what the best API is. I would probably put the wavelength axis first in the flux matrix because the most common operation is probably selecting a 1-D spectrum from the cube based on pixel. julia> wave, flux = [20, 40, 120, 160, 200], rand(5, 10, 10);
julia> spec_ifu = spectrum(wave, flux);
julia> spec_ifu[:, 1, 1] # Selects full 1-D spectrum from spatial pixel with index (1, 1)My intuition is that a scalar wavelength index would return a basic array (i.e, an image of the flux at a fixed wavelength). julia> spec_IFU[3, begin:4, begin:4] isa Matrix{Float64}
true
julia> spec_IFU[3, begin:4, begin:4] == flux[3, begin:4, begin:4]
trueI think returning a spectrum type only makes sense if the index is a range for the wavelength. I'm really not sure though, I don't really work on this either |
|
Oh cool! Thanks for the reference, I've never worked with IFUs before, so this was a really interesting read. I think "spaxel" is my new favorite word 😂. I see that I was mixing concepts between echelles and ifus when I was talking about orders, sorry about that. The split to 3D really helped clarify things for me. How does this new design look? I also cleaned up the show methods a bit (still need to update tests once this settles though) julia> using Spectra
julia> wave, flux = [20, 40, 120, 160, 200], rand(5, 10, 6);
julia> spec_ifu = spectrum(wave, flux)
IFUSpectrum(Int64, Float64)
wave ((5,)): 20 .. 200
flux ((5, 10, 6)): 0.6211135421955702 .. 0.4976591674645773
meta: Dict{Symbol, Any}()
julia> spec_ifu[:, 1, 1]
SingleSpectrum(Int64, Float64)
wave ((5,)): 20 .. 200
flux ((5,)): 0.6211135421955702 .. 0.9046136130205523
meta: Dict{Symbol, Any}()
julia> spec_ifu[:, begin:4, begin:3]
IFUSpectrum(Int64, Float64)
wave ((5,)): 20 .. 200
flux ((5, 4, 3)): 0.6211135421955702 .. 0.8211549470853685
meta: Dict{Symbol, Any}()
julia> spec_ifu[3, begin:4, begin:3]
4×3 Matrix{Float64}:
0.184341 0.942616 0.546656
0.206153 0.754342 0.120533
0.186781 0.0043856 0.0914326
0.601994 0.352066 0.82047
julia> spec_ifu[3, begin:4, begin:3] == flux[3, begin:4, begin:3]
true |
|
Hello @icweaver ! I've had a look at this PR, and include below some thought about how this would work with SpectralFitting.jl. These are just comments and observations towards some kind of base spectra package, but don't let me stop you with this PR. TL;DRFor this to be useful for SpectralFitting.jl, I'd need at the very least: # Returns the domain of the spectrum in units of energy (eV or KeV)
# This may have to be bin widths for X-ray too
Spectra.energy(::AbstractSpectrum)It would probably also be good to have an interface for converting the units of both wavelength / energy, but also any (specific) fluxes. There's a table for how to do the flux conversions I have printed out on my desk that I can share here too (once I find the PDF of it again). There's a few other caveats, but I can sort those within SpectralFitting.jl too if I were to depend on Spectra.jl. See below for those. DetailsI keep things brief here. X-ray astronomy (and other high energy fields) typically operate on spectra that are binned into channels by the measurement device. We usually deal with photon counts (as we don't observe many photons) that we can measure the energy (from the discrete channel) of individually. The data is typically arranged with three vectors (after some processing):
There's usually also an instrument response matrix that is carried around with the raw channel data that can be used to calculate the above three vectors, and is also needed for modelling purposes (akin to a PSF but which tabulates the probability of detection of a photon of true energy So the proposed changes in this PR aren't really suitable for SpectralFitting's alignment with X-ray astronomy at the moment, but would be perfect for other wavelengths. For X-rays, we need spectra where we can:
There's also a point about uncertainties in the spectra, as X-ray have (co)variances that are not necessarily Gaussian, but that could be done by convention in the metadata. In SpectralFitting, I currently have an To make an example:
Something like that would be very useful in Spectra.jl. Not everything would need to support both types of data, and to some extent that conversion can be automated provided it can be done without losing information. I'd also encourage not using terms like Virtual Observatory and SSAI don't know if we want to also take a leaf out of the VO's book and make our spectral types directly based on the Spectral Data Model. See, for example, 3.1 of SpectrumDM. They obviously are much more complete than we would need to be to get started, but they've thought through most of the problems we would need to face in a base spectra package. The table in 4.2 in flux (as spectral intensity) and all the metadata and units that would need to include is a good thing to look at. To reiterate what I said in the call though -- I would love a base spectra package, and I hope this doesn't look like I'm trying to poke holes in what you're doing here. Let me know if there's concrete ways you'd like me to contribute to this (very happy to open PRs also) so that we can get this moving 👍 |
|
Sorry after writing the above I noticed that |
|
Appreciate your thorough review Fergus! What you're doing with SpectralFitting.jl looks great. Just FYI for everyone involved, I will be very busy for the next month or two at least so I can't commit to doing much for this right now. I also don't really do observational spectroscopy myself (my interest is in loading/manipulating theoretical SEDs) so I am not really a great domain expert here anyhow. |
|
For sure, thanks for all of your current input, Chris! And Fergus, no apologies necessary at all. This is exactly the kind of expert feedback we need, thank you! I'm down to see if we can get some X-ray support going in this PR if you think there might be something there. Funnily enough, the I'm still getting up to speed, but would a design like this be starting to go in the direction that you are thinking? julia> using Spectra
Precompiling Spectra finished.
1 dependency successfully precompiled in 3 seconds. 114 already precompiled.
julia> const XraySpectrum = Spectrum{S, F, 2, 1} where {S, F}
Spectrum{S, F, 2, 1} where {S, F}
julia> spec = Spectrum([2:2:10;; 4:2:12], 1:5, Dict())
Spectrum{Int64, Int64, 2, 1}([2 4; 4 6; … ; 8 10; 10 12], 1:5, Dict{Symbol, Any}())
julia> energy_low, energy_high = eachcol(spectral_axis(spec))
2-element ColumnSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}:
[2, 4, 6, 8, 10]
[4, 6, 8, 10, 12]
julia> counts = flux_axis(spec)
1:5Or maybe even subtypee At any rate, I quite like your suggested I'd be happy to help support any additional PRs you might have in mind if I can. I've started working with SpectralFitting.jl, and working with the sample |
Yes, that would certainly get me some of the way. So thinking aloud for a few other bits:
Instrument responses and the ancillary files is another thing. Since they are big and tend not to change for a particular instrument (modulo updated calibrations), I have kept those seperate from spectra in the past. That does mean my So at the moment I'm thinking I could do something like using Spectra
const BinnedSpectrum = Spectrum{S, F, 2, 1} where {S, F}
struct SpectralData # a dataset type specific to SpectralFitting
source::BinnedSpectrum
background::BinnedSpectrum
response::ResponseMatrix # specific to SF
ancillary::AncillaryResponse # specific to SF
# .. other SF specific fields
endit's then easy for someone to take a For the response files, I wrote most of an OGIP parser for SpectralFitting.jl. It doesn't have any dependencies beyond a fits reader of some kind (will be FITSFiles.jl in the future). Would this be better served migrated over to this package? If not, then Spectra.jl will probably be of limited use to X-ray observers, as the spectra would only ever contain channel information in the spectral axis and not physical units. That would also mean needing some kind of redistribution matrix and ancillary response types in Spectra.jl as well. I don't think that would be out of scope for this package, but I am happy to be told otherwise :) I can elaborate on this if you like, or can start throwing PRs this way if you're happy for it to be included.
Yes absolutely. However, there would be some overlap at the moment. E.g., the One thing I found useful to do on this front is to have a abstract type AbstractSpectrum{S,F,Tag}By default, the |
|
Oh, this is getting exciting. To start preparing, I've just updated this PR with some very basic generalizations:
To help keep things organized, would merging this PR once the above looks good, and then having you open a separate PR so we can start exploring incorporating an updated |
TIL about OGIP, thanks for the link! I'm still reading up on how specutils handles data loaders, but it looks like moving this parsing functionality here would be in scope, yea? Would the idea be to also try and replace the Overall, since we're excising some of the old analysis bits here (e.g., |
haha, yea, I had a feeling that just trying to dispatch based on array dimension may have been trying to be a bit too clever. Looking forward to exploring the tag solution more! |
Sounds good! I'd like to play around with this in SF.jl a bit too and start putting a branch together using Spectra.jl to provide the spectral interface, to see what it look likes and what we have (inevitably) missed.
Yes, that would be ideal. Then the spectra types are really owned by Spectra.jl and SpectralFitting.jl can solely be a set of utilities and abstractions for model fitting.
In that case I'll start preparing an OGIP branch for Spectra.jl as well, to move the code over from SF.jl :) |
|
Perfect. I'll give this PR another once-over then merge things in |
Doc preview: https://juliaastro.org/Spectra.jl/previews/PR24/
Closes: #25, #37
What's changed
Following the architecture of
specutils, ourSpectrum,EchelleSpectrum, and a newIFUSpectrumfor spectral cube support, are all justSpectrum{S, F, M, N} <: AbstractSpectrum{S, F}s now, with their dimensionality specified in the type domain:where
Sis the eltype of the spectral axis with dimensionalityM, andFis the eltype of the flux axis with dimensionalityN.More of the array interface is implemented now so things like this will work:
Merged
common.jlintoSpectra.jl(for my own sanity)Renamed the
Analysispage of the docs toUtilitiesand movedblackbodyover to itShelved some analysis functionality (e.g., continuum fitting and equivalent width support) in favor of using downstream packages like SpectralFitting.jl
Generalized terms in preparation for introducing X-ray support in a future PR. e.g.,
wave --> spectral_axisandflux --> spectral-axis. These are now exported. Wavelength and energy now both referenced instead of just wavelengthFilled in some more API documentation
To-do
Future PR(s)
Spectrums Spectra algebra #43XSpectrumtypes #24 (comment)