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