-
Notifications
You must be signed in to change notification settings - Fork 279
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
Improvements to reading / writing FITS images #2246
Conversation
a0d51dd
to
a475b1c
Compare
Assuming the tests pass, this is now finally ready to go. I know it's huge and that it's not a very familiar part of the code, for which I apologize. |
… them. Move the beam stuff to the sky fits dataset because that's what it is for.
…t exists) to the file headers
…it parsers which should in theory be more robust
…ch means that all of its derived arrays and quantities will also be. Some other minor stylistic nits.
…eader in the FITSDataset class
…t spectrum as 3rd dimension
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
* ``BUNIT``: The units of the image | ||
|
||
FITS header keywords can easily be updated using AstroPy. For example, | ||
to set the ``BTYPE`` and ``BUNIT`` keywords: | ||
|
||
.. code-block:: python | ||
|
||
import astropy.io.fits as pyfits | ||
f = pyfits.open("xray_flux_image.fits", mode="update") | ||
from astropy.io import fits |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how modern! 😁
yt/frontends/fits/data_structures.py
Outdated
@@ -69,11 +67,13 @@ | |||
sky_prefixes = list(sky_prefixes) | |||
spec_prefixes = list(spec_names.keys()) | |||
|
|||
field_from_unit = {"Jy":"intensity", | |||
"K":"temperature"} | |||
field_from_unit = {"Jy": "intensity", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we ever expect these to be imported outside of this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, I'll fix that.
@@ -92,7 +93,7 @@ def __init__(self,ds,dataset_type='fits'): | |||
self.dataset_type = dataset_type | |||
self.field_indexes = {} | |||
self.dataset = weakref.proxy(ds) | |||
# for now, the index file is the dataset! | |||
# for now, the index file is the dataset |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:inception_noise:
if field_units[0] == "/": | ||
field_units = "1%s" % field_units | ||
return field_units | ||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way we could flatten this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If possible, flattening this would make it a bit more readable!
yt/frontends/fits/data_structures.py
Outdated
@@ -410,21 +402,22 @@ def _set_code_unit_attributes(self): | |||
length_factor = self.wcs.wcs.cdelt[0] | |||
length_unit = str(file_units[0]) | |||
mylog.info("Found length units of %s." % (length_unit)) | |||
else: | |||
elif "length_unit" not in self.units_override: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any chance we'll need a final else
block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add one. Thanks for catching this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, this looks great! I noticed the YTEP still has the old PR (not the one you rebased with 4.0) linked to it for management. Maybe include this PR in that too?
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"A 3D FITS cube can also be created from a covering grid:" | ||
"A 3D FITS cube can also be created from regularly gridded 3D data. In yt, there are covering grids and \"arbitrary grids\". The easiest way to make a grid is using `ds.r`:" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the transition from mentioning yt's covering grids and arbitrary grids, and then saying that a user should make a grid with ds.r() is a little bit confusing. Maybe a transition sentence that the ds.r() is creating an ArbitraryGrid object based on the boundaries you're passing it would help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
@@ -992,7 +989,7 @@ zero, and the left and right edges of the domain along this axis are | |||
Examples of Using FITS Data | |||
^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |||
|
|||
The following IPython notebooks show examples of working with FITS data in yt, | |||
The following Jupyter notebooks show examples of working with FITS data in yt, | |||
which we recommend you look at in the following order: | |||
|
|||
* :ref:`radio_cubes` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you also want to add a link to the FITSImageData notebook in /visualizing/
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
import astropy.io.fits as pyfits | ||
f = pyfits.open("xray_flux_image.fits", mode="update") | ||
from astropy.io import fits | ||
f = fits.open("xray_flux_image.fits", mode="update") | ||
f[0].header["BUNIT"] = "cts/s/pixel" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the notebook you showed updating header values with the update_header()
method. Can that be used to do the same thing as the code block here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a different situation where we show how to make FITS files more self-descripting so yt can read them properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok! That makes sense! Is there any threshold of requirements that yt needs to read FITS files properly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really a threshold, it's more that the more self-descripting your data is, the better it is supported. Even data that's not very self-descripting can still be loaded.
@@ -833,7 +847,7 @@ The third way is if auxiliary files are included along with the main file, like | |||
|
|||
.. code-block:: python | |||
|
|||
ds = load("flux.fits", auxiliary_files=["temp.fits","metal.fits"]) | |||
ds = yt.load("flux.fits", auxiliary_files=["temp.fits","metal.fits"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just checking: this is an example of a case where there might be auxiliary files. We don't have any datasets in the FITS frontend that have these, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@munkm we don't, no. I don't think I have any on hand at the moment, actually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's ok! I just wanted to check that I shouldn't be able to run it with any files that I currently have locally.
@@ -87,10 +91,12 @@ def test_fits_image(): | |||
cut = ds.cutting([0.1, 0.2, -0.9], [0.5, 0.42, 0.6]) | |||
cut_frb = cut.to_frb((0.5, "unitary"), 128) | |||
|
|||
fid3 = FITSImageData(cut_frb, fields=[("gas","density"), ds.fields.gas.temperature], units="cm") | |||
fid3 = cut_frb.to_fits_data(fields=[("gas","density"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe change fields=[("gas","density"),
to add a space between the fields, like fields=[("gas", "density"),
here and to the fields in the fits_cut three lines down?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One totally minor comment, respond or ignore as you like. I'm happy for this to be merged.
|
||
# We need to have a bunch of species fields here, too | ||
("metal_density", ("code_mass/code_length**3", ["metal_density"], None)), | ||
("hi_density", ("code_mass/code_length**3", ["hi_density"], None)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to also add the yt standard alias names (as per YTEP 3) for these species fields, so for example, 'H_p0_density`?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I talked to @jzuhone about this and he's going to address it in a different PR.
This PR introduces improvements to FITS file writing and reading. Specifically:
FITSImageData
class and its subclasses. This standard uses the header to write unit information and other metadata to the FITS file so that when it is read back into yt as a dataset it is more self-describing.YTFITSDataset
, which is a subclass ofFITSDataset
which looks specifically for this header metadata.FITSHierarchy
for determining units."beam"
unit part ofSkyDataFITSDataset
.convolve
method toFITSImageData
instances to convolve them with any kernel created using AstroPy or a simple Gaussian kernel.FITSImageData
subclasses should be 512. The previous behavior was inconsistent across the various subclasses.The new yt-based standard for writing FITS datasets with self-describing information and reading them back in as datasets is set out in a YTEP:
yt-project/ytep#5
Things left to do:
YTCoveringGrid
to write these grids to FITS files