Skip to content

Allow writing of all MaskedColumn to FITS. - #11194

Closed
mhvk wants to merge 1 commit into
astropy:masterfrom
mhvk:table-masked-column-fits-writing
Closed

mhvk wants to merge 1 commit into
astropy:masterfrom
mhvk:table-masked-column-fits-writing

Conversation

@mhvk

@mhvk mhvk commented Dec 28, 2020

Copy link
Copy Markdown
Contributor

@taldcroft - while looking at writing masked data to FITS, I noticed that it does not currently work for non-integer data. It seemed the solution was simple: just serialize fill_value. I tried that in this draft.

A problem is that now even integers go this route, and TNULL is no longer used, so perhaps this is too blunt.

Anyway, mostly for discussion for now.

Works by serializing fill_value.

A problem is that now even integers go this route, and TNULL is no
longer used.
@mhvk

mhvk commented Dec 28, 2020

Copy link
Copy Markdown
Contributor Author

p.s. Test failure is because it now requires yaml to run - they pass locally. Clearly, there should be one with and one without yaml.

@taldcroft

Copy link
Copy Markdown
Member

I'm not sure this is a good route, for the reason you mentioned about int types.

The recommended route for masked float data is to use the data_mask serialization method. Is that not sufficient? Maybe there should be a warning for masked non-int data to tell the user to use data_mask?

In [3]: t                                                                                                                                         
Out[3]: 
<Table masked=True length=3>
  a      b     c  
int64 float64 str1
----- ------- ----
   --     1.0    c
    2     2.0   --
    3      --    e

In [4]: t.write('junk.fits', overwrite=True)                                                                                                      

In [5]: t2 = Table.read('junk.fits')                                                                                                              

In [6]: t2                                                                                                                                        
Out[6]: 
<Table masked=True length=3>
  a      b      c   
int64 float64 bytes1
----- ------- ------
   --     1.0      c
    2     2.0      N
    3     nan      e

@taldcroft

Copy link
Copy Markdown
Member

In the example above it seems like the masked str values should go to '' per the FITS standard. The float is already doing the right thing and going to NaN, but it should become masked on construction.

@mhvk

mhvk commented Dec 29, 2020

Copy link
Copy Markdown
Contributor Author

Ah, if there is a standard, that is definitely the way to go! And better for my Masked class, since it does not carry a fill_value around...

@mhvk

mhvk commented Dec 29, 2020

Copy link
Copy Markdown
Contributor Author

As part of trying to look at where this might happen:

# Binary FITS tables support TNULL *only* for integer data columns
# TODO: Determine a schema for handling non-integer masked columns
# in FITS (if at all possible)
int_formats = ('B', 'I', 'J', 'K')
if not (col.format in int_formats or
col.format.p_format in int_formats):
continue
# The astype is necessary because if the string column is less
# than one character, the fill value will be N/A by default which
# is too long, and so no values will get masked.
fill_value = table[col.name].get_fill_value()
col.null = fill_value.astype(table[col.name].dtype)

@mhvk

mhvk commented Dec 30, 2020

Copy link
Copy Markdown
Contributor Author

@taldcroft - looking more at the conversion of tables to FITS, could it be that something was missed in changing the meaning of table.masked? Am I right in thinking that in convenience.py, one should have changed table.masked to table.has_masked_values()? See

# Create a new HDU object
if table.masked:
# float column's default mask value needs to be Nan
for column in table.columns.values():

@taldcroft

Copy link
Copy Markdown
Member

could it be that something was missed in changing the meaning of table.masked?

I think you are right. But there is still more that seems just broken, even when table.masked is True.

In [7]: t                                                                                                                             
Out[7]: 
<Table masked=True length=3>
  a      b     c  
int64 float64 str3
----- ------- ----
   --     1.0  abc
    2     2.0   --
    3      --  asd

In [8]: t.write('junk.fits', overwrite=True)                                                                                          

In [9]: t2 = Table.read('junk.fits')                                                                                                  

In [10]: t2                                                                                                                           
Out[10]: 
<Table masked=True length=3>
  a      b      c   
int64 float64 bytes3
----- ------- ------
   --     1.0    abc
    2     2.0    N/A
    3     nan    asd

Things that seem wrong:

  • The nan in b should be converted to a masked value when reading.
  • The masked value in c should be filled with "" for writing and then that "" should be masked for reading. (From the FITS standard for character data: "Null strings are defined by the presence of an ASCII NULL as the first character." In FITS parlance a null value is what we call a masked value.

This comment below completely confuses me. From the code above it seems that it can be reached only for int-type data, but the comment refers to character data.

# The astype is necessary because if the string column is less

@mhvk

mhvk commented Dec 31, 2020

Copy link
Copy Markdown
Contributor Author

So it looks like both on the writing and on the reading side there are some problems, with the writing one perhaps just the oversight of .masked. The comment I think is just another oversight, from a large pyfits update - see https://github.com/astropy/astropy/pull/1676/files#diff-2b263cbbb7d9b3215b1a7180dcd1b909c7ee9f78cf1e2e53834ee3b4d6b837b3

Need to see where the reading comes from...

@taldcroft

Copy link
Copy Markdown
Member

Somehow I had thought that writing losslessly to FITS was working better than this. Note that meta['__attributes__'] (hence hidden columns) is also broken when writing to FITS. Maybe we need to just automatically fall through to using the FITS serialization machinery for values in meta that cannot be stored as a FITS keyword.

@mhvk

mhvk commented Jan 7, 2021

Copy link
Copy Markdown
Contributor Author

A less invasive solution to MaskedColumn not being properly stored is in #11222.

A separate question perhaps remains whether the fill value should be serialized, if only for the data_mask serialization.

@mhvk mhvk closed this in #11222 Jan 21, 2021
@mhvk
mhvk deleted the table-masked-column-fits-writing branch April 13, 2021 21:22
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