github.com/astrogo/fitsio@v0.3.0/image.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 "encoding/binary" 9 "fmt" 10 "image" 11 "math" 12 "reflect" 13 14 "github.com/astrogo/fitsio/fltimg" 15 ) 16 17 // Image represents a FITS image 18 type Image interface { 19 HDU 20 Read(ptr interface{}) error 21 Write(ptr interface{}) error 22 Raw() []byte 23 Image() image.Image 24 25 freeze() error 26 } 27 28 // imageHDU is a Header-Data Unit extension holding an image as data payload 29 type imageHDU struct { 30 hdr Header 31 raw []byte 32 } 33 34 // NewImage creates a new Image with bitpix size for the pixels and axes as its axes 35 func NewImage(bitpix int, axes []int) *imageHDU { 36 hdr := NewHeader(nil, IMAGE_HDU, bitpix, axes) 37 return &imageHDU{ 38 hdr: *hdr, 39 raw: make([]byte, 0), 40 } 41 } 42 43 // Close closes this HDU, cleaning up cycles (if any) for garbage collection 44 func (img *imageHDU) Close() error { 45 return nil 46 } 47 48 // Header returns the Header part of this HDU block. 49 func (img *imageHDU) Header() *Header { 50 return &img.hdr 51 } 52 53 // Type returns the Type of this HDU 54 func (img *imageHDU) Type() HDUType { 55 return img.hdr.Type() 56 } 57 58 // Name returns the value of the 'EXTNAME' Card. 59 func (img *imageHDU) Name() string { 60 card := img.hdr.Get("EXTNAME") 61 if card == nil { 62 return "" 63 } 64 return card.Value.(string) 65 } 66 67 // Version returns the value of the 'EXTVER' Card (or 1 if none) 68 func (img *imageHDU) Version() int { 69 card := img.hdr.Get("EXTVER") 70 if card == nil { 71 return 1 72 } 73 return card.Value.(int) 74 } 75 76 // Raw returns the raw bytes which make the image 77 func (img *imageHDU) Raw() []byte { 78 return img.raw 79 } 80 81 // Read reads the image data into ptr 82 func (img *imageHDU) Read(ptr interface{}) error { 83 var err error 84 if img.raw == nil { 85 // FIXME(sbinet): load data from file 86 panic(fmt.Errorf("image with no raw data")) 87 } 88 89 hdr := img.Header() 90 nelmts := 1 91 for _, dim := range hdr.Axes() { 92 nelmts *= dim 93 } 94 95 if len(hdr.Axes()) <= 0 { 96 nelmts = 0 97 } 98 99 //rv := reflect.Indirect(reflect.ValueOf(ptr)) 100 rv := reflect.ValueOf(ptr).Elem() 101 rt := rv.Type() 102 103 if rt.Kind() != reflect.Slice && rt.Kind() != reflect.Array { 104 return fmt.Errorf("fitsio: invalid type (%v). expected array or slice", rt.Kind()) 105 } 106 107 pixsz := hdr.Bitpix() / 8 108 if pixsz < 0 { 109 pixsz = -pixsz 110 } 111 otype := rt.Elem() 112 if int(otype.Size()) != pixsz { 113 return fmt.Errorf( 114 "fitsio: element-size do not match. bitpix=%d. elmt-size=%d (conversion not yet supported)", 115 hdr.Bitpix(), otype.Size()) 116 } 117 118 if rt.Kind() == reflect.Slice { 119 rv.SetLen(nelmts) 120 } 121 122 r := newReader(img.raw) 123 switch hdr.Bitpix() { 124 case 8: 125 switch slice := rv.Interface().(type) { 126 case []int8: 127 for i := 0; i < nelmts; i++ { 128 r.readI8(&slice[i]) 129 } 130 return nil 131 case []byte: 132 for i := 0; i < nelmts; i++ { 133 r.readByte(&slice[i]) 134 } 135 return nil 136 } 137 138 itype := reflect.TypeOf((*byte)(nil)).Elem() 139 if !rt.Elem().ConvertibleTo(itype) { 140 return fmt.Errorf("fitsio: can not convert []byte to %s", rt.Name()) 141 } 142 cnv := func(v reflect.Value) reflect.Value { 143 return v.Convert(otype) 144 } 145 146 for i := 0; i < nelmts; i++ { 147 var v byte 148 r.readByte(&v) 149 rv.Index(i).Set(cnv(reflect.ValueOf(v))) 150 } 151 152 case 16: 153 if otype.Kind() == reflect.Int16 { 154 slice := rv.Interface().([]int16) 155 for i := 0; i < nelmts; i++ { 156 r.readI16(&slice[i]) 157 } 158 return nil 159 } 160 161 itype := reflect.TypeOf((*int16)(nil)).Elem() 162 if !rt.Elem().ConvertibleTo(itype) { 163 return fmt.Errorf("fitsio: can not convert []int16 to %s", rt.Name()) 164 } 165 cnv := func(v reflect.Value) reflect.Value { 166 return v.Convert(otype) 167 } 168 for i := 0; i < nelmts; i++ { 169 var v int16 170 r.readI16(&v) 171 rv.Index(i).Set(cnv(reflect.ValueOf(v))) 172 } 173 174 case 32: 175 if otype.Kind() == reflect.Int32 { 176 slice := rv.Interface().([]int32) 177 for i := 0; i < nelmts; i++ { 178 r.readI32(&slice[i]) 179 } 180 return nil 181 } 182 183 itype := reflect.TypeOf((*int32)(nil)).Elem() 184 if !rt.Elem().ConvertibleTo(itype) { 185 return fmt.Errorf("fitsio: can not convert []int32 to %s", rt.Name()) 186 } 187 cnv := func(v reflect.Value) reflect.Value { 188 return v.Convert(otype) 189 } 190 for i := 0; i < nelmts; i++ { 191 var v int32 192 r.readI32(&v) 193 rv.Index(i).Set(cnv(reflect.ValueOf(v))) 194 } 195 196 case 64: 197 if otype.Kind() == reflect.Int64 { 198 slice := rv.Interface().([]int64) 199 for i := 0; i < nelmts; i++ { 200 r.readI64(&slice[i]) 201 } 202 return nil 203 } 204 205 itype := reflect.TypeOf((*int64)(nil)).Elem() 206 if !rt.Elem().ConvertibleTo(itype) { 207 return fmt.Errorf("fitsio: can not convert []int64 to %s", rt.Name()) 208 } 209 cnv := func(v reflect.Value) reflect.Value { 210 return v.Convert(otype) 211 } 212 for i := 0; i < nelmts; i++ { 213 var v int64 214 r.readI64(&v) 215 rv.Index(i).Set(cnv(reflect.ValueOf(v))) 216 } 217 218 case -32: 219 if otype.Kind() == reflect.Float32 { 220 slice := rv.Interface().([]float32) 221 for i := 0; i < nelmts; i++ { 222 r.readF32(&slice[i]) 223 } 224 return nil 225 } 226 227 itype := reflect.TypeOf((*float32)(nil)).Elem() 228 if !rt.Elem().ConvertibleTo(itype) { 229 return fmt.Errorf("fitsio: can not convert []float32 to %s", rt.Name()) 230 } 231 cnv := func(v reflect.Value) reflect.Value { 232 return v.Convert(otype) 233 } 234 for i := 0; i < nelmts; i++ { 235 var v float32 236 r.readF32(&v) 237 rv.Index(i).Set(cnv(reflect.ValueOf(v))) 238 } 239 240 case -64: 241 if otype.Kind() == reflect.Float64 { 242 slice := rv.Interface().([]float64) 243 for i := 0; i < nelmts; i++ { 244 r.readF64(&slice[i]) 245 } 246 return nil 247 } 248 249 itype := reflect.TypeOf((*float64)(nil)).Elem() 250 if !rt.Elem().ConvertibleTo(itype) { 251 return fmt.Errorf("fitsio: can not convert []float64 to %s", rt.Name()) 252 } 253 cnv := func(v reflect.Value) reflect.Value { 254 return v.Convert(otype) 255 } 256 for i := 0; i < nelmts; i++ { 257 var v float64 258 r.readF64(&v) 259 rv.Index(i).Set(cnv(reflect.ValueOf(v))) 260 } 261 262 default: 263 return fmt.Errorf("invalid image type [bitpix=%d]", hdr.Bitpix()) 264 } 265 266 return err 267 } 268 269 // Write writes the given image data to the HDU 270 func (img *imageHDU) Write(data interface{}) error { 271 var err error 272 rv := reflect.Indirect(reflect.ValueOf(data)) 273 274 hdr := img.Header() 275 naxes := len(hdr.Axes()) 276 if naxes == 0 { 277 return nil 278 } 279 nelmts := 1 280 for _, dim := range hdr.Axes() { 281 nelmts *= dim 282 } 283 284 pixsz := hdr.Bitpix() / 8 285 if pixsz < 0 { 286 pixsz = -pixsz 287 } 288 289 img.raw = make([]byte, pixsz*nelmts) 290 w := newWriter(img.raw) 291 switch data := rv.Interface().(type) { 292 case []byte: 293 if hdr.Bitpix() != 8 { 294 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 295 } 296 copy(img.raw, data) 297 298 case []int8: 299 if hdr.Bitpix() != 8 { 300 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 301 } 302 for _, v := range data { 303 w.writeI8(v) 304 } 305 306 case []int16: 307 if hdr.Bitpix() != 16 { 308 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 309 } 310 for _, v := range data { 311 w.writeI16(v) 312 } 313 case []uint16: 314 if hdr.Bitpix() != 16 { 315 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 316 } 317 for _, v := range data { 318 w.writeU16(v) 319 } 320 321 case []int32: 322 if hdr.Bitpix() != 32 { 323 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 324 } 325 for _, v := range data { 326 w.writeI32(v) 327 } 328 case []uint32: 329 if hdr.Bitpix() != 32 { 330 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 331 } 332 for _, v := range data { 333 w.writeU32(v) 334 } 335 336 case []int64: 337 if hdr.Bitpix() != 64 { 338 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 339 } 340 for _, v := range data { 341 w.writeI64(v) 342 } 343 case []uint64: 344 if hdr.Bitpix() != 64 { 345 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 346 } 347 for _, v := range data { 348 w.writeU64(v) 349 } 350 351 case []float32: 352 if hdr.Bitpix() != -32 { 353 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 354 } 355 for _, v := range data { 356 w.writeF32(v) 357 } 358 359 case []float64: 360 if hdr.Bitpix() != -64 { 361 return fmt.Errorf("fitsio: got a %T but bitpix!=%d", data, hdr.Bitpix()) 362 } 363 for _, v := range data { 364 w.writeF64(v) 365 } 366 367 default: 368 return fmt.Errorf("fitsio: invalid image type (%T)", rv.Interface()) 369 } 370 371 //img.raw = buf.Bytes() 372 return err 373 } 374 375 // Image returns an image.Image value. 376 func (img *imageHDU) Image() image.Image { 377 378 // Getting the HDU bitpix and axes. 379 header := img.Header() 380 bitpix := header.Bitpix() 381 axes := header.Axes() 382 raw := make([]byte, len(img.Raw())) 383 copy(raw, img.Raw()) 384 385 if len(axes) < 2 { 386 return nil 387 } 388 389 // Image width and height. 390 w := axes[0] 391 h := axes[1] 392 393 switch { 394 case w <= 0: 395 return nil 396 case h <= 0: 397 return nil 398 } 399 400 // handle BSCALE/BZERO rescaling header keywords. 401 // see: https://heasarc.gsfc.nasa.gov/docs/fcg/standard_dict.html 402 if bscale, bzero := header.Get("BSCALE"), header.Get("BZERO"); bscale != nil || bzero != nil { 403 scale := 1.0 404 zero := 0.0 405 if bzero != nil { 406 switch v := bzero.Value.(type) { 407 case float64: 408 zero = v 409 case int: 410 zero = float64(v) 411 default: 412 panic("fitsio: handle non-float types for BSCALE and BZERO") 413 } 414 } 415 if bscale != nil { 416 switch v := bscale.Value.(type) { 417 case float64: 418 scale = v 419 case int: 420 scale = float64(v) 421 default: 422 panic("fitsio: handle non-float types for BSCALE and BZERO") 423 } 424 } 425 switch bitpix { 426 case 8: 427 zero := uint8(zero) 428 scale := uint8(scale) 429 for i, v := range raw { 430 raw[i] = zero + scale*v 431 } 432 433 case 16: 434 zero := uint16(zero) 435 scale := uint16(scale) 436 for i := 0; i < len(raw); i += 2 { 437 v := zero + scale*binary.BigEndian.Uint16(raw[i:i+2]) 438 binary.BigEndian.PutUint16(raw[i:i+2], v) 439 } 440 441 case 32: 442 zero := uint32(zero) 443 scale := uint32(scale) 444 for i := 0; i < len(raw); i += 4 { 445 v := zero + scale*binary.BigEndian.Uint32(raw[i:i+4]) 446 binary.BigEndian.PutUint32(raw[i:i+4], v) 447 } 448 449 case 64: 450 zero := uint64(zero) 451 scale := uint64(scale) 452 for i := 0; i < len(raw); i += 8 { 453 v := zero + scale*binary.BigEndian.Uint64(raw[i:i+8]) 454 binary.BigEndian.PutUint64(raw[i:i+8], v) 455 } 456 457 case -32: 458 zero := float32(zero) 459 scale := float32(scale) 460 for i := 0; i < len(raw); i += 4 { 461 v := zero + scale*math.Float32frombits(binary.BigEndian.Uint32(raw[i:i+4])) 462 binary.BigEndian.PutUint32(raw[i:i+4], math.Float32bits(v)) 463 } 464 465 case -64: 466 for i := 0; i < len(raw); i += 8 { 467 v := zero + scale*math.Float64frombits(binary.BigEndian.Uint64(raw[i:i+8])) 468 binary.BigEndian.PutUint64(raw[i:i+8], math.Float64bits(v)) 469 } 470 } 471 } 472 473 rect := image.Rect(0, 0, w, h) 474 475 switch bitpix { 476 case 8: 477 img := &image.Gray{ 478 Pix: raw, 479 Stride: 1 * w, 480 Rect: rect, 481 } 482 return img 483 484 case 16: 485 img := &image.Gray16{ 486 Pix: raw, 487 Stride: 2 * w, 488 Rect: rect, 489 } 490 return img 491 492 case 32: 493 img := &image.RGBA{ 494 Pix: raw, 495 Stride: 4 * w, 496 Rect: rect, 497 } 498 return img 499 500 case 64: 501 img := &image.RGBA64{ 502 Pix: raw, 503 Stride: 8 * w, 504 Rect: rect, 505 } 506 return img 507 508 case -32: 509 img := fltimg.NewGray32(rect, raw) 510 return img 511 512 case -64: 513 img := fltimg.NewGray64(rect, raw) 514 return img 515 516 default: 517 panic(fmt.Errorf("fitsio: image with unknown BITPIX value of %d", bitpix)) 518 } 519 520 } 521 522 // freeze freezes an Image before writing, finalizing header values. 523 func (img *imageHDU) freeze() error { 524 var err error 525 card := img.Header().Get("XTENSION") 526 if card != nil { 527 return err 528 } 529 530 err = img.Header().prepend(Card{ 531 Name: "XTENSION", 532 Value: "IMAGE ", 533 Comment: "IMAGE extension", 534 }) 535 if err != nil { 536 return err 537 } 538 539 return err 540 }