github.com/jjjabc/fitsio@v0.0.0-20161215022839-d1807e9e818e/table.go (about)

     1  // Copyright 2015 The astrogo Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fitsio
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  )
    11  
    12  type Table struct {
    13  	hdr    Header
    14  	binary bool
    15  
    16  	data []byte // main data table
    17  	heap []byte // heap data table (for variable length arrays)
    18  
    19  	rowsz  int   // size of each row in bytes (ie: NAXIS1)
    20  	nrows  int64 // number of rows (ie: NAXIS2)
    21  	cols   []Column
    22  	colidx map[string]int // associates a column name to its index
    23  }
    24  
    25  // Close closes this HDU, cleaning up cycles (if any) for garbage collection
    26  func (t *Table) Close() error {
    27  	return nil
    28  }
    29  
    30  // Header returns the Header part of this HDU block.
    31  func (t *Table) Header() *Header {
    32  	return &t.hdr
    33  }
    34  
    35  // Type returns the Type of this HDU
    36  func (t *Table) Type() HDUType {
    37  	return t.hdr.Type()
    38  }
    39  
    40  // Name returns the value of the 'EXTNAME' Card.
    41  func (t *Table) Name() string {
    42  	card := t.hdr.Get("EXTNAME")
    43  	if card == nil {
    44  		return ""
    45  	}
    46  	return card.Value.(string)
    47  }
    48  
    49  // Version returns the value of the 'EXTVER' Card (or 1 if none)
    50  func (t *Table) Version() int {
    51  	card := t.hdr.Get("EXTVER")
    52  	if card == nil {
    53  		return 1
    54  	}
    55  	return card.Value.(int)
    56  }
    57  
    58  // Data returns the image payload
    59  func (t *Table) Data() (Value, error) {
    60  	panic("not implemented")
    61  }
    62  
    63  func (t *Table) NumRows() int64 {
    64  	return t.nrows
    65  }
    66  
    67  func (t *Table) NumCols() int {
    68  	return len(t.cols)
    69  }
    70  
    71  func (t *Table) Cols() []Column {
    72  	return t.cols
    73  }
    74  
    75  func (t *Table) Col(i int) *Column {
    76  	return &t.cols[i]
    77  }
    78  
    79  // Index returns the index of the first column with name `n` or -1
    80  func (t *Table) Index(n string) int {
    81  	idx, ok := t.colidx[n]
    82  	if !ok {
    83  		return -1
    84  	}
    85  	return idx
    86  }
    87  
    88  // ReadRange reads rows over the range [beg, end) and returns the corresponding iterator.
    89  // if end > maxrows, the iteration will stop at maxrows
    90  // ReadRange has the same semantics than a `for i=0; i < max; i+=inc {...}` loop
    91  func (t *Table) ReadRange(beg, end, inc int64) (*Rows, error) {
    92  	var err error
    93  	var rows *Rows
    94  
    95  	maxrows := t.NumRows()
    96  	if end > maxrows {
    97  		end = maxrows
    98  	}
    99  
   100  	if beg < 0 {
   101  		beg = 0
   102  	}
   103  
   104  	cols := make([]int, len(t.cols))
   105  	for i := range t.cols {
   106  		cols[i] = i
   107  	}
   108  
   109  	rows = &Rows{
   110  		table: t,
   111  		cols:  cols,
   112  		i:     beg,
   113  		n:     end,
   114  		inc:   inc,
   115  		cur:   beg - inc,
   116  		err:   nil,
   117  		icols: make(map[reflect.Type][][2]int),
   118  	}
   119  	return rows, err
   120  }
   121  
   122  // Read reads rows over the range [beg, end) and returns the corresponding iterator.
   123  // if end > maxrows, the iteration will stop at maxrows
   124  // ReadRange has the same semantics than a `for i=0; i < max; i++ {...}` loop
   125  func (t *Table) Read(beg, end int64) (*Rows, error) {
   126  	return t.ReadRange(beg, end, 1)
   127  }
   128  
   129  // NewTable creates a new table in the given FITS file
   130  func NewTable(name string, cols []Column, hdutype HDUType) (*Table, error) {
   131  	var err error
   132  
   133  	isbinary := true
   134  	switch hdutype {
   135  	case ASCII_TBL:
   136  		isbinary = false
   137  	case BINARY_TBL:
   138  		isbinary = true
   139  	default:
   140  		return nil, fmt.Errorf("fitsio: invalid HDUType (%v)", hdutype)
   141  	}
   142  
   143  	ncols := len(cols)
   144  	table := &Table{
   145  		hdr:    Header{},
   146  		binary: isbinary,
   147  		data:   make([]byte, 0),
   148  		heap:   make([]byte, 0),
   149  		rowsz:  0, // NAXIS1 in bytes
   150  		nrows:  0, // NAXIS2
   151  		cols:   make([]Column, ncols),
   152  		colidx: make(map[string]int, ncols),
   153  	}
   154  
   155  	copy(table.cols, cols)
   156  
   157  	cards := make([]Card, 0, len(cols)+2)
   158  	cards = append(
   159  		cards,
   160  		Card{
   161  			Name:    "TFIELDS",
   162  			Value:   ncols,
   163  			Comment: "number of fields in each row",
   164  		},
   165  	)
   166  
   167  	offset := 0
   168  	for i := 0; i < ncols; i++ {
   169  		col := &table.cols[i]
   170  		col.offset = offset
   171  		switch hdutype {
   172  		case BINARY_TBL:
   173  			col.write = col.writeBin
   174  			col.read = col.readBin
   175  		case ASCII_TBL:
   176  			col.write = col.writeTxt
   177  			col.read = col.readTxt
   178  		default:
   179  			return nil, fmt.Errorf("fitsio: invalid HDUType (%v)", hdutype)
   180  		}
   181  
   182  		table.colidx[col.Name] = i
   183  
   184  		if col.Format == "" {
   185  			return nil, fmt.Errorf("fitsio: column (col=%s) has NO valid format", col.Name)
   186  		}
   187  
   188  		cards = append(cards,
   189  			Card{
   190  				Name:    fmt.Sprintf("TTYPE%d", i+1),
   191  				Value:   col.Name,
   192  				Comment: fmt.Sprintf("label for column %d", i+1),
   193  			},
   194  			Card{
   195  				Name:    fmt.Sprintf("TFORM%d", i+1),
   196  				Value:   col.Format,
   197  				Comment: fmt.Sprintf("data format for column %d", i+1),
   198  			},
   199  		)
   200  
   201  		col.dtype, err = typeFromForm(col.Format, hdutype)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  
   206  		offset += col.dtype.dsize * col.dtype.len
   207  		col.txtfmt = txtfmtFromForm(col.Format)
   208  
   209  		if offset == 0 && i > 0 {
   210  			return nil, fmt.Errorf("fitsio: invalid data-layout")
   211  		}
   212  
   213  		if col.Unit != "" {
   214  			cards = append(cards,
   215  				Card{
   216  					Name:    fmt.Sprintf("TUNIT%d", i+1),
   217  					Value:   col.Unit,
   218  					Comment: fmt.Sprintf("unit for column %d", i+1),
   219  				},
   220  			)
   221  		}
   222  
   223  		if col.Null != "" {
   224  			cards = append(cards,
   225  				Card{
   226  					Name:    fmt.Sprintf("TNULL%d", i+1),
   227  					Value:   col.Null,
   228  					Comment: fmt.Sprintf("default value for column %d", i+1),
   229  				},
   230  			)
   231  		}
   232  
   233  		cards = append(cards,
   234  			Card{
   235  				Name:    fmt.Sprintf("TSCAL%d", i+1),
   236  				Value:   col.Bscale,
   237  				Comment: fmt.Sprintf("scaling offset for column %d", i+1),
   238  			},
   239  		)
   240  
   241  		cards = append(cards,
   242  			Card{
   243  				Name:    fmt.Sprintf("TZERO%d", i+1),
   244  				Value:   col.Bzero,
   245  				Comment: fmt.Sprintf("zero value for column %d", i+1),
   246  			},
   247  		)
   248  
   249  		if col.Start != 0 {
   250  			cards = append(cards,
   251  				Card{
   252  					Name:  fmt.Sprintf("TBCOL%d", i+1),
   253  					Value: int(col.Start),
   254  				},
   255  			)
   256  		} else {
   257  			cards = append(cards,
   258  				Card{
   259  					Name:  fmt.Sprintf("TBCOL%d", i+1),
   260  					Value: offset - col.dtype.dsize*col.dtype.len + 1,
   261  				},
   262  			)
   263  		}
   264  
   265  		if col.Display != "" {
   266  			cards = append(cards,
   267  				Card{
   268  					Name:    fmt.Sprintf("TDISP%d", i+1),
   269  					Value:   col.Display,
   270  					Comment: fmt.Sprintf("display format for column %d", i+1),
   271  				},
   272  			)
   273  		}
   274  
   275  		if len(col.Dim) > 0 {
   276  			str := "("
   277  			for idim, dim := range col.Dim {
   278  				str += fmt.Sprintf("%d", dim)
   279  				if idim+1 < len(col.Dim) {
   280  					str += ","
   281  				}
   282  			}
   283  			str += ")"
   284  			cards = append(cards,
   285  				Card{
   286  					Name:  fmt.Sprintf("TDIM%d", i+1),
   287  					Value: str,
   288  				},
   289  			)
   290  		}
   291  
   292  	}
   293  
   294  	cards = append(
   295  		cards,
   296  		Card{
   297  			Name:    "EXTNAME",
   298  			Value:   name,
   299  			Comment: "name of this table extension",
   300  		},
   301  	)
   302  
   303  	bitpix := 8
   304  	hdr := newHeader(cards, hdutype, bitpix, []int{offset, 0})
   305  	table.hdr = *hdr
   306  	table.rowsz = offset
   307  
   308  	return table, err
   309  }
   310  
   311  // NewTableFrom creates a new table in the given FITS file, using the struct v as schema
   312  func NewTableFrom(name string, v Value, hdutype HDUType) (*Table, error) {
   313  	rv := reflect.Indirect(reflect.ValueOf(v))
   314  	rt := rv.Type()
   315  	if rt.Kind() != reflect.Struct {
   316  		return nil, fmt.Errorf("fitsio: NewTableFrom takes a struct value. got: %T", v)
   317  	}
   318  	nmax := rt.NumField()
   319  	cols := make([]Column, 0, nmax)
   320  	for i := 0; i < nmax; i++ {
   321  		ft := rt.Field(i)
   322  		name := ft.Tag.Get("fits")
   323  		if name == "" {
   324  			name = ft.Name
   325  		}
   326  		field := rv.Field(i)
   327  		form := formFromGoType(field.Type(), hdutype)
   328  		if form == "" {
   329  			return nil, fmt.Errorf("fitsio: no FITS TFORM for field [%d] %#v", i, field.Interface())
   330  		}
   331  		cols = append(cols,
   332  			Column{
   333  				Name:   name,
   334  				Format: form,
   335  			},
   336  		)
   337  	}
   338  	return NewTable(name, cols, hdutype)
   339  }
   340  
   341  // Write writes the data into the columns at the current row.
   342  func (t *Table) Write(args ...interface{}) error {
   343  	var err error
   344  
   345  	t.data = append(t.data, make([]byte, t.rowsz)...)
   346  
   347  	switch len(args) {
   348  	case 0:
   349  		return fmt.Errorf("fitsio: Rows.Scan needs at least one argument")
   350  
   351  	case 1:
   352  		// maybe special case: map? struct?
   353  		rt := reflect.TypeOf(args[0]).Elem()
   354  		switch rt.Kind() {
   355  		case reflect.Map:
   356  			err = t.writeMap(*args[0].(*map[string]interface{}))
   357  		case reflect.Struct:
   358  			err = t.writeStruct(args[0])
   359  		default:
   360  			err = t.write(args[0])
   361  		}
   362  	default:
   363  		err = t.write(args...)
   364  	}
   365  
   366  	if err != nil {
   367  		return err
   368  	}
   369  
   370  	t.nrows += 1
   371  	t.hdr.axes[1] += 1
   372  	return err
   373  }
   374  
   375  func (t *Table) write(args ...interface{}) error {
   376  	var err error
   377  	if len(args) != len(t.cols) {
   378  		return fmt.Errorf(
   379  			"fitsio.Table.Write: invalid number of arguments (got %d. expected %d)",
   380  			len(args),
   381  			len(t.cols),
   382  		)
   383  	}
   384  
   385  	for i := range t.cols {
   386  		err = t.cols[i].write(t, i, t.nrows, args[i])
   387  		if err != nil {
   388  			return err
   389  		}
   390  	}
   391  
   392  	return err
   393  }
   394  
   395  func (t *Table) writeMap(data map[string]interface{}) error {
   396  	var err error
   397  	icols := make([]int, 0, len(data))
   398  	switch len(data) {
   399  	case 0:
   400  		icols = make([]int, len(t.cols))
   401  		for i := range t.cols {
   402  			icols[i] = i
   403  		}
   404  	default:
   405  		for k := range data {
   406  			icol := t.Index(k)
   407  			if icol >= 0 {
   408  				icols = append(icols, icol)
   409  			}
   410  		}
   411  	}
   412  
   413  	for _, icol := range icols {
   414  		col := t.Col(icol)
   415  		val := reflect.New(col.Type())
   416  		err = col.write(t, icol, t.nrows, val.Interface())
   417  		if err != nil {
   418  			return err
   419  		}
   420  		data[col.Name] = val.Elem().Interface()
   421  	}
   422  	return err
   423  }
   424  
   425  func (t *Table) writeStruct(data interface{}) error {
   426  	var err error
   427  	rt := reflect.TypeOf(data).Elem()
   428  	rv := reflect.ValueOf(data).Elem()
   429  	icols := make([][2]int, 0, rt.NumField())
   430  
   431  	if true { // fixme: devise a cache ?
   432  		for i := 0; i < rt.NumField(); i++ {
   433  			f := rt.Field(i)
   434  			n := f.Tag.Get("fits")
   435  			if n == "" {
   436  				n = f.Name
   437  			}
   438  			icol := t.Index(n)
   439  			if icol >= 0 {
   440  				icols = append(icols, [2]int{i, icol})
   441  			}
   442  		}
   443  	}
   444  
   445  	for _, icol := range icols {
   446  		col := &t.cols[icol[1]]
   447  		field := rv.Field(icol[0])
   448  		value := field.Addr().Interface()
   449  		err = col.write(t, icol[1], t.nrows, value)
   450  		if err != nil {
   451  			return err
   452  		}
   453  	}
   454  	return err
   455  }
   456  
   457  // freeze freezes a Table before writing, calculating offsets and finalizing header values.
   458  func (t *Table) freeze() error {
   459  	var err error
   460  	nrows := t.nrows
   461  	t.hdr.axes[1] = int(nrows)
   462  
   463  	if card := t.Header().Get("XTENSION"); card == nil {
   464  		hduext := ""
   465  		if t.binary {
   466  			hduext = "BINTABLE"
   467  		} else {
   468  			hduext = "TABLE   "
   469  		}
   470  		cards := []Card{
   471  			{
   472  				Name:    "XTENSION",
   473  				Value:   hduext,
   474  				Comment: "table extension",
   475  			},
   476  			{
   477  				Name:    "BITPIX",
   478  				Value:   t.Header().Bitpix(),
   479  				Comment: "number of bits per data pixel",
   480  			},
   481  			{
   482  				Name:    "NAXIS",
   483  				Value:   len(t.Header().Axes()),
   484  				Comment: "number of data axes",
   485  			},
   486  			{
   487  				Name:    "NAXIS1",
   488  				Value:   t.Header().Axes()[0],
   489  				Comment: "length of data axis 1",
   490  			},
   491  			{
   492  				Name:    "NAXIS2",
   493  				Value:   t.Header().Axes()[1],
   494  				Comment: "length of data axis 2",
   495  			},
   496  			{
   497  				Name:    "PCOUNT",
   498  				Value:   len(t.heap),
   499  				Comment: "heap area size (bytes)",
   500  			},
   501  			{
   502  				Name:    "GCOUNT",
   503  				Value:   1,
   504  				Comment: "one data group",
   505  			},
   506  		}
   507  
   508  		err = t.hdr.prepend(cards...)
   509  		if err != nil {
   510  			return err
   511  		}
   512  	}
   513  
   514  	if card := t.Header().Get("THEAP"); card == nil {
   515  		err = t.hdr.Append([]Card{
   516  			{
   517  				Name:    "THEAP",
   518  				Value:   0,
   519  				Comment: "gap size (bytes)",
   520  			},
   521  		}...)
   522  	}
   523  
   524  	return err
   525  }
   526  
   527  // CopyTable copies all the rows from src into dst.
   528  func CopyTable(dst, src *Table) error {
   529  	return CopyTableRange(dst, src, 0, src.NumRows())
   530  }
   531  
   532  // CopyTableRange copies the rows interval [beg,end) from src into dst
   533  func CopyTableRange(dst, src *Table, beg, end int64) error {
   534  	var err error
   535  	if dst == nil {
   536  		return fmt.Errorf("fitsio: dst pointer is nil")
   537  	}
   538  	if src == nil {
   539  		return fmt.Errorf("fitsio: src pointer is nil")
   540  	}
   541  
   542  	vla := false
   543  	for _, col := range src.Cols() {
   544  		if col.dtype.tc < 0 {
   545  			vla = true
   546  			break
   547  		}
   548  	}
   549  
   550  	// FIXME(sbinet)
   551  	// need to also handle VLAs
   552  	// src.heap -> dst.heap
   553  	// convert offsets into dst.heap
   554  	//
   555  	// for the time being: go the slow way
   556  	switch vla {
   557  
   558  	case true:
   559  		rows, err := src.Read(beg, end)
   560  		if err != nil {
   561  			return err
   562  		}
   563  		defer rows.Close()
   564  
   565  		ncols := len(src.Cols())
   566  		data := make([]interface{}, ncols)
   567  		for i := range src.Cols() {
   568  			col := &src.cols[i]
   569  			rt := col.dtype.gotype
   570  			rv := reflect.New(rt)
   571  			xx := rv.Interface()
   572  			data[i] = xx
   573  		}
   574  		for rows.Next() {
   575  			err = rows.Scan(data...)
   576  			if err != nil {
   577  				return err
   578  			}
   579  			err = dst.Write(data...)
   580  			if err != nil {
   581  				return err
   582  			}
   583  		}
   584  		err = rows.Err()
   585  		if err != nil {
   586  			return err
   587  		}
   588  
   589  		return err
   590  
   591  	case false:
   592  		nrows := end - beg
   593  		// reserve enough capacity for the new rows
   594  		dst.data = dst.data[:len(dst.data) : len(dst.data)+int(nrows)*src.rowsz]
   595  		for irow := beg; irow < end; irow++ {
   596  			pstart := src.rowsz * int(irow)
   597  			pend := pstart + src.rowsz
   598  			row := src.data[pstart:pend]
   599  			dst.data = append(dst.data, row...)
   600  		}
   601  		dst.nrows += nrows
   602  		dst.hdr.Axes()[1] += int(nrows)
   603  	}
   604  
   605  	return err
   606  }