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 }