Skip to content

Serialize all astropy core objects to FITS - #6912

Merged
taldcroft merged 15 commits into
astropy:masterfrom
taldcroft:fits-mixins
Dec 19, 2017
Merged

taldcroft merged 15 commits into
astropy:masterfrom
taldcroft:fits-mixins

Conversation

@taldcroft

Copy link
Copy Markdown
Member

This is a proof of concept for serializing all astropy core objects to FITS. It heavily leverages the machinery used for ECSV, and the concept is pretty much identical. HDF5 can be done in much the same way.

For writing the process is to encode a table with mixins into a table with only standard Columns + appropriate YAML-encoded metadata. That metadata has all the information needed to fully-reconstruct the mixin columns.

The key question is how to store the YAML-encoded metadata in FITS. This POC takes a simple approach and just puts it into FITS COMMENT cards, contained within two sentinel strings to allow being able to reliable identify such metadata in a FITS file. This is not the only approach, but it is pretty simple and non-invasive. See code comments for an implementation detail that required base64 encoding of the YAML.

The requirement here is that this process is minimally impacting to output FITS files. The final implementation would be careful to cause no change to the output FITS file for a Table that has no mixin columns. That is not currently the case in this POC.

Reading is just the reverse, and the Columns-only table + metadata are decoded into a Table with mixin columns.

This works right now:

In [3]: t = QTable([[1, 2] * u.m, 
                    SkyCoord([1,2], [3,4], unit='deg'), 
                    [5.0, 6.0]])
   ...: t['col0'].info.format = '.2f'
   ...: t['col1'].info.meta = {'a': 1}
   ...: t['col2'].format = '.3f'
   ...: t['col2'].description = 'interesting'
   ...: t.write('junk.fits', overwrite=True)
   ...: t2 = QTable.read('junk.fits')
   ...: t2
   ...: 
Out[3]: 
<QTable length=2>
  col0    col1    col2 
   m    deg,deg        
float64  object float64
------- ------- -------
   1.00 1.0,3.0   5.000
   2.00 2.0,4.0   6.000

In [4]: t2['col1'].info.meta
Out[4]: {'a': 1}

$ fitsheader junk.fits 
# HDU 0 in junk.fits:
SIMPLE  =                    T / conforms to FITS standard                      
BITPIX  =                    8 / array data type                                
NAXIS   =                    0 / number of array dimensions                     
EXTEND  =                    T                                                  

# HDU 1 in junk.fits:
XTENSION= 'BINTABLE'           / binary table extension                         
BITPIX  =                    8 / array data type                                
NAXIS   =                    2 / number of array dimensions                     
NAXIS1  =                   32 / length of dimension 1                          
NAXIS2  =                    2 / length of dimension 2                          
PCOUNT  =                    0 / number of group parameters                     
GCOUNT  =                    1 / number of groups                               
TFIELDS =                    4 / number of table fields                         
TTYPE1  = 'col0    '                                                            
TFORM1  = 'D       '                                                            
TUNIT1  = 'm       '                                                            
TTYPE2  = 'col1.ra '                                                            
TFORM2  = 'D       '                                                            
TUNIT2  = 'deg     '                                                            
TTYPE3  = 'col1.dec'                                                            
TFORM3  = 'D       '                                                            
TUNIT3  = 'deg     '                                                            
TTYPE4  = 'col2    '                                                            
TFORM4  = 'D       '                                                            
COMMENT --BEGIN-BASE64-ASTROPY-SERIALIZED-COLUMNS--                             
COMMENT e2RhdGF0eXBlOiBbe25hbWU6IGNvbDAsIHVuaXQ6IG0sIGRhdGF0eXBlOiBmbG9hdDY0LCBm
COMMENT b3JtYXQ6IC4yZn0sIHtuYW1lOiBjb2wxLnJhLCB1bml0OiBkZWcsIGRhdGF0eXBlOiBmbG9h
COMMENT dDY0fSwge25hbWU6IGNvbDEuZGVjLCB1bml0OiBkZWcsIGRhdGF0eXBlOiBmbG9hdDY0fSwg
COMMENT e25hbWU6IGNvbDIsIGRhdGF0eXBlOiBmbG9hdDY0LCBmb3JtYXQ6IC4zZiwgZGVzY3JpcHRp
COMMENT b246IGludGVyZXN0aW5nfV0sIG1ldGE6ICEhb21hcCBbe19fc2VyaWFsaXplZF9jb2x1bW5z
COMMENT X186IHtjb2wwOiB7X19jbGFzc19fOiBhc3Ryb3B5LnVuaXRzLnF1YW50aXR5LlF1YW50aXR5
COMMENT LCB2YWx1ZTogIWFzdHJvcHkudGFibGUuU2VyaWFsaXplZENvbHVtbiB7bmFtZTogY29sMH19
COMMENT LCBjb2wxOiB7X19jbGFzc19fOiBhc3Ryb3B5LmNvb3JkaW5hdGVzLnNreV9jb29yZGluYXRl
COMMENT LlNreUNvb3JkLCBfX2luZm9fXzoge21ldGE6IHthOiAxfX0sIGRlYzogIWFzdHJvcHkudGFi
COMMENT bGUuU2VyaWFsaXplZENvbHVtbiB7X19jbGFzc19fOiBhc3Ryb3B5LmNvb3JkaW5hdGVzLmFu
COMMENT Z2xlcy5MYXRpdHVkZSwgdmFsdWU6ICFhc3Ryb3B5LnRhYmxlLlNlcmlhbGl6ZWRDb2x1bW4g
COMMENT e25hbWU6IGNvbDEuZGVjfX0sIGZyYW1lOiBpY3JzLCByYTogIWFzdHJvcHkudGFibGUuU2Vy
COMMENT aWFsaXplZENvbHVtbiB7X19jbGFzc19fOiBhc3Ryb3B5LmNvb3JkaW5hdGVzLmFuZ2xlcy5M
COMMENT b25naXR1ZGUsIHZhbHVlOiAhYXN0cm9weS50YWJsZS5TZXJpYWxpemVkQ29sdW1uIHtuYW1l
COMMENT OiBjb2wxLnJhfSwgd3JhcF9hbmdsZTogIWFzdHJvcHkuY29vcmRpbmF0ZXMuQW5nbGUge3Vu
COMMENT aXQ6ICFhc3Ryb3B5LnVuaXRzLlVuaXQge3VuaXQ6IGRlZ30sIHZhbHVlOiAzNjAuMH19LCBy
COMMENT ZXByZXNlbnRhdGlvbjogc3BoZXJpY2FsfX19XX0=                                
COMMENT --END-BASE64-ASTROPY-SERIALIZED-COLUMNS--                               

@astropy-bot

astropy-bot Bot commented Nov 29, 2017 •

Copy link
Copy Markdown

Hi there @taldcroft 👋 - thanks for the pull request! I'm just a friendly 🤖 that checks for issues related to the changelog and making sure that this pull request is milestoned and labeled correctly. This is mainly intended for the maintainers, so if you are not a maintainer you can ignore this, and a maintainer will let you know if any action is required on your part 😃.

Everything looks good from my point of view! 👍

If there are any issues with this message, please report them here.

@taldcroft taldcroft added this to the v3.0.0 milestone Nov 29, 2017
@taldcroft
taldcroft requested a review from mhvk November 29, 2017 17:04
@mhvk

mhvk commented Nov 29, 2017

Copy link
Copy Markdown
Contributor

Hmm, remarkably similar to how ESO-MIDAS does it:

HISTORY  ESO-DESCRIPTORS START   ................                               
HISTORY  'LHCUTS','R*4',1,4,'5E14.7'                                            
HISTORY   1.5305000E+04 1.5550000E+04 1.4768391E+04 1.5546273E+04               
HISTORY                                                                         
HISTORY  'DISPLAY_DATA','I*4',1,9,'7I10'                                        
HISTORY          -2        -2        82        81         0        -1        -1 
HISTORY          -1        -1                                                   
HISTORY                                                                         
HISTORY  ESO-DESCRIPTORS END     ................                               

They do seem to get around the white-space issue in adding backslashes to the end of the cards:

HISTORY COMPUTE/IMAG t1368.bdf = (s1368.bdf-dark6x10n4)/ff13681372             \
HISTORY          AVERAGE/IMAG t13681372 = t1368,t1369,t1370,t1371,t1372 N 0.000\
HISTORY 00E+00 AV +       COMPUTE/IMAG psrj1012h = (t13681372+t13831387)/2.    \
HISTORY                                                                         

I don't know if HISTORY has special treatment in this regard, or whether it is a trick they use. It might help avoid encoding as base64, which somewhat beats the purpose of having a nicely human readable representation.

@taldcroft

Copy link
Copy Markdown
Member Author

Unlike ECSV, this is not intended to be human readable. Here it is without encoding:

COMMENT --BEGIN-BASE64-ASTROPY-SERIALIZED-COLUMNS--                             
COMMENT {datatype: [{name: col0, unit: m, datatype: float64, format: .2f}, {name
COMMENT : col1.ra, unit: deg, datatype: float64}, {name: col1.dec, unit: deg, da
COMMENT tatype: float64}, {name: col2, datatype: float64, format: .3f, descripti
COMMENT on: interesting}], meta: !!omap [{__serialized_columns__: {col0: {__clas
COMMENT s__: astropy.units.quantity.Quantity, value: !astropy.table.SerializedCo
COMMENT lumn {name: col0}}, col1: {__class__: astropy.coordinates.sky_coordinate
COMMENT .SkyCoord, __info__: {meta: {a: 1}}, dec: !astropy.table.SerializedColum
COMMENT n {__class__: astropy.coordinates.angles.Latitude, value: !astropy.table
COMMENT .SerializedColumn {name: col1.dec}}, frame: icrs, ra: !astropy.table.Ser
COMMENT ializedColumn {__class__: astropy.coordinates.angles.Longitude, value: !
COMMENT astropy.table.SerializedColumn {name: col1.ra}, wrap_angle: !astropy.coo
COMMENT rdinates.Angle {unit: !astropy.units.Unit {unit: deg}, value: 360.0}}, r
COMMENT epresentation: spherical}}}]}                                           
COMMENT --END-BASE64-ASTROPY-SERIALIZED-COLUMNS--                               

However, very good point that it is actually preserving leading spaces and so I can cut the one-line YAML by hand and put in EOL markers to preserve trailing space. Will do, I wasn't exactly happy about the encoding.

In case it isn't obvious, we definitely want to use the compact YAML format here because we cannot assume that the nice "human readable" form will always fit within the width of a COMMENT card.

@taldcroft

Copy link
Copy Markdown
Member Author

OK, now it looks like this, which is readable by sufficiently persistent humans:

COMMENT --BEGIN-BASE64-ASTROPY-SERIALIZED-COLUMNS--                             
COMMENT {datatype: [{name: col0, unit: m, datatype: float64, format: .2f}, {na\ 
COMMENT me: col1.ra, unit: deg, datatype: float64}, {name: col1.dec, unit: deg\ 
COMMENT , datatype: float64}, {name: col2, datatype: float64, format: .3f, des\ 
COMMENT cription: interesting}], meta: !!omap [{__serialized_columns__: {col0:\ 
COMMENT  {__class__: astropy.units.quantity.Quantity, value: !astropy.table.Se\ 
COMMENT rializedColumn {name: col0}}, col1: {__class__: astropy.coordinates.sk\ 
COMMENT y_coordinate.SkyCoord, __info__: {meta: {a: 1}}, dec: !astropy.table.S\ 
COMMENT erializedColumn {__class__: astropy.coordinates.angles.Latitude, value\ 
COMMENT : !astropy.table.SerializedColumn {name: col1.dec}}, frame: icrs, ra: \ 
COMMENT !astropy.table.SerializedColumn {__class__: astropy.coordinates.angles\ 
COMMENT .Longitude, value: !astropy.table.SerializedColumn {name: col1.ra}, wr\ 
COMMENT ap_angle: !astropy.coordinates.Angle {unit: !astropy.units.Unit {unit:\ 
COMMENT  deg}, value: 360.0}}, representation: spherical}}}]}\                  
COMMENT --END-BASE64-ASTROPY-SERIALIZED-COLUMNS--                               

@taldcroft

Copy link
Copy Markdown
Member Author

I guess if we really wanted the fully human readable format it would be possible to do the line extensions only for lines beyond 70 characters. I sort of think of FITS as a binary format that is not meant for direct human inspection, and making the parsing more difficult will make it harder for other (non-astropy) implementations to support this.

@astrofrog

Copy link
Copy Markdown
Member

I worry a little about the case where someone reads in the file into another program, removes a column or renames one and writes it back then reads it into astropy. Maybe we need a checksum of the data so that we can discard the YAML if the checksum disagrees?

@mhvk

mhvk commented Nov 29, 2017

Copy link
Copy Markdown
Contributor

@taldcroft - that looks good; agreed it should be the compact form, but now someone just looking at the file at least has a chance to understand what is there!

Bit more generally, as you know I have been advocating HIERARCH ASTROPY over inventing new 8-character FITS header keys, but I think your current scheme nicely avoids either. It also seems fairly future proof in that if someone actually comes up with a standard for storing, say, coordinates, in FITS, we can by default start using that technique and start omitting those from the mixin column list (like we'll presumably do for Time now).

@saimn

saimn commented Nov 29, 2017

Copy link
Copy Markdown
Contributor

Great idea! Having a human readable format is important, so not base64 blob please 😱
Ideally and if it's not too complicated, the "fully human readable format" would be best imho, because I think FITS headers are often read by humans to try to remember "what the heck is in this table" !

@taldcroft

Copy link
Copy Markdown
Member Author

OK it's fully-human-readable now, and passing tests.

@taldcroft taldcroft changed the title POC: serialize all astropy core objects to FITS Serialize all astropy core objects to FITS Dec 4, 2017
@astrofrog
astrofrog self-requested a review December 4, 2017 10:26
@mhvk

mhvk commented Dec 8, 2017

Copy link
Copy Markdown
Contributor

@taldcroft - now had a closer look and really liked it. But I noticed that TimeDelta was commented out. Should we just treat it as a mixin column for now? The FITS standard doesn't really apply to it.

A possible extension of this - for another PR - is to use the new information to ensure no info gets lost even for columns that can be represented in FITS.

@taldcroft

Copy link
Copy Markdown
Member Author

@mhvk - early in the work I had gotten the idea that TimeDelta was supported by the Time FITS machinery, although it seemed somewhat broken. But looking closer I don't see any tests, and the fact that I just commented out the TimeDelta stuff reflected that I wasn't quite sure of this.

Good catch, so now 8e70992 puts back support and this is good!

@taldcroft

Copy link
Copy Markdown
Member Author

For the record, here is the current header for a somewhat complicated example with Time, Quantity, EarthLocation and SkyCoord:

# HDU 1 in junk.fits:
XTENSION= 'BINTABLE'           / binary table extension                         
BITPIX  =                    8 / array data type                                
NAXIS   =                    2 / number of array dimensions                     
NAXIS1  =                   88 / length of dimension 1                          
NAXIS2  =                    2 / length of dimension 2                          
PCOUNT  =                    0 / number of group parameters                     
GCOUNT  =                    1 / number of groups                               
TFIELDS =                   10 / number of table fields                         
TREFPOS = 'TOPOCENTER'         / Time reference position                        
JDREF   =                  0.0 / Time columns are jd = jd1 + jd2                
TIMESYS = 'UTC     '           / Default time scale                             
OBSGEO-X=                  1.0                                                  
OBSGEO-Y=                  3.0                                                  
OBSGEO-Z=                  5.0                                                  
TTYPE1  = 'col0    '                                                            
TFORM1  = 'K       '                                                            
TTYPE2  = 'col1    '                                                            
TFORM2  = '2D      '                                                            
TUNIT2  = 'd       '                                                            
TCTYP2  = 'TT      '                                                            
TCUNI2  = 'd       '                                                            
TRPOS2  = 'TOPOCENTER'                                                          
TTYPE3  = 'col2.ra '                                                            
TFORM3  = 'D       '                                                            
TUNIT3  = 'deg     '                                                            
TTYPE4  = 'col2.dec'                                                            
TFORM4  = 'D       '                                                            
TUNIT4  = 'deg     '                                                            
TTYPE5  = 'col2.location.x'                                                     
TFORM5  = 'D       '                                                            
TUNIT5  = 'm       '                                                            
TTYPE6  = 'col2.location.y'                                                     
TFORM6  = 'D       '                                                            
TUNIT6  = 'm       '                                                            
TTYPE7  = 'col2.location.z'                                                     
TFORM7  = 'D       '                                                            
TUNIT7  = 'm       '                                                            
TTYPE8  = 'col3.x  '                                                            
TFORM8  = 'D       '                                                            
TUNIT8  = 'm       '                                                            
TTYPE9  = 'col3.y  '                                                            
TFORM9  = 'D       '                                                            
TUNIT9  = 'm       '                                                            
TTYPE10 = 'col3.z  '                                                            
TFORM10 = 'D       '                                                            
TUNIT10 = 'm       '                                                            
COMMENT --BEGIN-ASTROPY-SERIALIZED-COLUMNS--                                    
COMMENT datatype:                                                               
COMMENT - {name: col0, datatype: int64}                                         
COMMENT - {name: col1, datatype: object}                                        
COMMENT - {name: col2.ra, unit: deg, datatype: float64}                         
COMMENT - {name: col2.dec, unit: deg, datatype: float64}                        
COMMENT - {name: col2.location.x, unit: m, datatype: float64}                   
COMMENT - {name: col2.location.y, unit: m, datatype: float64}                   
COMMENT - {name: col2.location.z, unit: m, datatype: float64}                   
COMMENT - {name: col3.x, unit: m, datatype: float64}                            
COMMENT - {name: col3.y, unit: m, datatype: float64}                            
COMMENT - {name: col3.z, unit: m, datatype: float64}                            
COMMENT meta:                                                                   
COMMENT   __serialized_columns__:                                               
COMMENT     col2:                                                               
COMMENT       __class__: astropy.coordinates.sky_coordinate.SkyCoord            
COMMENT       __info__:                                                         
COMMENT         description: Sky Coord!                                         
COMMENT         meta: {a: 1}                                                    
COMMENT       dec: !astropy.table.SerializedColumn                              
COMMENT         __class__: astropy.coordinates.angles.Latitude                  
COMMENT         value: !astropy.table.SerializedColumn {name: col2.dec}         
COMMENT       frame: icrs                                                       
COMMENT       location: !astropy.table.SerializedColumn                         
COMMENT         __class__: astropy.coordinates.earth.EarthLocation              
COMMENT         ellipsoid: WGS84                                                
COMMENT         x: !astropy.table.SerializedColumn                              
COMMENT           __class__: astropy.units.quantity.Quantity                    
COMMENT           value: !astropy.table.SerializedColumn {name: col2.location.\ 
COMMENT x}                                                                      
COMMENT         y: !astropy.table.SerializedColumn                              
COMMENT           __class__: astropy.units.quantity.Quantity                    
COMMENT           value: !astropy.table.SerializedColumn {name: col2.location.\ 
COMMENT y}                                                                      
COMMENT         z: !astropy.table.SerializedColumn                              
COMMENT           __class__: astropy.units.quantity.Quantity                    
COMMENT           value: !astropy.table.SerializedColumn {name: col2.location.\ 
COMMENT z}                                                                      
COMMENT       ra: !astropy.table.SerializedColumn                               
COMMENT         __class__: astropy.coordinates.angles.Longitude                 
COMMENT         value: !astropy.table.SerializedColumn {name: col2.ra}          
COMMENT         wrap_angle: !astropy.coordinates.Angle                          
COMMENT           unit: !astropy.units.Unit {unit: deg}                         
COMMENT           value: 360.0                                                  
COMMENT       representation: spherical                                         
COMMENT     col3:                                                               
COMMENT       __class__: astropy.coordinates.earth.EarthLocation                
COMMENT       ellipsoid: WGS84                                                  
COMMENT       x: !astropy.table.SerializedColumn                                
COMMENT         __class__: astropy.units.quantity.Quantity                      
COMMENT         value: !astropy.table.SerializedColumn {name: col3.x}           
COMMENT       y: !astropy.table.SerializedColumn                                
COMMENT         __class__: astropy.units.quantity.Quantity                      
COMMENT         value: !astropy.table.SerializedColumn {name: col3.y}           
COMMENT       z: !astropy.table.SerializedColumn                                
COMMENT         __class__: astropy.units.quantity.Quantity                      
COMMENT         value: !astropy.table.SerializedColumn {name: col3.z}           
COMMENT --END-ASTROPY-SERIALIZED-COLUMNS--                                      

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really is great. I remain amazed at how nicely the work we did for ecsv is translated here. Only minor comments.

"""Encode a Table ``tbl`` that may have mixin columns to a Table with only
astropy Columns + appropriate meta-data to allow subsequent decoding.
"""
# If PyYAML is not available then check to see if there are any mixin cols

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought for quite a bit about whether we should not warn, and fe;t that perhaps we should warn if loss of information can be prevented by installing yaml.

But definitely only when actually necessary: so, I would move this test below.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. But I prefer to keep the logic here. This is mostly for code clarity, i.e. being explicit right away about the PyYAML dependence. Also if there is no YAML then there is no reason to bother with going through the serialize._represent_mixins_as_columns(...) step.

try:
encode_tbl.meta = {ser_col: encode_tbl.meta[ser_col]}
meta_yaml_lines = meta.get_yaml_from_table(encode_tbl)
finally:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add the yaml import test before the try, but might also consider adding an except ImportError here (true EAFP instead of LBYL).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As from above I think the way it is written now is a bit easier to understand and more efficient.

Comment thread astropy/io/fits/fitstime.py Outdated

# Time column reference position
if col.location is None:
if getattr(col, 'location', None) is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a left-over of trying to get TimeDelta to work? If so, probably best to remove it again. Time should always have .location.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment thread astropy/io/fits/tests/test_connect.py Outdated
a2 = {}

if isinstance(a1, np.ndarray) and a1.dtype.kind == 'f':
assert quantity_allclose(a1, a2, rtol=1e-10)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More out of curiosity, why don't floats round-trip?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, that is from ECSV where you might be comparing ASCII-serialized values. I changed this here and it seems to be passing tests.

for name, col in t.columns.items():
col2 = t2[name]

# Special-case Time, which does not yet support round-tripping

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle, I guess this could be added to the additional information stored. Maybe raise a new issue so we do not forget?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle, but we should just do this right and use a column header keyword in the TimeFITS machinery.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that there is a little issue because for Time the format attribute is a native attribute that is different from Time.info.format (with the latter not really having any meaning in this case).

So right now the Time object is ignored in the serialization process to allow for TimeFITS to do its job, and only the info gets stored in meta. Finessing this is not so easy. So again, just do it right in TimeFITS.

compare_class = True

if isinstance(col.info, QuantityInfo):
# Downgrade Quantity to Column + unit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably related to previous comment: shouldn't we be able to keep this information? It is, in principle, present in the yaml, no? (again can be for another issue).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is maybe misleading (indeed I forgot exactly where this was headed). All the info attributes are still there and tested, but what is missing are specific class-related attributes like value or wrap_angle (in the case of Longitude). So basically I think this is OK since Column cannot have a wrap_angle.

However I noticed that the data values were not being compared in this case (also for ECSV) so I fixed that.

@taldcroft

Copy link
Copy Markdown
Member Author

@mhvk - I think I addressed all your comments.

@mhvk

mhvk commented Dec 18, 2017

Copy link
Copy Markdown
Contributor

I think this looks great, thanks for addressing the nitpicks; this does need a rebase now...

Just to clarify, currently, we do save all the information from .info, independently of whether they are mixin or not, correct? I ask in part because, if so, one way to store Time.format is for Time.info.format to have a sensible link to it. (But in any case, this is separate from this PR.)

@taldcroft

Copy link
Copy Markdown
Member Author

we do save all the information from .info, independently of whether they are mixin or not, correct?

Yup!

In [2]: tm = Time([1,2,3,4], format='cxcsec')
In [3]: tm.info.format = 'blahblah'
In [4]: tm.info.description = 'my description'
In [5]: t = Table([tm])
In [6]: t.write('junk.fits', overwrite=True)
In [7]: t2 = Table.read('junk.fits', astropy_native=True)
In [8]: t2['col0'].info
Out[8]: 
name = col0
dtype = object
format = blahblah
description = my description
class = Time
n_bad = 0
length = 4

one way to store Time.format is for Time.info.format to have a sensible link to it.

I actually did try this for fun (make info format come from the parent), but it didn't work out of the box. More crucially, this comes back to the different semantic meaning of Time.format and Time.info.format. If you link the two then when you print a table it wants info.format to be some sort of string formatting information and things start breaking.

In [9]: print(t2)
   ...
ValueError: unable to parse format string blahblah for its column.

However, I think we could put in a special case to basically set time.info.format = time.format (for any time objects) before the encoding step, and conversely time.format = time.info.format; time.info.format = None after the decoding step. This little hack could be made to work.

@mhvk

mhvk commented Dec 19, 2017

Copy link
Copy Markdown
Contributor

Ah, great. The hack could also work by having info.format default to {0.<format>} and having setting it extract the format that way. But I think we should leave that to a separate PR. This one is good as is!

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ready to go in once CI passes!

@bsipocz

bsipocz commented Jan 30, 2018

Copy link
Copy Markdown
Member

@taldcroft

Copy link
Copy Markdown
Member Author

Copy, will do.

@cdeil

cdeil commented Apr 5, 2018

Copy link
Copy Markdown
Member

@taldcroft - Apologies for coming late to the discussion. Thanks for adding this! I tried it for the first time now, and wanted to ask if those are features or bugs:

Calling table.write modifies table.meta by adding comments. IMO this is an undesirable side effect, calling table.write shouldn't modify table.meta, no? Also, if you call table.write multiple times, the table serialisation info gets added multiple times to table.meta.

Finally, if you call hdu = fits.BinTableHDU(table) or hdu = fits.table_to_hdu(table), then the table serialisation COMMENTS aren't added. This is confusing, that it's automatically added by table.write, but not if I go to BinTableHDU directly. Shouldn't the default behaviour be the same in both cases?

See examples here: https://github.com/gammapy/gammapy/pull/1358/files#diff-21886c0af8e866d3b74e6042ab6761bfR123

Let me know if opening a new issue with those points is useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants