Add spectrogram metadata classes#245
Conversation
| # Identification | ||
| @property | ||
| @abc.abstractmethod | ||
| def instrument(self): |
There was a problem hiding this comment.
I think a short description and the type info should be encode here for each property e.g.
def instrument(self) -> str:
"""
Name of the instrument
"""
pass
and then here if we only return str then the default south be "" if that better or worse then returning a str or none e.g. -> str|None
There was a problem hiding this comment.
for properties like date_start, frequency_rangeor data_units, what should i write for them??
There was a problem hiding this comment.
"Start of the observation" Time
"Frequency range of observation" Quantity
"Unit for the data" Unit
Maybe we don't need the docstrings for them all but they types would be good
There was a problem hiding this comment.
One more doubt, what would you prefer for observer_location and data_mask?
|
Also you can embed mermaid charts directly in GitHub doesn't match exactly the UML but close enough classDiagram
direction TB
class NDMetaABC {
<<ndcube.meta>>
}
class SpectrogramMetaABC {
<<abstract>>
+instrument
+observatory
+detector
+date_start
+date_end
...
}
class NDMeta {
<<ndcube.meta>>
dict subclass
+axes
...
+slice()
+add()
+rebin()
}
class SpectrogramMeta {
<<concrete base>>
dict-backed via NDMeta
+instrument
+observatory
+detector
+date_start
+date_end
...
}
class WAVESMeta {
+background
+receiver
}
class CALISTOMeta {
+observer_location
}
class OtherInstrumentMetas {
<<...>>
}
%% Relationships
SpectrogramMetaABC --|> NDMetaABC : inherits
NDMeta --|> NDMetaABC : inherits
SpectrogramMeta --|> NDMeta : inherits
SpectrogramMeta ..|> SpectrogramMetaABC : implements
WAVESMeta --|> SpectrogramMeta
CALISTOMeta --|> SpectrogramMeta
OtherInstrumentMetas --|> SpectrogramMeta
|
| @property | ||
| @abc.abstractmethod | ||
| def data_units(self) -> Unit: | ||
| """Unit for the data""" | ||
| pass |
There was a problem hiding this comment.
I think this is aleady cover by the unit in NDCube
| def observer_location(self): | ||
| """Observer location.""" | ||
| pass |
There was a problem hiding this comment.
Maybe observe_coordinate would be better and this should be a SkyCoord | None
There was a problem hiding this comment.
Got it, I'm working on it
Thats so cool, I didn't know GitHub supported Mermaid diagrams. I'll definitely try using them, Thanks a lot! |
|
Should i add the rest of the classes in this PR or like break it in different PRs?? |
|
I think adding the classes necessary to handle the instruments that are currently supported would make sense. |
|
I have added the remaining classes, let know your thoughts on this @samaloney |
hayesla
left a comment
There was a problem hiding this comment.
Thanks for this @Amityush-lgtm! this is looking good (sorry for delay)
I think there are some issue here, I think it would also be good to make some tests making some generic wrapping itself, for example a test that constructs a GenericSpectrogram from a dict and assert the meta etc, and that the things like spec.observatory/start_time/etc return the right types?
it is probably also good to check that meta survives slicing (i.e. if you slice the Spectrogram does the meta slice?), somethign worth checking! this may also be good to do in another PR
| return self.get("frequency_resolution") | ||
|
|
||
| @property | ||
| def calibration_state(self) -> str | None: |
There was a problem hiding this comment.
hmm I wonder if calibration_state is the right word here? could it just be calibrated and then have boolean? @samaloney would there ever be a case where the data is partially calibrated?
There was a problem hiding this comment.
🤷 I'm not sure if this isn't redundant with level but maybe that's ok?
| """Metadata for WIND/WAVES spectrograms.""" | ||
|
|
||
| @property | ||
| def background(self): |
There was a problem hiding this comment.
why is this here? only on WAVES?
There was a problem hiding this comment.
background is only used by WAVES, i added it on WAVESMeta to make it easy to find. Should i move it in the base class ??
| """Metadata for PSP FIELDS/RFS spectrograms.""" | ||
|
|
||
| @property | ||
| def level(self) -> str | None: |
There was a problem hiding this comment.
this is the processing_level no?
There was a problem hiding this comment.
yes, it is processing_level but since it will override from the base class, thats why i changed its name. I've updated it to override.
There was a problem hiding this comment.
So this is the point is we map different convention e.g. name back to our "standard" name.
| """ | ||
| The name of the observatory which recorded the spectrogram. | ||
| """ | ||
| return self.meta["observatory"].upper() | ||
| val = getattr(self.meta, "observatory", self.meta.get("observatory")) | ||
| return val.upper() if val else None |
There was a problem hiding this comment.
hmm there's an issue with this, this getattr finds the observatory property on SpectrogramMeta, and so the .get() fallback here is never used - so there is actually a bug with a KeyError being raised is observatory isnt passed
There was a problem hiding this comment.
I've tried to simplify these to just use self.meta.get() directly for fallback
| """Metadata for e-CALLISTO spectrograms.""" | ||
|
|
||
| @property | ||
| def observer_coordinate(self) -> SkyCoord | None: |
There was a problem hiding this comment.
this actually returns an EarthLocation not a SkyCoord - i think we should actually use a SkyCoord (i.e. convert the EarthLocation to a SkyCoord) i think this can be done with .get_gcrs(obstime)
|
|
||
| @property | ||
| def date_start(self) -> Time: | ||
| return self["start_time"] |
There was a problem hiding this comment.
should this be
| return self["start_time"] | |
| return Time(self["start_time"]) | |
| ```? |
There was a problem hiding this comment.
like the -> Time annotation only holds if the caller stored a Time?
| return self["start_time"] | ||
|
|
||
| @property | ||
| def date_end(self) -> Time: |
| { | ||
| "processing_level": "L2", | ||
| "version": "1.0", | ||
| "wavelength": "radio", |
There was a problem hiding this comment.
this wavelength here shouldnt be a string? (i realise this is just a test but its not right)
| return self.get("background") | ||
|
|
||
| @property | ||
| def receiver(self): |
There was a problem hiding this comment.
is this specific to waves? it could be on base class?
| super().__init__(meta=meta, data=data, **kwargs) | ||
|
|
||
| @property | ||
| def receiver(self): |
There was a problem hiding this comment.
see comment below about this maybe being on base class
|
I've pushed the latest changes with all the suggested updates, let me know if i've missed anything or anything else to improve |

PR Description
Fixes #243
Implementing a metadata class hierarchy for radiospectra, following the pattern used in sunraster and the LC Radio Workshop metadata requirements.
I tried to make some flowcharts as well, will attach them too.
Changes:
SpectrogramMetaABCandSpectrogramMeta(backed by NDMeta) inradiospectra/spectrogram/meta.pyWAVESMeta,SWAVESMeta,CALISTOMeta,EOVSAMeta,RSTNMeta)GenericSpectrogramto wrap raw dict metadata intoSpectrogramMetaautomaticallyAI Assistance Disclosure
AI tools were used for:
This is an initial draft, so I've just tried to make the changes in WAVES and CALISTO, if it looks in the right direction then I'll add the remaining subclasses also. Let me know your thoughts on this @samaloney and @hayesla.