github.com/jjjabc/fitsio@v0.0.0-20161215022839-d1807e9e818e/utils.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  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"math/big"
    12  	"reflect"
    13  	"strconv"
    14  	"strings"
    15  )
    16  
    17  // var (
    18  // 	g_debug = false
    19  // )
    20  // func printf(format string, args ...interface{}) (int, error) {
    21  // 	if g_debug {
    22  // 		return fmt.Printf(format, args...)
    23  // 	}
    24  // 	return 0, nil
    25  // }
    26  
    27  // readBlock reads a full FITS block
    28  func readBlock(r io.Reader) ([]byte, error) {
    29  	buf := make([]byte, blockSize)
    30  	n, err := r.Read(buf)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	if n != blockSize {
    35  		return nil, fmt.Errorf("fitsio: read too few bytes: %d. expected %d", n, blockSize)
    36  	}
    37  	return buf, nil
    38  }
    39  
    40  // alignBlock returns a size adjusted to align at a FITS block size
    41  func alignBlock(sz int) int {
    42  	padding := padBlock(sz)
    43  	return sz + padding
    44  }
    45  
    46  // padBlock returns the amount of padding to align to a FITS block size
    47  func padBlock(sz int) int {
    48  	padding := (blockSize - (sz % blockSize)) % blockSize
    49  	return padding
    50  }
    51  
    52  // processString is utilized by DecodeHDU to process string-type values in the header
    53  // it uses a 3-state machine to process double single quotes
    54  func processString(s string) (string, int, error) {
    55  	var buf bytes.Buffer
    56  
    57  	state := 0
    58  	for i, char := range s {
    59  		quote := (char == '\'')
    60  		switch state {
    61  		case 0:
    62  			if !quote {
    63  				return "", i, fmt.Errorf("fitsio: string does not start with a quote (%q)", s)
    64  			}
    65  			state = 1
    66  		case 1:
    67  			if quote {
    68  				state = 2
    69  			} else {
    70  				buf.WriteRune(char)
    71  				state = 1
    72  			}
    73  		case 2:
    74  			if quote {
    75  				buf.WriteRune(char)
    76  				state = 1
    77  			} else {
    78  				return strings.TrimRight(buf.String(), " "), i, nil
    79  			}
    80  		}
    81  	}
    82  	if s[len(s)-1] == '\'' {
    83  		return strings.TrimRight(buf.String(), " "), len(s), nil
    84  	}
    85  	return "", 0, fmt.Errorf("fitsio: string ends prematurely (%q)", s)
    86  }
    87  
    88  // parseHeaderLine parses a 80-byte line from an input header FITS block.
    89  // transliteration of CFITSIO's ffpsvc.
    90  func parseHeaderLine(bline []byte) (*Card, error) {
    91  	var err error
    92  	var card Card
    93  
    94  	valpos := 0
    95  	keybeg := 0
    96  	keyend := 0
    97  
    98  	const (
    99  		kLINE = 80
   100  	)
   101  
   102  	var (
   103  		kHIERARCH = []byte("HIERARCH ")
   104  		kCOMMENT  = []byte("COMMENT ")
   105  		kCONTINUE = []byte("CONTINUE")
   106  		kHISTORY  = []byte("HISTORY ")
   107  		kEND      = []byte("END     ")
   108  		kEMPTY    = []byte("        ")
   109  	)
   110  
   111  	if len(bline) != kLINE {
   112  		return nil, fmt.Errorf("fitsio: invalid header line length")
   113  	}
   114  
   115  	// support for ESO HIERARCH keywords: find the '='
   116  	if bytes.HasPrefix(bline, kHIERARCH) {
   117  		idx := bytes.Index(bline, []byte("="))
   118  		if idx < 0 {
   119  			// no value indicator
   120  			card.Comment = strings.TrimRight(string(bline[8:]), " ")
   121  			return &card, nil
   122  		}
   123  		valpos = idx + 1 // point after '='
   124  		keybeg = len(kHIERARCH)
   125  		keyend = idx
   126  
   127  	} else if len(bline) < 9 ||
   128  		bytes.HasPrefix(bline, kCOMMENT) ||
   129  		bytes.HasPrefix(bline, kCONTINUE) ||
   130  		bytes.HasPrefix(bline, kHISTORY) ||
   131  		bytes.HasPrefix(bline, kEND) ||
   132  		bytes.HasPrefix(bline, kEMPTY) ||
   133  		!bytes.HasPrefix(bline[8:], []byte("= ")) { // no '= ' in cols 9-10
   134  
   135  		// no value, so the comment extends from cols 9 - 80
   136  		card.Comment = strings.TrimRight(string(bline[8:]), " ")
   137  
   138  		if bytes.HasPrefix(bline, kCOMMENT) {
   139  			card.Name = "COMMENT"
   140  		} else if bytes.HasPrefix(bline, kCONTINUE) {
   141  			card.Name = "CONTINUE"
   142  			str := strings.TrimSpace(string(bline[len(kCONTINUE):]))
   143  			value, _, err := processString(str)
   144  			if err != nil {
   145  				return nil, err
   146  			}
   147  			card.Comment = value
   148  			return &card, nil
   149  
   150  		} else if bytes.HasPrefix(bline, kHISTORY) {
   151  			card.Name = "HISTORY"
   152  		} else if bytes.HasPrefix(bline, kEND) {
   153  			card.Name = "END"
   154  		} else if bytes.HasPrefix(bline, kEMPTY) ||
   155  			!bytes.HasPrefix(bline[8:], []byte("= ")) {
   156  			card.Name = ""
   157  		}
   158  
   159  		return &card, nil
   160  	} else {
   161  		valpos = 10
   162  		keybeg = 0
   163  		keyend = 8
   164  	}
   165  
   166  	card.Name = strings.TrimSpace(string(bline[keybeg:keyend]))
   167  
   168  	// find number of leading blanks
   169  	nblanks := 0
   170  	for _, c := range bline[valpos:] {
   171  		if c != ' ' {
   172  			break
   173  		}
   174  		nblanks += 1
   175  	}
   176  
   177  	if nblanks+valpos == len(bline) {
   178  		// the absence of a value string is legal and simply indicates
   179  		// that the keyword value is undefined.
   180  		// don't write an error message in this case
   181  		return &card, nil
   182  	}
   183  
   184  	i := valpos + nblanks
   185  	switch bline[i] {
   186  	case '/': // start of the comment
   187  		i += 1
   188  	case '\'': // quoted string value ?
   189  		str, idx, err := processString(string(bline[i:]))
   190  		if err != nil {
   191  			return nil, err
   192  		}
   193  		switch {
   194  		case len(str) <= 69: // don't exceed 70-char null-terminated string length
   195  			card.Value = str
   196  		case len(str) > 69:
   197  			card.Value = str[:70]
   198  		}
   199  		i += idx
   200  
   201  	case '(': // a complex value
   202  		idx := bytes.IndexByte(bline[i:], ')')
   203  		if idx < 0 {
   204  			return nil, fmt.Errorf("fitsio: complex keyword missing closing ')' (%q)", string(bline))
   205  		}
   206  		var x, y float64
   207  		str := strings.TrimSpace(string(bline[i : i+idx+1]))
   208  		_, err = fmt.Sscanf(str, "(%f,%f)", &x, &y)
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  		card.Value = complex(x, y)
   213  		i += idx + 1
   214  
   215  	default: // integer, float or logical FITS value string
   216  		v0 := bline[i]
   217  		value := ""
   218  
   219  		// find the end of the token
   220  		if valend := bytes.Index(bline[i:], []byte(" /")); valend < 0 {
   221  			value = string(bline[i:])
   222  		} else {
   223  			value = string(bline[i : i+valend])
   224  		}
   225  		i += len(value)
   226  
   227  		if (v0 >= '0' && v0 <= '9') || v0 == '+' || v0 == '-' {
   228  			value = strings.TrimSpace(value)
   229  			if strings.ContainsAny(value, ".DE") {
   230  				value = strings.Replace(value, "D", "E", 1) // converts D type floats to E type
   231  				x, err := strconv.ParseFloat(value, 64)
   232  				if err != nil {
   233  					return nil, err
   234  				}
   235  				card.Value = x
   236  			} else {
   237  				x, err := strconv.ParseInt(value, 10, 64)
   238  				if err != nil {
   239  					switch err := err.(type) {
   240  					case *strconv.NumError:
   241  						// try math/big.Int
   242  						if err.Err == strconv.ErrRange {
   243  							var x big.Int
   244  							_, err := fmt.Sscanf(value, "%v", &x)
   245  							if err != nil {
   246  								return nil, err
   247  							}
   248  							card.Value = x
   249  						}
   250  					default:
   251  						return nil, err
   252  					}
   253  				} else {
   254  					card.Value = int(x)
   255  				}
   256  			}
   257  		} else if v0 == 'T' {
   258  			card.Value = true
   259  		} else if v0 == 'F' {
   260  			card.Value = false
   261  		} else {
   262  			return nil, fmt.Errorf("fitsio: invalid card line (%q)", string(bline))
   263  		}
   264  	}
   265  
   266  	idx := bytes.IndexByte(bline[i:], '/')
   267  	if idx < 0 {
   268  		// no comment
   269  		return &card, err
   270  	}
   271  
   272  	com := bline[i+idx+1:]
   273  	card.Comment = strings.TrimSpace(string(com))
   274  	return &card, err
   275  }
   276  
   277  // makeHeaderLine makes a 80-byte line (or more) for a header FITS block from a Card.
   278  // transliterated from CFITSIO's ffmkky.
   279  func makeHeaderLine(card *Card) ([]byte, error) {
   280  	var err error
   281  	const kLINE = 80
   282  	var (
   283  		kCONTINUE = []byte("CONTINUE")
   284  	)
   285  
   286  	buf := new(bytes.Buffer)
   287  	buf.Grow(kLINE)
   288  
   289  	if card == nil {
   290  		return nil, fmt.Errorf("fitsio: nil Card")
   291  	}
   292  
   293  	switch card.Name {
   294  	case "", "COMMENT", "HISTORY":
   295  		str := card.Comment
   296  		vlen := len(str)
   297  		for i := 0; i < vlen; i += 72 {
   298  			end := i + 72
   299  			if end > vlen {
   300  				end = vlen
   301  			}
   302  			_, err = fmt.Fprintf(buf, "%-8s%-72s", card.Name, str[i:end])
   303  			if err != nil {
   304  				return nil, err
   305  			}
   306  		}
   307  		return buf.Bytes(), err
   308  	case "END":
   309  		_, err = fmt.Fprintf(buf, "%-80s", "END")
   310  		if err != nil {
   311  			return nil, err
   312  		}
   313  		return buf.Bytes(), err
   314  	}
   315  
   316  	klen := len(card.Name)
   317  
   318  	if klen <= 8 && verifyCardName(card) == nil {
   319  		// a normal FITS keyword
   320  		_, err = fmt.Fprintf(buf, "%-8s= ", card.Name)
   321  		if err != nil {
   322  			return nil, err
   323  		}
   324  		klen = 10
   325  	} else {
   326  		// use the ESO HIERARCH convention for longer keyword names
   327  
   328  		if strings.Contains(card.Name, "=") {
   329  			return nil, fmt.Errorf(
   330  				"fitsio: illegal keyword name. contains an equal sign [%s]",
   331  				card.Name,
   332  			)
   333  		}
   334  		key := card.Name
   335  		// dont repeat HIERARCH if the keyword already contains it
   336  		if !strings.HasPrefix(card.Name, "HIERARCH ") &&
   337  			!strings.HasPrefix(card.Name, "hierarch ") {
   338  			key = "HIERARCH " + card.Name
   339  		}
   340  		n, err := fmt.Fprintf(buf, "%s= ", key)
   341  		if err != nil {
   342  			return nil, err
   343  		}
   344  		klen = n
   345  	}
   346  
   347  	if card.Value == nil {
   348  		// this case applies to normal keywords only
   349  		if klen == 10 {
   350  			// keywords with no value have no '='
   351  			buf.Bytes()[8] = ' '
   352  			if card.Comment != "" {
   353  				comment := " / " + card.Comment
   354  				max := len(comment)
   355  				if max > kLINE-klen {
   356  					max = kLINE - klen
   357  				}
   358  				_, err = fmt.Fprintf(buf, "%s", comment[:max])
   359  				if err != nil {
   360  					return nil, err
   361  				}
   362  			}
   363  		}
   364  	} else {
   365  		buflen := buf.Len()
   366  		//valstr := ""
   367  		n := 0
   368  		switch v := card.Value.(type) {
   369  		case string:
   370  			vstr := "''"
   371  			if v != "" {
   372  				vstr = fmt.Sprintf("'%-8s'", v)
   373  			}
   374  			if len(vstr) < kLINE-buflen {
   375  				n, err = fmt.Fprintf(buf, "%-20s", vstr)
   376  				if err != nil {
   377  					return nil, fmt.Errorf("fitsio: error writing card value [%s]: %v", card.Name, err)
   378  				}
   379  			} else {
   380  				// string too long.
   381  				// use CONTINUE blocks.
   382  				// replace last character of string with '&'
   383  				ampersand := len("&")
   384  				quotes := len("''")
   385  				spacesz := len("  ")
   386  				sz := kLINE - buflen - ampersand - quotes
   387  				vstr = fmt.Sprintf("'%-8s'", v[:sz]+"&")
   388  				n, err = fmt.Fprintf(buf, "%-20s", vstr)
   389  				if err != nil {
   390  					return nil, fmt.Errorf("fitsio: error writing card value [%s]: %v", card.Name, err)
   391  				}
   392  				contlen := len(kCONTINUE)
   393  				blocksz := kLINE - contlen - ampersand - quotes - spacesz
   394  				for i := sz; i < len(v); i += blocksz {
   395  					end := i + blocksz
   396  					amper := "&"
   397  					if end > len(v) {
   398  						end = len(v)
   399  						amper = ""
   400  					}
   401  					vv := v[i:end]
   402  					vstr := fmt.Sprintf("'%-8s'", vv+amper)
   403  					n, err = fmt.Fprintf(buf, "%s  %-20s", string(kCONTINUE), vstr)
   404  					if err != nil {
   405  						return nil, fmt.Errorf("fitsio: error writing card value [%s]: %v", card.Name, err)
   406  					}
   407  				}
   408  				// fill buffer up to 80-byte mark so any remaining comment
   409  				// will have to be handled by a separate 'COMMENT' line
   410  				n = buf.Len()
   411  				align80 := (kLINE - (n % kLINE)) % kLINE
   412  				if align80 > 0 {
   413  					_, err = buf.Write(bytes.Repeat([]byte(" "), align80))
   414  					if err != nil {
   415  						return nil, err
   416  					}
   417  				}
   418  
   419  				n = 0
   420  				buflen = buf.Len() % kLINE
   421  			}
   422  
   423  		case bool:
   424  			vv := "F"
   425  			if v {
   426  				vv = "T"
   427  			}
   428  			n, err = fmt.Fprintf(buf, "%20s", vv)
   429  			if err != nil {
   430  				return nil, fmt.Errorf("fitsio: error writing card value [%s]: %v", card.Name, err)
   431  			}
   432  
   433  		case int:
   434  			n, err = fmt.Fprintf(buf, "%20d", v)
   435  			if err != nil {
   436  				return nil, fmt.Errorf("fitsio: error writing card value [%s]: %v", card.Name, err)
   437  			}
   438  
   439  		case float64:
   440  			n, err = fmt.Fprintf(buf, "%20f", v)
   441  			if err != nil {
   442  				return nil, fmt.Errorf("fitsio: error writing card value [%s]: %v", card.Name, err)
   443  			}
   444  
   445  		case complex128:
   446  			n, err = fmt.Fprintf(buf, "(%10f,%10f)", real(v), imag(v))
   447  			if err != nil {
   448  				return nil, fmt.Errorf("fitsio: error writing card value [%s]: %v", card.Name, err)
   449  			}
   450  
   451  		case big.Int:
   452  			n, err = fmt.Fprintf(buf, "%s", v.String())
   453  			if err != nil {
   454  				return nil, fmt.Errorf("fitsio: error writing card value [%s]: %v", card.Name, err)
   455  			}
   456  
   457  		default:
   458  			panic(fmt.Errorf("fitsio: invalid card value [%s]: %#v (%T)", card.Name, v, v))
   459  		}
   460  
   461  		if n+buflen > kLINE {
   462  			return nil, fmt.Errorf("fitsio: value-string too big (%d) for card [%s]: %v\nbuf=|%s|",
   463  				n, card.Name, card.Value, string(buf.Bytes()),
   464  			)
   465  		}
   466  
   467  		buflen = buf.Len() % kLINE
   468  
   469  		comment := " / " + card.Comment
   470  		max := len(comment)
   471  		// if card.Comment == "my-comment" {
   472  		// 	fmt.Printf("max=%d\n", max)
   473  		// 	fmt.Printf("buf=%d\n", buflen)
   474  		// 	fmt.Printf("buf=%d\n", buf.Len())
   475  		// 	fmt.Printf("dif=%d\n", kLINE-buflen)
   476  		// }
   477  
   478  		if max > kLINE-buflen || (buf.Len() > kLINE && (buf.Len()%kLINE) == 0) {
   479  			// append a 'COMMENT' line
   480  			if buflen > 0 {
   481  				_, err = buf.Write(bytes.Repeat([]byte(" "), kLINE-buflen))
   482  				if err != nil {
   483  					return nil, err
   484  				}
   485  			}
   486  			comline, err := makeHeaderLine(&Card{Name: "COMMENT", Comment: card.Comment})
   487  			if err != nil {
   488  				return nil, err
   489  			}
   490  			_, err = buf.Write(comline)
   491  			if err != nil {
   492  				return nil, err
   493  			}
   494  		} else {
   495  			_, err = fmt.Fprintf(buf, "%s", comment[:max])
   496  			if err != nil {
   497  				return nil, err
   498  			}
   499  		}
   500  
   501  	}
   502  
   503  	n := buf.Len()
   504  	align80 := (kLINE - (n % kLINE)) % kLINE
   505  	if align80 > 0 {
   506  		_, err = buf.Write(bytes.Repeat([]byte(" "), align80))
   507  	}
   508  	return buf.Bytes(), err
   509  }
   510  
   511  // verifyCardName verifies a Card name conforms to the FITS standard.
   512  // Must contain only capital letters, digits, minus or underscore chars.
   513  // Trailing spaces are allowed.
   514  func verifyCardName(card *Card) error {
   515  	var err error
   516  	spaces := false
   517  
   518  	max := len(card.Name)
   519  	if max > 8 {
   520  		max = 8
   521  	}
   522  
   523  	for idx, c := range card.Name {
   524  		switch {
   525  		case (c >= 'A' && c <= 'Z') ||
   526  			(c >= '0' && c <= '9') ||
   527  			c == '-' || c == '_':
   528  			if spaces {
   529  				return fmt.Errorf("fitsio: card name contains embedded space(s): %q", card.Name)
   530  			}
   531  		case c == ' ':
   532  			spaces = true
   533  		default:
   534  			return fmt.Errorf(
   535  				"fitsio: card name contains illegal character %q (idx=%d)",
   536  				card.Name, idx,
   537  			)
   538  		}
   539  	}
   540  
   541  	return err
   542  }
   543  
   544  // typeFromForm returns a FITS Type corresponding to a FITS TFORM string
   545  func typeFromForm(form string, htype HDUType) (Type, error) {
   546  	var err error
   547  	var typ Type
   548  
   549  	switch htype {
   550  	case BINARY_TBL:
   551  		j := strings.IndexAny(form, "PQABCDEIJKLMX")
   552  		if j < 0 {
   553  			return typ, fmt.Errorf("fitsio: invalid TFORM format (%s)", form)
   554  		}
   555  		repeat := 1
   556  		if j > 0 {
   557  			r, err := strconv.ParseInt(form[:j], 10, 32)
   558  			if err != nil {
   559  				return typ, fmt.Errorf("fitsio: invalid TFORM format (%s)", form)
   560  			}
   561  			repeat = int(r)
   562  		}
   563  		slice := false
   564  		dsize := 0
   565  		hsize := 0
   566  		switch form[j] {
   567  		case 'P':
   568  			j += 1
   569  			slice = true
   570  			dsize = 2 * 4
   571  		case 'Q':
   572  			j += 1
   573  			slice = true
   574  			dsize = 2 * 8
   575  		}
   576  		tc, ok := g_fits2tc[BINARY_TBL][form[j]]
   577  		if !ok {
   578  			return typ, fmt.Errorf("fitsio: invalid TFORM format (%s) (no typecode found)", form)
   579  		}
   580  		rt, ok := g_fits2go[BINARY_TBL][form[j]]
   581  		if !ok {
   582  			return typ, fmt.Errorf("fitsio: invalid TFORM format (%s) (no Type found)", form)
   583  		}
   584  
   585  		elemsz := 0
   586  		switch form[j] {
   587  		case 'A':
   588  			elemsz = repeat
   589  			repeat = 1
   590  		case 'X':
   591  			elemsz = 1
   592  			const nbits = 8
   593  			sz := repeat + (nbits-(repeat%nbits))%nbits
   594  			repeat = sz / nbits
   595  
   596  		case 'L', 'B':
   597  			elemsz = 1
   598  		case 'I':
   599  			elemsz = 2
   600  		case 'J', 'E':
   601  			elemsz = 4
   602  		case 'K', 'D', 'C':
   603  			elemsz = 8
   604  		case 'M':
   605  			elemsz = 16
   606  		}
   607  
   608  		switch slice {
   609  		case true:
   610  			hsize = elemsz
   611  			typ = Type{
   612  				tc:     -tc,
   613  				len:    repeat,
   614  				dsize:  dsize,
   615  				hsize:  hsize,
   616  				gotype: reflect.SliceOf(rt),
   617  			}
   618  
   619  		case false:
   620  			dsize = elemsz
   621  			if repeat > 1 {
   622  				typ = Type{
   623  					tc:     tc,
   624  					len:    repeat,
   625  					dsize:  dsize,
   626  					hsize:  hsize,
   627  					gotype: reflect.ArrayOf(repeat, rt),
   628  				}
   629  			} else {
   630  				typ = Type{
   631  					tc:     tc,
   632  					len:    repeat,
   633  					dsize:  dsize,
   634  					hsize:  hsize,
   635  					gotype: rt,
   636  				}
   637  			}
   638  		}
   639  
   640  		if typ.dsize*typ.len == 0 {
   641  			if form != "0A" {
   642  				return typ, fmt.Errorf("fitsio: invalid dtype! form=%q typ=%#v\n", form, typ)
   643  			}
   644  		}
   645  
   646  	case ASCII_TBL:
   647  		// fmt.Printf("### form %q\n", form)
   648  		j := strings.IndexAny(form, "ADEFI")
   649  		if j < 0 {
   650  			return typ, fmt.Errorf("fitsio: invalid TFORM format (%s)", form)
   651  		}
   652  		j = strings.Index(form, ".")
   653  		if j == -1 {
   654  			j = len(form)
   655  		}
   656  		repeat := 1
   657  		r, err := strconv.ParseInt(form[1:j], 10, 32)
   658  		if err != nil {
   659  			return typ, fmt.Errorf("fitsio: invalid TFORM format (%s)", form)
   660  		}
   661  		repeat = int(r)
   662  
   663  		tc, ok := g_fits2tc[ASCII_TBL][form[0]]
   664  		if !ok {
   665  			return typ, fmt.Errorf("fitsio: invalid TFORM format (%s) (no typecode found)", form)
   666  		}
   667  		rt, ok := g_fits2go[ASCII_TBL][form[0]]
   668  		if !ok {
   669  			return typ, fmt.Errorf("fitsio: invalid TFORM format (%s) (no Type found)", form)
   670  		}
   671  
   672  		dsize := 0
   673  		hsize := 0
   674  
   675  		switch form[0] {
   676  		case 'A':
   677  			dsize = repeat
   678  			repeat = 1
   679  		case 'I':
   680  			dsize = repeat
   681  			repeat = 1
   682  		case 'D', 'E':
   683  			dsize = repeat
   684  			repeat = 1
   685  		case 'F':
   686  			dsize = repeat
   687  			repeat = 1
   688  		}
   689  
   690  		typ = Type{
   691  			tc:     tc,
   692  			len:    repeat,
   693  			dsize:  dsize,
   694  			hsize:  hsize,
   695  			gotype: rt,
   696  		}
   697  		// fmt.Printf(">>> %#v (%v)\n", typ, typ.gotype.Name())
   698  
   699  		if typ.dsize*typ.len == 0 {
   700  			if form != "0A" {
   701  				return typ, fmt.Errorf("fitsio: invalid dtype! form=%q typ=%#v\n", form, typ)
   702  			}
   703  		}
   704  	}
   705  
   706  	return typ, err
   707  }
   708  
   709  // txtfmtFromForm returns a suitable go-fmt format from a FITS TFORM
   710  func txtfmtFromForm(form string) string {
   711  	var format string
   712  	var code rune
   713  	m := -1
   714  	w := 14
   715  
   716  	fmt.Sscanf(form, "%c%d.%d", &code, &w, &m)
   717  
   718  	switch form[0] {
   719  	case 'A':
   720  		format = fmt.Sprintf("%%%d.%ds", w, w) // Aw -> %ws
   721  	case 'I':
   722  		format = fmt.Sprintf("%%%dd", w) // Iw -> %wd
   723  	case 'B':
   724  		format = fmt.Sprintf("%%%db", w) // Bw -> %wb, binary
   725  	case 'O':
   726  		format = fmt.Sprintf("%%%do", w) // Ow -> %wo, octal
   727  	case 'Z':
   728  		format = fmt.Sprintf("%%%dX", w) // Zw -> %wX, hexadecimal
   729  	case 'F':
   730  		if m != -1 {
   731  			format = fmt.Sprintf("%%%d.%df", w, m) // Fw.d -> %w.df
   732  		} else {
   733  			format = fmt.Sprintf("%%%df", w) // Fw -> %wf
   734  		}
   735  	case 'E', 'D':
   736  		if m != -1 {
   737  			format = fmt.Sprintf("%%%d.%de", w, m) // Fw.d -> %w.df
   738  		} else {
   739  			format = fmt.Sprintf("%%%de", w) // Ew -> %we
   740  		}
   741  	case 'G':
   742  		if m != -1 {
   743  			format = fmt.Sprintf("%%%d.%dg", w, m) // Fw.d -> %w.df
   744  		} else {
   745  			format = fmt.Sprintf("%%%dg", w) // Gw -> %wg
   746  		}
   747  	}
   748  	return format
   749  }
   750  
   751  // formFromGoType returns a suitable FITS TFORM string from a reflect.Type
   752  func formFromGoType(rt reflect.Type, htype HDUType) string {
   753  	hdr := ""
   754  	var t reflect.Type
   755  	switch rt.Kind() {
   756  	case reflect.Array:
   757  		hdr = fmt.Sprintf("%d", rt.Len())
   758  		t = rt.Elem()
   759  	case reflect.Slice:
   760  		hdr = "Q"
   761  		t = rt.Elem()
   762  	default:
   763  		t = rt
   764  	}
   765  
   766  	dict, ok := g_gotype2FITS[t.Kind()]
   767  	if !ok {
   768  		return ""
   769  	}
   770  
   771  	form, ok := dict[htype]
   772  	if !ok {
   773  		return ""
   774  	}
   775  
   776  	return hdr + form
   777  }
   778  
   779  var g_gotype2FITS = map[reflect.Kind]map[HDUType]string{
   780  
   781  	reflect.Bool: {
   782  		ASCII_TBL:  "",
   783  		BINARY_TBL: "L",
   784  	},
   785  
   786  	reflect.Int: {
   787  		ASCII_TBL:  "I4",
   788  		BINARY_TBL: "K",
   789  	},
   790  
   791  	reflect.Int8: {
   792  		ASCII_TBL:  "I4",
   793  		BINARY_TBL: "B",
   794  	},
   795  
   796  	reflect.Int16: {
   797  		ASCII_TBL:  "I4",
   798  		BINARY_TBL: "I",
   799  	},
   800  
   801  	reflect.Int32: {
   802  		ASCII_TBL:  "I4",
   803  		BINARY_TBL: "J",
   804  	},
   805  
   806  	reflect.Int64: {
   807  		ASCII_TBL:  "I4",
   808  		BINARY_TBL: "K",
   809  	},
   810  
   811  	reflect.Uint: {
   812  		ASCII_TBL:  "I4",
   813  		BINARY_TBL: "V",
   814  	},
   815  
   816  	reflect.Uint8: {
   817  		ASCII_TBL:  "I4",
   818  		BINARY_TBL: "B",
   819  	},
   820  
   821  	reflect.Uint16: {
   822  		ASCII_TBL:  "I4",
   823  		BINARY_TBL: "U",
   824  	},
   825  
   826  	reflect.Uint32: {
   827  		ASCII_TBL:  "I4",
   828  		BINARY_TBL: "V",
   829  	},
   830  
   831  	reflect.Uint64: {
   832  		ASCII_TBL:  "I4",
   833  		BINARY_TBL: "V",
   834  	},
   835  
   836  	reflect.Uintptr: {
   837  		ASCII_TBL:  "",
   838  		BINARY_TBL: "",
   839  	},
   840  
   841  	reflect.Float32: {
   842  		ASCII_TBL:  "E26.17", // must write as float64 since we can only read as such
   843  		BINARY_TBL: "E",
   844  	},
   845  
   846  	reflect.Float64: {
   847  		ASCII_TBL:  "E26.17",
   848  		BINARY_TBL: "D",
   849  	},
   850  
   851  	reflect.Complex64: {
   852  		ASCII_TBL:  "",
   853  		BINARY_TBL: "C",
   854  	},
   855  
   856  	reflect.Complex128: {
   857  		ASCII_TBL:  "",
   858  		BINARY_TBL: "M",
   859  	},
   860  
   861  	reflect.Array: {
   862  		ASCII_TBL:  "",
   863  		BINARY_TBL: "",
   864  	},
   865  
   866  	reflect.Slice: {
   867  		ASCII_TBL:  "",
   868  		BINARY_TBL: "",
   869  	},
   870  
   871  	reflect.String: {
   872  		ASCII_TBL:  "A80",
   873  		BINARY_TBL: "80A",
   874  	},
   875  }
   876  
   877  var g_fits2go = map[HDUType]map[byte]reflect.Type{
   878  	ASCII_TBL: {
   879  		'A': reflect.TypeOf((*string)(nil)).Elem(),
   880  		'I': reflect.TypeOf((*int)(nil)).Elem(),
   881  		'E': reflect.TypeOf((*float64)(nil)).Elem(),
   882  		'D': reflect.TypeOf((*float64)(nil)).Elem(),
   883  		'F': reflect.TypeOf((*float64)(nil)).Elem(),
   884  	},
   885  
   886  	BINARY_TBL: {
   887  		'A': reflect.TypeOf((*string)(nil)).Elem(),
   888  		'B': reflect.TypeOf((*byte)(nil)).Elem(),
   889  		'L': reflect.TypeOf((*bool)(nil)).Elem(),
   890  		'I': reflect.TypeOf((*int16)(nil)).Elem(),
   891  		'J': reflect.TypeOf((*int32)(nil)).Elem(),
   892  		'K': reflect.TypeOf((*int64)(nil)).Elem(),
   893  		'E': reflect.TypeOf((*float32)(nil)).Elem(),
   894  		'D': reflect.TypeOf((*float64)(nil)).Elem(),
   895  		'C': reflect.TypeOf((*complex64)(nil)).Elem(),
   896  		'M': reflect.TypeOf((*complex128)(nil)).Elem(),
   897  		'X': reflect.TypeOf((*byte)(nil)).Elem(),
   898  	},
   899  }
   900  
   901  var g_fits2tc = map[HDUType]map[byte]typecode{
   902  	ASCII_TBL: {
   903  		'A': tcString,
   904  		'I': tcInt64,
   905  		'E': tcFloat64,
   906  		'D': tcFloat64,
   907  		'F': tcFloat64,
   908  	},
   909  
   910  	BINARY_TBL: {
   911  		'A': tcString,
   912  		'B': tcByte,
   913  		'L': tcBool,
   914  		'I': tcInt16,
   915  		'J': tcInt32,
   916  		'K': tcInt64,
   917  		'E': tcFloat32,
   918  		'D': tcFloat64,
   919  		'C': tcComplex64,
   920  		'M': tcComplex128,
   921  		'X': tcByte,
   922  	},
   923  }