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  }