1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
|
# image.py: Python image functions
# Copyright (C) 2008
# Associated Universities, Inc. Washington DC, USA.
#
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
#
# Correspondence concerning AIPS++ should be addressed as follows:
# Internet email: aips2-request@nrao.edu.
# Postal address: AIPS++ Project Office
# National Radio Astronomy Observatory
# 520 Edgemont Road
# Charlottesville, VA 22903-2475 USA
from ._images import Image
import numpy
import numpy.ma as nma
from casacore.images.coordinates import coordinatesystem
class image(Image):
"""The Python interface to casacore images.
An image can be constructed in a variety of ways:
- Opening an existing image. The image format is determined automatically
and can be
`casacore <http://casacore.googlecode.com>`_,
`HDF5 <http://www.hdfgroup.org/HDF5>`_,
`FITS <http://heasarc.gsfc.nasa.gov/docs/software/fitsio/fitsio.html>`_,
or `MIRIAD <http://www.atnf.csiro.au/computing/software/miriad>`_.
FITS and MIRIAD always have data type float, but casacore and HDF5 images
can have data type float, double, complex, or dcomplex.
- Open an image expression by giving a `LEL expression
<../../casacore/doc/notes/223.html>`_ string.
Note that in an expression `$im` can be used similar to TaQL commands
(see function :func:`tables.taql`).
- Create a new temporary image from a shape or a numpy array.
- Virtually concatenate a number of images along a given axis. This can
be used to form a spectral line image cube from separate channel images.
The following arguments can be given:
`imagename`
| It can be given in several forms:
| If it is a tuple or list, the image is opened as the virtual
concatenation of the given image names or objects.
| Otherwise it should be a string giving the image name (or expression).
If argument `values` or `shape` is given, a new image with that name
is created using the values or shape. If the name is empty, a temporary
image is created which can be written later using :func:`saveas`.
| Otherwise it is tried to open an existing image with that name.
| If the open fails, it is opened as a LEL image expression.
`axis`
The axis number along which images have to be concatenated.
`maskname`
| The name of the mask to be used when opening an existing image. If not
given, the default image mask is used.
| If an image is created, a mask with this name is created.
`images`
Possible images objects to be used for $n arguments in an expression
string.
'values`
If given, the image will be created and these values will be stored in it.
It should be a numpy array or masked array. If it is a masked array,
its mask acts as the `mask` argument described below.
The data type of the image is derived from the array's data type.
`coordsys`
The coordinate system to be used when creating an image.
If not given, an appropriate default coordnate system will be used.
`overwrite`
If False, an exception is raised if the new image file already exists.
Default is True.
`ashdf5`
If True, the image is created in HDF5 format, otherwise in casacore
format. Default is casacore format.
`mask`
| An optional mask to be stored in the image when creating the image.
If a mask is given, but no maskname is given, the mask will get the
name `mask0`.
| The mask can also be given in argument `values` (see above).
| Note that the casacore images use the convention that a mask value
True means good and False means bad. However, numpy uses the opposite.
Therefore the mask will be negated, so a numpy masked can be given
directly.
`shape`
If given, the image will be created. If `values` is also given, its
shape should match. If `values` is not given, an image with data type
double will be created.
`tileshape`
Advanced users can give the tile shape to be used. See the
:mod:`tables` module for more information about Tiled Storage Managers.
For example::
im = image('3c343.fits') # open existing fits image
im = image('a.img1 - a.img2') # open as expression
im = image(shape=(256,256)) # create temp image
im = image('a', shape=(256,256)) # create image a
"""
def __init__(self, imagename, axis=0, maskname="", images=(), values=None,
coordsys=None, overwrite=True, ashdf5=False, mask=(),
shape=None, tileshape=()):
coord = {}
if coordsys is not None:
coord = coordsys.dict()
if isinstance(imagename, Image):
# Create from the value returned by subimage, etc.
Image.__init__(self, imagename)
else:
opened = False
if isinstance(imagename, tuple) or isinstance(imagename, list):
if len(imagename) == 0:
raise ValueError('No images given in list or tuple')
if isinstance(imagename[0], str):
# Concatenate from image names
Image.__init__(self, imagename, axis)
opened = True
elif isinstance(imagename[0], image):
# Concatenate from image objects
Image.__init__(self, imagename, axis, 0, 0)
opened = True
if not opened:
if not isinstance(imagename, str):
raise ValueError("first argument must be name or" +
" sequence of images or names")
if shape is None:
if values is None:
# Open an image from name or expression
# Copy the tables argument and make sure it is a list
imgs = []
for img in images:
imgs += [img]
try:
# Substitute possible $ arguments
import casacore.util as cu
imagename = cu.substitute(imagename,
[(image, '', imgs)],
locals=cu.getlocals(3))
except:
print("Probably could not import casacore.util")
pass
Image.__init__(self, imagename, maskname, imgs)
else:
# Create an image from an array
# The values can be a masked array
# use the mask if no explicit mask is given
if isinstance(values, nma.MaskedArray):
if len(mask) == 0:
mask = nma.getmaskarray(values)
values = values.data
if len(mask) > 0:
mask = ~mask # casa and numpy have opposite flags
Image.__init__(self, values, mask, coord,
imagename, overwrite, ashdf5,
maskname, tileshape)
else:
# Create an image from a shape (values gives the data type)
# default type is float.
if values is None:
values = numpy.array([0], dtype='float32')[0]
Image.__init__(self, shape, values, coord,
imagename, overwrite, ashdf5,
maskname, tileshape, 0)
def __str__(self):
"""Get image name."""
return self.name(strippath=True)
def __len__(self):
"""Get nr of pixels in the image."""
return self._size()
def ispersistent(self):
"""Test if the image is persistent, i.e. stored on disk."""
return self._ispersistent()
def name(self, strippath=False):
"""Get image name."""
return self._name(strippath)
def shape(self):
"""Get image shape."""
return self._shape()
def ndim(self):
"""Get dimensionality of the image."""
return self._ndim()
def size(self):
"""Get nr of pixels in the image."""
return self._size()
def datatype(self):
"""Get data type of the image."""
return self._datatype()
def imagetype(self):
"""Get image type of the image (PagedImage, HDF5Image, etc.)."""
return self._imagetype()
def attrgroupnames(self):
"""Get the names of all attribute groups."""
return self._attrgroupnames()
def attrcreategroup(self, groupname):
"""Create a new attribute group."""
self._attrcreategroup(groupname)
def attrnames(self, groupname):
"""Get the names of all attributes in this group."""
return self._attrnames(groupname)
def attrnrows(self, groupname):
"""Get the number of rows in this attribute group."""
return self._attrnrows(groupname)
def attrget(self, groupname, attrname, rownr):
"""Get the value of an attribute in the given row in a group."""
return self._attrget(groupname, attrname, rownr)
def attrgetcol(self, groupname, attrname):
"""Get the value of an attribute for all rows in a group."""
values = []
for rownr in range(self.attrnrows(groupname)):
values.append(self.attrget(groupname, attrname, rownr))
return values
def attrfindrows(self, groupname, attrname, value):
"""Get the row numbers of all rows where the attribute matches the given value."""
values = self.attrgetcol(groupname, attrname)
return [i for i in range(len(values)) if values[i] == value]
def attrgetrow(self, groupname, key, value=None):
"""Get the values of all attributes of a row in a group.
If the key is an integer, the key is the row number for which
the attribute values have to be returned.
Otherwise the key has to be a string and it defines the name of an
attribute. The attribute values of the row for which the key matches
the given value is returned.
It can only be used for unique attribute keys. An IndexError exception
is raised if no or multiple matches are found.
"""
if not isinstance(key, str):
return self._attrgetrow(groupname, key)
# The key is an attribute name whose value has to be found.
rownrs = self.attrfindrows(groupname, key, value)
if len(rownrs) == 0:
raise IndexError("Image attribute " + key + " in group " +
groupname + " has no matches for value " +
str(value))
if len(rownrs) > 1:
raise IndexError("Image attribute " + key + " in group " +
groupname + " has multiple matches for value " +
str(value))
return self._attrgetrow(groupname, rownrs[0])
def attrgetunit(self, groupname, attrname):
"""Get the unit(s) of an attribute in a group."""
return self._attrgetunit(groupname, attrname)
def attrgetmeas(self, groupname, attrname):
"""Get the measinfo (type, frame) of an attribute in a group."""
return self._attrgetmeas(groupname, attrname)
def attrput(self, groupname, attrname, rownr, value, unit=[], meas=[]):
"""Put the value and optionally unit and measinfo
of an attribute in a row in a group."""
return self._attrput(groupname, attrname, rownr, value, unit, meas)
def getdata(self, blc=(), trc=(), inc=()):
"""Get image data.
Using the arguments blc (bottom left corner), trc (top right corner),
and inc (stride) it is possible to get a data slice.
The data is returned as a numpy array. Its dimensionality is the same
as the dimensionality of the image, even if an axis has length 1.
"""
return self._getdata(self._adjustBlc(blc),
self._adjustTrc(trc),
self._adjustInc(inc))
# Negate the mask; in numpy True means invalid.
def getmask(self, blc=(), trc=(), inc=()):
"""Get image mask.
Using the arguments blc (bottom left corner), trc (top right corner),
and inc (stride) it is possible to get a mask slice. Not all axes
need to be specified. Missing values default to begin, end, and 1.
The mask is returned as a numpy array. Its dimensionality is the same
as the dimensionality of the image, even if an axis has length 1.
Note that the casacore images use the convention that a mask value
True means good and False means bad. However, numpy uses the opposite.
Therefore the mask will be negated, so it can be used directly in
numpy operations.
If the image has no mask, an array will be returned with all values
set to False.
"""
return numpy.logical_not(self._getmask(self._adjustBlc(blc),
self._adjustTrc(trc),
self._adjustInc(inc)))
# Get data and mask
def get(self, blc=(), trc=(), inc=()):
"""Get image data and mask.
Get the image data and mask (see ::func:`getdata` and :func:`getmask`)
as a numpy masked array.
"""
return nma.masked_array(self.getdata(blc, trc, inc),
self.getmask(blc, trc, inc))
def putdata(self, value, blc=(), trc=(), inc=()):
"""Put image data.
Using the arguments blc (bottom left corner), trc (top right corner),
and inc (stride) it is possible to put a data slice. Not all axes
need to be specified. Missing values default to begin, end, and 1.
The data should be a numpy array. Its dimensionality must be the same
as the dimensionality of the image.
"""
return self._putdata(value, self._adjustBlc(blc),
self._adjustInc(inc))
def putmask(self, value, blc=(), trc=(), inc=()):
"""Put image mask.
Using the arguments blc (bottom left corner), trc (top right corner),
and inc (stride) it is possible to put a data slice. Not all axes
need to be specified. Missing values default to begin, end, and 1.
The data should be a numpy array. Its dimensionality must be the same
as the dimensionality of the image.
Note that the casacore images use the convention that a mask value
True means good and False means bad. However, numpy uses the opposite.
Therefore the mask will be negated, so a numpy masked can be given
directly.
The mask is not written if the image has no mask and if it the entire
mask is False. In that case the mask most likely comes from a getmask
operation on an image without a mask.
"""
# casa and numpy have opposite flags
return self._putmask(~value, self._adjustBlc(blc),
self._adjustInc(inc))
def put(self, value, blc=(), trc=(), inc=()):
"""Put image data and mask.
Put the image data and optionally the mask (see ::func:`getdata`
and :func:`getmask`).
If the `value` argument is a numpy masked array, but data and mask will
bw written. If it is a normal numpy array, only the data will be
written.
"""
if isinstance(value, nma.MaskedArray):
self.putdata(value.data, blc, trc, inc)
self.putmask(nma.getmaskarray(value), blc, trc, inc)
else:
self.putdata(value, blc, trc, inc)
def haslock(self, write=False):
"""Test if the image holds a read or write lock.
| See `func:`tables.table.haslock` for more information.
| Locks are only used for images in casacore format. For other formats
(un)locking is a no-op, so this method always returns True.
"""
return self._haslock(write)
def lock(self, write=False, nattempts=0):
"""Acquire a read or write lock on the image.
| See `func:`tables.table.haslock` for more information.
| Locks are only used for images in casacore format. For other formats
(un)locking is a no-op, so this method always returns True.
Only advanced users should use locking. In normal operations
explicit locking and unlocking is not necessary.
"""
return self._lock(write, nattempts)
def unlock(self):
"""Release a lock on the image.
| See `func:`tables.table.haslock` for more information.
| Locks are only used for images in casacore format. For other formats
(un)locking is a no-op, so this method always returns True.
"""
return self._unlock()
def subimage(self, blc=(), trc=(), inc=(), dropdegenerate=True):
"""Form a subimage.
An image object containing a subset of an image is returned.
The arguments blc (bottom left corner), trc (top right corner),
and inc (stride) define the subset. Not all axes need to be specified.
Missing values default to begin, end, and 1.
By default axes with length 1 are left out.
A subimage is a so-called virtual image. It is not stored, but only
references the original image. It can be made persistent using the
:func:`saveas` method.
"""
return image(self._subimage(self._adjustBlc(blc),
self._adjustTrc(trc),
self._adjustInc(inc),
dropdegenerate))
def coordinates(self):
"""Get the :class:`coordinatesystem` of the image."""
return coordinatesystem(self._coordinates())
def toworld(self, pixel):
"""Convert the pixel coordinates of an image value to world coordinates.
The coordinates must be given with the slowest varying axis first.
Thus normally like frequency(,polarisation axis),Dec,Ra.
"""
return self._toworld(pixel, True)
def topixel(self, world):
"""Convert the world coordinates of an image value to pixel coordinates.
The coordinates must be given with the slowest varying axis first.
Thus normally like frequency(,polarisation axis),Dec,Ra.
"""
return self._topixel(world, True)
def imageinfo(self):
"""Get the standard image info."""
return self._imageinfo()
def miscinfo(self):
"""Get the auxiliary image info."""
return self._miscinfo()
def unit(self):
"""Get the pixel unit of the image."""
return self._unit()
def history(self):
"""Get the image processing history."""
return self._history()
def info(self):
"""Get coordinates, image info, and unit"."""
return {'coordinates': self._coordinates(),
'imageinfo': self._imageinfo(),
'miscinfo': self._miscinfo(),
'unit': self._unit()
}
def tofits(self, filename, overwrite=True, velocity=True,
optical=True, bitpix=-32, minpix=1, maxpix=-1):
"""Write the image to a file in FITS format.
`filename`
FITS file name
`overwrite`
If False, an exception is raised if the new image file already exists.
Default is True.
`velocity`
By default a velocity primary spectral axis is written if possible.
`optical`
If writing a velocity, use the optical definition
(otherwise use radio).
`bitpix`
can be set to -32 (float) or 16 (short) only. When `bitpix` is
16 it will write BSCALE and BZERO into the FITS file. If minPix
`minpix` and `maxpix`
are used to determine BSCALE and BZERO if `bitpix=16`.
If `minpix` is greater than `maxpix` (which is the default),
the minimum and maximum pixel values will be determined from the ddta.
Oherwise the supplied values will be used and pixels outside that
range will be clipped to the minimum and maximum pixel values.
Note that this truncation does not occur for `bitpix=-32`.
"""
return self._tofits(filename, overwrite, velocity, optical,
bitpix, minpix, maxpix)
def saveas(self, filename, overwrite=True, hdf5=False,
copymask=True, newmaskname="", newtileshape=()):
"""Write the image to disk.
Note that the created disk file is a snapshot, so it is not updated
for possible later changes in the image object.
`overwrite`
If False, an exception is raised if the new image file already exists.
Default is True.
`ashdf5`
If True, the image is created in HDF5 format, otherwise in casacore
format. Default is casacore format.
`copymask`
By default the mask is written as well if the image has a mask.
'newmaskname`
If the mask is written, the name is the same the original or
`mask0` if the original mask has no name. Using this argument a
different mask name can be given.
`tileshape`
Advanced users can give a new tile shape. See the :mod:`tables`
module for more information about Tiled Storage Managers.
"""
self._saveas(filename, overwrite, hdf5,
copymask, newmaskname,
newtileshape)
def statistics(self, axes=(), minmaxvalues=(), exclude=False, robust=True):
"""Calculate statistics for the image.
Statistics are returned in a dict for the given axes.
E.g. if axes [0,1] is given in a 3-dim image, the statistics are
calculated for each plane along the 3rd axis. By default statistics
are calculated for the entire image.
`minmaxvalues` can be given to include or exclude pixels
with values in the given range. If only one value is given,
min=-abs(val) and max=abs(val).
By default robust statistics (Median, MedAbsDevMed, and Quartile) are
calculated too.
"""
return self._statistics(self._adaptAxes(axes), "",
minmaxvalues, exclude, robust)
def regrid(self, axes, coordsys, outname="", overwrite=True,
outshape=(), interpolation="linear",
decimate=10, replicate=False,
refchange=True, forceregrid=False):
"""Regrid the image to a new image object.
Regrid the image on the given axes to the given coordinate system.
The output is stored in the given file; it no file name is given a
temporary image is made.
If the output shape is empty, the old shape is used.
`replicate=True` means replication rather than regridding.
"""
return image(self._regrid(self._adaptAxes(axes),
outname, overwrite,
outshape, coordsys.dict(),
interpolation, decimate, replicate,
refchange, forceregrid))
def view(self, tempname='/tmp/tempimage'):
"""Display the image using casaviewer.
If the image is not persistent, a copy will be made that the user
has to delete once viewing has finished. The name of the copy can be
given in argument `tempname`. Default is '/tmp/tempimage'.
"""
import os
# Test if casaviewer can be found.
# On OS-X 'which' always returns 0, so use test on top of it.
if os.system('test -x `which casaviewer` > /dev/null 2>&1') == 0:
print("Starting casaviewer in the background ...")
self.unlock()
if self.ispersistent():
os.system('casaviewer ' + self.name() + ' &')
elif len(tempname) > 0:
print(" making a persistent copy in " + tempname)
print(" which should be deleted after the viewer has ended")
self.saveas(tempname)
os.system('casaviewer ' + tempname + ' &')
else:
print("Cannot view because the image is in memory only.")
print("You can browse a persistent copy of the image like:")
print(" t.view('/tmp/tempimage')")
else:
print("casaviewer cannot be found")
def _adaptAxes(self, axes):
# If axes is a single integer value, turn it into a list.
if isinstance(axes, int):
axes = [axes]
# ImageProxy expects Fortran-numbered axes.
# So reverse the axes.
n = self.ndim() - 1
axout = []
for i in range(len(axes), 0, -1):
axout += [n - axes[i - 1]]
return axout
def _adjust(self, val, defval):
retval = defval
if isinstance(val, tuple) or isinstance(val, list):
retval[0:len(val)] = val
else:
retval[0] = val
return retval
# Append blc with 0 if shorter than shape.
def _adjustBlc(self, blc):
shp = self._shape()
return self._adjust(blc, [0 for x in shp])
# Append trc with shape-1 if shorter than shape.
def _adjustTrc(self, trc):
shp = self._shape()
return self._adjust(trc, [x - 1 for x in shp])
# Append inc with 1 if shorter than shape.
def _adjustInc(self, inc):
shp = self._shape()
return self._adjust(inc, [1 for x in shp])
|