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 }