github.com/astrogo/fitsio@v0.3.0/decode.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  	"io"
    10  	"io/ioutil"
    11  	"reflect"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  type Decoder interface {
    17  	DecodeHDU() (HDU, error)
    18  }
    19  
    20  // NewDecoder creates a new Decoder according to the capabilities of the underlying io.Reader
    21  func NewDecoder(r io.Reader) Decoder {
    22  	// FIXME(sbinet)
    23  	// if rr, ok := r.(io.ReadSeeker); ok {
    24  	// 	return &seekDecoder{r: rr}
    25  	// }
    26  	return &streamDecoder{r: r}
    27  }
    28  
    29  // streamDecoder is a decoder which can not perform random access
    30  // into the underlying Reader
    31  type streamDecoder struct {
    32  	r io.Reader
    33  }
    34  
    35  func (dec *streamDecoder) DecodeHDU() (HDU, error) {
    36  	var err error
    37  	var hdu HDU
    38  
    39  	cards := make(map[string]int, 30)
    40  	slice := make([]Card, 0, 1)
    41  
    42  	get_card := func(k string) (Card, bool) {
    43  		i, ok := cards[k]
    44  		if ok {
    45  			return slice[i], ok
    46  		}
    47  		return Card{}, ok
    48  	}
    49  
    50  	add_card := func(c *Card) {
    51  		n := c.Name
    52  		if n == "COMMENT" || n == "HISTORY" || n == "" {
    53  			slice = append(slice, *c)
    54  			return
    55  		}
    56  		// For compatibility with C FITSIO, silently swallow duplicate card keys.
    57  		// See:
    58  		//  https://github.com/astrogo/fitsio/issues/38
    59  		//
    60  		// _, dup := cards[n]
    61  		// if dup {
    62  		// 	panic(fmt.Errorf("fitsio: duplicate keyword [%s]", n))
    63  		// }
    64  		cards[n] = len(slice)
    65  		slice = append(slice, *c)
    66  	}
    67  
    68  	axes := []int{}
    69  	buf := make([]byte, blockSize)
    70  
    71  	iblock := -1
    72  blocks_loop:
    73  	for {
    74  		iblock += 1
    75  		_, err = io.ReadFull(dec.r, buf)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  
    80  		// each FITS header block is comprised of up to 36 80-byte lines
    81  		const maxlines = 36
    82  		for i := 0; i < maxlines; i++ {
    83  			card, err := parseHeaderLine(buf[i*80 : (i+1)*80])
    84  			if err != nil {
    85  				return nil, err
    86  			}
    87  			if card.Name == "CONTINUE" {
    88  				idx := len(slice) - 1
    89  				last := slice[idx]
    90  				str := last.Value.(string)
    91  				if len(str) > 0 {
    92  					last.Value = str[:len(str)-1] + card.Comment
    93  				}
    94  				slice[idx] = last
    95  				continue
    96  			}
    97  			add_card(card)
    98  			if card.Name == "END" {
    99  				break
   100  			}
   101  		}
   102  
   103  		_, ends := get_card("END")
   104  		if ends {
   105  			card, ok := get_card("NAXIS")
   106  			if ok {
   107  				n := card.Value.(int)
   108  				axes = make([]int, n)
   109  				for i := 0; i < n; i++ {
   110  					k := fmt.Sprintf("NAXIS%d", i+1)
   111  					c, ok := get_card(k)
   112  					if !ok {
   113  						return nil, fmt.Errorf("fitsio: missing '%s' key", k)
   114  					}
   115  					axes[i] = c.Value.(int)
   116  				}
   117  			}
   118  			break blocks_loop
   119  		}
   120  	}
   121  
   122  	htype, primary, err := hduTypeFrom(slice)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	bitpix := 0
   128  	if card, ok := get_card("BITPIX"); ok {
   129  		bitpix = int(reflect.ValueOf(card.Value).Int())
   130  	} else {
   131  		return nil, fmt.Errorf("fitsio: missing 'BITPIX' card")
   132  	}
   133  
   134  	hdr := NewHeader(slice, htype, bitpix, axes)
   135  	switch htype {
   136  	case IMAGE_HDU:
   137  		var data []byte
   138  		data, err = dec.loadImage(hdr)
   139  		if err != nil {
   140  			return nil, fmt.Errorf("fitsio: error loading image: %v", err)
   141  		}
   142  
   143  		switch primary {
   144  		case true:
   145  			hdu = &primaryHDU{
   146  				imageHDU: imageHDU{
   147  					hdr: *hdr,
   148  					raw: data,
   149  				},
   150  			}
   151  		case false:
   152  			hdu = &imageHDU{
   153  				hdr: *hdr,
   154  				raw: data,
   155  			}
   156  		}
   157  
   158  	case BINARY_TBL:
   159  		hdu, err = dec.loadTable(hdr, htype)
   160  		if err != nil {
   161  			return nil, fmt.Errorf("fitsio: error loading binary table: %v", err)
   162  		}
   163  
   164  	case ASCII_TBL:
   165  		hdu, err = dec.loadTable(hdr, htype)
   166  		if err != nil {
   167  			return nil, fmt.Errorf("fitsio: error loading ascii table: %v", err)
   168  		}
   169  
   170  	case ANY_HDU:
   171  		fallthrough
   172  	default:
   173  		return nil, fmt.Errorf("fitsio: invalid HDU Type (%v)", htype)
   174  	}
   175  
   176  	return hdu, err
   177  }
   178  
   179  func (dec *streamDecoder) loadImage(hdr *Header) ([]byte, error) {
   180  	var err error
   181  	var buf []byte
   182  
   183  	nelmts := 1
   184  	for _, dim := range hdr.Axes() {
   185  		nelmts *= dim
   186  	}
   187  
   188  	if len(hdr.Axes()) <= 0 {
   189  		nelmts = 0
   190  	}
   191  
   192  	pixsz := hdr.Bitpix() / 8
   193  	if pixsz < 0 {
   194  		pixsz = -pixsz
   195  	}
   196  
   197  	buf = make([]byte, nelmts*pixsz)
   198  	if nelmts == 0 {
   199  		return buf, nil
   200  	}
   201  
   202  	n, err := io.ReadFull(dec.r, buf)
   203  	if err != nil {
   204  		return nil, fmt.Errorf("fitsio: error reading %d bytes (got %d): %v", len(buf), n, err)
   205  	}
   206  
   207  	// data array is also aligned at 2880-bytes blocks
   208  	pad := padBlock(n)
   209  	if pad > 0 {
   210  		if n, err := io.CopyN(ioutil.Discard, dec.r, int64(pad)); err != nil {
   211  			return nil, fmt.Errorf("fitsio: error reading %d bytes (got %d): %v", pad, n, err)
   212  		}
   213  	}
   214  
   215  	return buf, err
   216  }
   217  
   218  func (dec *streamDecoder) loadTable(hdr *Header, htype HDUType) (*Table, error) {
   219  	var err error
   220  	var table *Table
   221  
   222  	isbinary := true
   223  	switch htype {
   224  	case ASCII_TBL:
   225  		isbinary = false
   226  	case BINARY_TBL:
   227  		isbinary = true
   228  	default:
   229  		return nil, fmt.Errorf("fitsio: invalid HDU type (%v)", htype)
   230  	}
   231  
   232  	rowsz := hdr.Axes()[0]
   233  	nrows := int64(hdr.Axes()[1])
   234  	ncols := 0
   235  	if card := hdr.Get("TFIELDS"); card != nil && card.Value != nil {
   236  		ncols = card.Value.(int)
   237  	}
   238  
   239  	datasz := int(nrows) * rowsz
   240  	heapsz := 0
   241  	if card := hdr.Get("PCOUNT"); card != nil && card.Value != nil {
   242  		heapsz = card.Value.(int)
   243  	}
   244  
   245  	blocksz := alignBlock(datasz + heapsz)
   246  
   247  	block := make([]byte, blocksz)
   248  	n, err := io.ReadFull(dec.r, block)
   249  	if err != nil {
   250  		return nil, fmt.Errorf("fitsio: error reading %d bytes (got %d): %v", len(block), n, err)
   251  	}
   252  
   253  	gapsz := 0
   254  	if card := hdr.Get("THEAP"); card != nil && card.Value != nil {
   255  		gapsz = card.Value.(int)
   256  	}
   257  
   258  	data := block[:datasz]
   259  	heap := block[datasz+gapsz:]
   260  
   261  	cols := make([]Column, ncols)
   262  	colidx := make(map[string]int, ncols)
   263  
   264  	get := func(str string, ii int) *Card {
   265  		return hdr.Get(fmt.Sprintf(str+"%d", ii+1))
   266  	}
   267  
   268  	offset := 0
   269  	for i := 0; i < ncols; i++ {
   270  		col := &cols[i]
   271  
   272  		switch htype {
   273  		case BINARY_TBL:
   274  			col.write = col.writeBin
   275  			col.read = col.readBin
   276  		case ASCII_TBL:
   277  			col.write = col.writeTxt
   278  			col.read = col.readTxt
   279  		default:
   280  			return nil, fmt.Errorf("fitsio: invalid HDUType (%v)", htype)
   281  		}
   282  
   283  		col.offset = offset
   284  
   285  		card := get("TTYPE", i)
   286  		col.Name = card.Value.(string)
   287  
   288  		card = get("TFORM", i)
   289  		if card == nil {
   290  			return nil, fmt.Errorf("fitsio: missing 'TFORM%d' for column '%s'", i+1, col.Name)
   291  		} else {
   292  			col.Format = card.Value.(string)
   293  		}
   294  
   295  		card = get("TUNIT", i)
   296  		if card != nil && card.Value != nil {
   297  			col.Unit = card.Value.(string)
   298  		}
   299  
   300  		card = get("TNULL", i)
   301  		if card != nil && card.Value != nil {
   302  			col.Null = fmt.Sprintf("%v", card.Value)
   303  		}
   304  
   305  		card = get("TSCAL", i)
   306  		if card != nil && card.Value != nil {
   307  			switch vv := card.Value.(type) {
   308  			case float64:
   309  				col.Bscale = vv
   310  			case int64:
   311  				col.Bscale = float64(vv)
   312  			case int:
   313  				col.Bscale = float64(vv)
   314  			default:
   315  				return nil, fmt.Errorf("fitsio: unhandled type [%T]", vv)
   316  			}
   317  		} else {
   318  			col.Bscale = 1.0
   319  		}
   320  
   321  		card = get("TZERO", i)
   322  		if card != nil && card.Value != nil {
   323  			switch vv := card.Value.(type) {
   324  			case float64:
   325  				col.Bzero = vv
   326  			case int64:
   327  				col.Bzero = float64(vv)
   328  			case int:
   329  				col.Bzero = float64(vv)
   330  			default:
   331  				return nil, fmt.Errorf("fitsio: unhandled type [%T]", vv)
   332  			}
   333  		} else {
   334  			col.Bzero = 0.0
   335  		}
   336  
   337  		card = get("TDISP", i)
   338  		if card != nil && card.Value != nil {
   339  			col.Display = card.Value.(string)
   340  		}
   341  
   342  		card = get("TDIM", i)
   343  		if card != nil && card.Value != nil {
   344  			dims := card.Value.(string)
   345  			dims = strings.Replace(dims, "(", "", -1)
   346  			dims = strings.Replace(dims, ")", "", -1)
   347  			toks := make([]string, 0)
   348  			for _, tok := range strings.Split(dims, ",") {
   349  				tok = strings.Trim(tok, " \t\n")
   350  				if tok == "" {
   351  					continue
   352  				}
   353  				toks = append(toks, tok)
   354  			}
   355  			col.Dim = make([]int64, 0, len(toks))
   356  			for _, tok := range toks {
   357  				dim, err := strconv.ParseInt(tok, 10, 64)
   358  				if err != nil {
   359  					return nil, err
   360  				}
   361  				col.Dim = append(col.Dim, dim)
   362  			}
   363  		}
   364  
   365  		card = get("TBCOL", i)
   366  		if card != nil && card.Value != nil {
   367  			col.Start = int64(card.Value.(int))
   368  		}
   369  
   370  		col.dtype, err = typeFromForm(col.Format, htype)
   371  		if err != nil {
   372  			return nil, err
   373  		}
   374  		offset += col.dtype.dsize * col.dtype.len
   375  		if htype == ASCII_TBL {
   376  			col.txtfmt = txtfmtFromForm(col.Format)
   377  		}
   378  		colidx[col.Name] = i
   379  	}
   380  
   381  	table = &Table{
   382  		hdr:    *hdr,
   383  		binary: isbinary,
   384  		data:   data,
   385  		heap:   heap,
   386  		rowsz:  rowsz,
   387  		nrows:  nrows,
   388  		cols:   cols,
   389  		colidx: colidx,
   390  	}
   391  
   392  	return table, err
   393  }
   394  
   395  // seekDecoder is a decoder which can perform random access into
   396  // the underlying Reader
   397  type seekDecoder struct {
   398  	r io.ReadSeeker
   399  }
   400  
   401  func (dec *seekDecoder) DecodeHDU() (HDU, error) {
   402  	panic("not implemented")
   403  }
   404  
   405  func hduTypeFrom(cards []Card) (HDUType, bool, error) {
   406  	var err error
   407  	var htype HDUType = -1
   408  	var primary bool
   409  
   410  	keys := make([]string, 0, len(cards))
   411  	for _, card := range cards {
   412  		keys = append(keys, card.Name)
   413  		switch card.Name {
   414  		case "SIMPLE":
   415  			primary = true
   416  			return IMAGE_HDU, primary, nil
   417  		case "XTENSION":
   418  			str := card.Value.(string)
   419  			switch str {
   420  			case "IMAGE":
   421  				htype = IMAGE_HDU
   422  			case "TABLE":
   423  				htype = ASCII_TBL
   424  			case "BINTABLE":
   425  				htype = BINARY_TBL
   426  			case "ANY", "ANY_HDU":
   427  				htype = ANY_HDU
   428  			default:
   429  				return htype, primary, fmt.Errorf("fitsio: invalid 'XTENSION' value: %q", str)
   430  			}
   431  
   432  			return htype, primary, err
   433  		}
   434  	}
   435  
   436  	return htype, primary, fmt.Errorf("fitsio: invalid header (missing 'SIMPLE' or 'XTENSION' card): keys=%v", keys)
   437  }