PANOPTES Utilities¶
PANOPTES Utilities¶
Utility functions for use within the Project PANOPTES ecosystem and for general astronomical processing.
This library defines a number of modules that contain useful functions as well as a few services.
Install¶
To install type:
pip install panoptes-utils
Full options for install:
pip install "panoptes-utils[config,docs,images,testing,social]"
See the full documentation at: https://panoptes-utils.readthedocs.io
Dependencies¶
There are a few system dependencies depending on what functionality you will be using.
In particular, the plate solving requires astrometry.net
and the appropriate index files.
Use the following on a debian-based system (e.g. Ubuntu) to easily install all dependencies:
apt-get update && apt-get install --no-install-recommends --yes \
libffi-dev libssl-dev \
astrometry.net astrometry-data-tycho2 \
dcraw exiftool libcfitsio-dev libcfitsio-bin \
libfreetype6-dev libpng-dev libjpeg-dev libffi-dev
Command Line¶
The panoptes-utils
command line tool is available for use with subcommands
corresponding to the modules in this library. Currently, the only implemented
subcommand is image
, which includes commands for converting cr2
files into
jpg
and/or fits
files as well as for plate-solving fits
images.
The panoptes-utils image watch <path>
command will watch the given path for
new files and convert them to jpg
and/or fits
files as they are added.
See panoptes-utils --help
and panoptes-utils image --help
for details.
Config Server¶
There is a simple key-value configuration server available as part of the module.
After installing with the config
option as above, type:
panoptes-config-server run --config-file <path-to-file.yaml>
Developing¶
panoptes-utils
uses pyscaffold
for project setup,
which then uses the standard tox
and pyproject.toml
tools to manage the project. Tests can
be run with tox
, e.g.
# Clean repository.
tox -e clean
# Run tests.
tox
# Build project.
tox -e build
Contents¶
Command Line Utils¶
panoptes-utils
provides a command line interface for some of the common functions
in the module.
Commands¶
The main command is called panoptes-utils
and includes subcommands for specific
tasks. The subcommands are available via the help menu:
$ panoptes-utils --help
Usage: panoptes-utils [options] <command> [<args>]
Options:
--help Show this message and exit.
Commands:
image Process an image.
image¶
The image
subcommand provides access to image conversion and plate-solving as
well as a generic tool for watching a directory and performing any of the other
image subcommands.
$ panoptes-utils image --help
Usage: panoptes-utils image [OPTIONS] COMMAND [ARGS]...
Process an image.
Options:
--help Show this message and exit.
Commands:
cr2
fits
watch Watch a directory for changes and process any new files.
image watch¶
A tool for watching a directory and performing subcommands on all incoming files.
This command will block until cancelled by the user via Ctrl-c
.
Usage: panoptes-utils image watch [OPTIONS] PATH
Watch a directory for changes and process any new files.
The files will be processed according to the boolean flags, with the flag
names corresponding to other image commands.
By default, all the flags are enabled, which will:
* Extract JPG files from a CR2.
* Convert CR2 files to FITS.
* Plate-solve FITS files.
Arguments:
PATH [required]
Options:
--to-jpg / --no-to-jpg [default: to-jpg]
--to-fits / --no-to-fits [default: to-fits]
--solve / --no-solve [default: solve]
--overwrite / --no-overwrite [default: no-overwrite]
--remove-cr2 / --no-remove-cr2 [default: no-remove-cr2]
--help Show this message and exit.
image cr2¶
Canon CR2
can have a JPG extracted and be converted to FITS files. See the --help
command for each of the specific subcommands for more details.
Usage: panoptes-utils image cr2 [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
to-fits Convert a CR2 image to a FITS, return the new path name.
to-jpg Extract a JPG image from a CR2, return the new path name.
image fits¶
FITS files can be easily plate-solved.
Usage: panoptes-utils image fits [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
solve Plate-solve a FITS file.
Config Server¶
The config server is a simple web service that runs either on a local machine or a remote server.
The configuration is a key/value system where the keys and values must be serializable as valid yaml (or json). Configuration can be initially defined in an external yaml file and any values saved to the active server will by default be saved back to a copy of the yaml file.
The module will install the panoptes-config-server
for command line usage, which defines
a number of subcommands for interacting with (and starting) the server.
$ panoptes-config-server --help ─╯
Usage: panoptes-config-server [OPTIONS] COMMAND [ARGS]...
Options:
--verbose / --no-verbose Turn on panoptes logger for utils, default False
--help Show this message and exit.
Commands:
get Get an item from the config server.
run Runs the config server with command line options.
set Set an item in the config server.
Each subcommand has its own --help
command. See below for specific usage.
Starting the config server¶
Command line¶
To start the service from the command-line, use panoptes-config-server run
:
$ panoptes-config-server run --help ─╯
Usage: panoptes-config-server run [OPTIONS] CONFIG_FILE
Runs the config server with command line options.
This function is installed as an entry_point for the module, accessible at
`panoptes-config-server`.
Options:
--host TEXT The config server IP address or host name,
default 0.0.0.0
--port TEXT The config server port, default 6563
--save / --no-save If the set values should be saved
permanently, default True
--ignore-local / --no-ignore-local
Ignore the local config files, default
False. Mostly for testing.
--debug / --no-debug
--help Show this message and exit.
Python¶
From python, for instance when running in a jupyter notebook, you can use:
>>> from panoptes.utils.config.server import config_server
>>> server_process = config_server()
...
>>> server_process.terminate() # Or just exit notebook/console
Options¶
ignore_local¶
By default, local versions of the config files are parsed and replace
any default values. For instance, the default config file is
$PANDIR/conf_files/pocs.yaml
but the config server will also look
for and parse $PANDIR/conf_files/pocs_local.yaml
.
This allows for overriding of default entries while still maintaing the originals.
This option can be disabled with the ignore_local
setting.
Note
Automatic tests run via pytest
will always ignore
local config files unless they are being run with the --hardware
options.
Using the config server¶
Python¶
The server can be queried/set in python:
>>> from panoptes.utils.config import client
# Show the entire config item.
>>> client.get_config('location')
{'elevation': 3400.0,
'flat_horizon': -6.0,
'focus_horizon': -12.0,
'gmt_offset': -600.0,
'horizon': 30,
'latitude': 19.54,
'longitude': -155.58,
'name': 'Mauna Loa Observatory',
'observe_horizon': -18.0,
'timezone': 'US/Hawaii'}
# Get just a specific value.
>>> client.get_config('location.horizon')
30.0
# Set to a new value.
>>> client.set_config('location.horizon', 45)
{'location.horizon': 45.0}
# Retrieve new value.
>>> client.get_config('location.horizon')
45.0
# Work with units.
>>> from astropy import units as u
>>> client.set_config('location.horizon', 45 * u.deg)
{'location.horizon': <Quantity 45. deg>}
>>> client.get_config('location.horizon')
<Quantity 45. deg>
>>> client.get_config('location')
{'elevation': 3400.0,
'flat_horizon': -6.0,
'focus_horizon': -12.0,
'gmt_offset': -600.0,
'horizon': <Quantity 45. deg>,
'latitude': 19.54,
'longitude': -155.58,
'name': 'Mauna Loa Observatory',
'observe_horizon': -18.0,
'timezone': 'US/Hawaii'}
# Get the second camera model
>>> client.get_config('cameras.devices[1].model')
'canon_gphoto2'
Command-line¶
The panoptes-config-server get
command will fetch the requested key (or the entire
config if no is provided) and print it out to the console as JSON string.
The panoptes-config-server set
command will set the value for the given key.
$ panoptes-config-server get --key location
{
"elevation": 3400,
"flat_horizon": -6,
"focus_horizon": -12,
"gmt_offset": -600,
"horizon": "45.0 deg",
"latitude": 19.54,
"longitude": -155.58,
"name": "Mauna Loa Observatory",
"observe_horizon": -18,
"timezone": "US/Hawaii"
}
$ panoptes-config-server set 'location.horizon' '37 deg'
{'location.horizon': <Quantity 37. deg>}
See panoptes-config-server get --help
and panoptes-config-server set --help
for more details.
panoptes¶
panoptes namespace¶
Subpackages¶
panoptes.utils package¶
Subpackages¶
panoptes.utils.cli namespace¶
Submodules¶
panoptes.utils.cli.image module¶
- panoptes.utils.cli.image.cr2_to_fits(cr2_fname: Path, fits_fname: str | None = None, overwrite: bool = True, remove_cr2: bool = False) Path [source]¶
Convert a CR2 image to a FITS, return the new path name.
- panoptes.utils.cli.image.cr2_to_jpg(cr2_fname: Path, jpg_fname: str | None = None, title: str = '', overwrite: bool = False, remove_cr2: bool = False) Path | None [source]¶
Extract a JPG image from a CR2, return the new path name.
- panoptes.utils.cli.image.solve_fits(fits_fname: Path, **kwargs) Path | None [source]¶
Plate-solve a FITS file.
- panoptes.utils.cli.image.watch_directory(path: Path, to_jpg: bool = True, to_fits: bool = True, solve: bool = True, overwrite: bool = False, remove_cr2: bool = False) None [source]¶
Watch a directory for changes and process any new files.
The files will be processed according to the boolean flags, with the flag names corresponding to other image commands.
By default, all the flags are enabled, which will:
Extract JPG files from a CR2.
Convert CR2 files to FITS.
Plate-solve FITS files.
panoptes.utils.cli.main module¶
panoptes.utils.config package¶
Submodules¶
panoptes.utils.config.cli module¶
panoptes.utils.config.client module¶
- panoptes.utils.config.client.get_config(key=None, default=None, host=None, port=None, endpoint='get-config', parse=True, verbose=False)[source]¶
Get a config item from the config server.
Return the config entry for the given
key
. Ifkey=None
(default), return the entire config.Nested keys can be specified as a string, as per scalpl.
Examples:
>>> get_config(key='name') 'Testing PANOPTES Unit' >>> get_config(key='location.horizon') <Quantity 30. deg> >>> # With no parsing, the raw string (including quotes) is returned. >>> get_config(key='location.horizon', parse=False) '"30 deg"' >>> get_config(key='cameras.devices[1].model') 'canon_gphoto2' >>> # Returns `None` if key is not found. >>> foobar = get_config(key='foobar') >>> foobar is None True >>> # But you can supply a default. >>> get_config(key='foobar', default='baz') 'baz' >>> # key and default are first two parameters. >>> get_config('foobar', 'baz') 'baz' >>> # Can use Quantities as well. >>> from astropy import units as u >>> get_config('foobar', 42 * u.meter) <Quantity 42. m>
Notes
By default all calls to this function will log at the trace level because there are some calls (e.g. during POCS operation) that will be quite noisy.
Setting verbose=True changes those to debug log levels for an individual call.
- Parameters:
key (str) – The key to update, see Examples in
get_config()
for details.default (str, optional) – The config server port, defaults to 6563.
host (str, optional) – The config server host. First checks for PANOPTES_CONFIG_HOST env var, defaults to ‘localhost’.
port (str or int, optional) – The config server port. First checks for PANOPTES_CONFIG_HOST env var, defaults to 6563.
endpoint (str, optional) – The relative url endpoint to use for getting the config items, default ‘get-config’. See server_is_running() for example of usage.
parse (bool, optional) – If response should be parsed by
panoptes.utils.serializers.from_json()
, default True.verbose (bool, optional) – Determines the output log level, defaults to True (i.e. debug log level). See notes for details.
- Returns:
The corresponding config entry.
- Return type:
- Raises:
Exception – Raised if the config server is not available.
- panoptes.utils.config.client.server_is_running(*args, **kwargs)[source]¶
Thin-wrapper to check server.
- panoptes.utils.config.client.set_config(key, new_value, host=None, port=None, parse=True)[source]¶
Set config item in config server.
Given a key entry, update the config to match. The key is a dot accessible string, as given by scalpl. See Examples in
get_config()
for details.Examples:
>>> from astropy import units as u >>> # Can use astropy units. >>> set_config('location.horizon', 35 * u.degree) {'location.horizon': <Quantity 35. deg>} >>> get_config(key='location.horizon') <Quantity 35. deg> >>> # String equivalent works for 'deg', 'm', 's'. >>> set_config('location.horizon', '30 deg') {'location.horizon': <Quantity 30. deg>}
- Parameters:
key (str) – The key to update, see Examples in
get_config()
for details.new_value (scalar|object) – The new value for the key, can be any serializable object.
host (str, optional) – The config server host. First checks for PANOPTES_CONFIG_HOST env var, defaults to ‘localhost’.
port (str or int, optional) – The config server port. First checks for PANOPTES_CONFIG_HOST env var, defaults to 6563.
parse (bool, optional) – If response should be parsed by
panoptes.utils.serializers.from_json()
, default True.
- Returns:
The updated config entry.
- Return type:
- Raises:
Exception – Raised if the config server is not available.
panoptes.utils.config.helpers module¶
- panoptes.utils.config.helpers.load_config(config_files: Path | List | None = None, parse: bool = True, load_local: bool = True)[source]¶
Load configuration information.
Note
This function is used by the config server and normal config usage should be via a running config server.
This function supports loading of a number of different files. If no options are passed to
config_files
then the default$PANOPTES_CONFIG_FILE
will be loaded.config_files
is a list and loaded in order, so the second entry will overwrite any values specified by similarly named keys in the first entry.config_files
should be specified by an absolute path, which can exist anywhere on the filesystem.Local versions of files can override built-in versions and are automatically loaded if they exist alongside the specified config path. Local files have a
<>_local.yaml
name, where<>
is the built-in file.Given the following path:
/path/to/dir |- my_conf.yaml |- my_conf_local.yaml
You can do a
load_config('/path/to/dir/my_conf.yaml')
and both versions of the file will be loaded, with the values in the local file overriding the non-local. Typically the local file would also be ignored bygit
, etc.For example, the
panoptes.utils.config.server.config_server
will always save values to a local version of the file so the default settings can always be recovered if necessary.Local files can be ignored (mostly for testing purposes or for recovering default values) with the
load_local=False
parameter.- Parameters:
config_files (list, optional) – A list of files to load as config, see Notes for details of how to specify files.
parse (bool, optional) – If the config file should attempt to create objects such as dates, astropy units, etc.
load_local (bool, optional) – If local files should be used, see Notes for details.
- Returns:
A dictionary of config items.
- Return type:
- panoptes.utils.config.helpers.parse_config_directories(directories: Dict[str, str])[source]¶
Parse the config dictionary for common objects.
Given a base entry that corresponds to the absolute path of a directory, prepend the base to all other relative directory entries.
The base directory must exist or an exception is rasied.
If the base entry is not given the current working directory is used.
>>> dirs_config = dict(base='/tmp', foo='bar', baz='bam', app='/app') >>> parse_config_directories(dirs_config) {'base': '/tmp', 'foo': '/tmp/bar', 'baz': '/tmp/bam', 'app': '/app'} >>> # If base doesn't exist an exception is raised. >>> dirs_config = dict(base='/panoptes', foo='bar', baz='bam', app='/app') >>> parse_config_directories(dirs_config) Traceback (most recent call last): ... panoptes.utils.error.NotFound: NotFound: Base directory does not exist: /panoptes
- Parameters:
directories (dict) – The dictionary of directory information. Usually comes from the “directories” entry in the config.
- Returns:
The same directory but with relative directories resolved.
- Return type:
- Raises:
panoptes.utils.error.NotFound – if the ‘base’ entry is given but does not exist.
panoptes.utils.config.server module¶
- panoptes.utils.config.server.config_server(config_file, host=None, port=None, load_local=True, save_local=False, auto_start=True, access_logs=None, error_logs='logger')[source]¶
Start the config server in a separate process.
A convenience function to start the config server.
- Parameters:
config_file (str or None) – The absolute path to the config file to load.
host (str, optional) – The config server host. First checks for PANOPTES_CONFIG_HOST env var, defaults to ‘localhost’.
port (str or int, optional) – The config server port. First checks for PANOPTES_CONFIG_HOST env var, defaults to 6563.
load_local (bool, optional) – If local config files should be used when loading, default True.
save_local (bool, optional) – If setting new values should auto-save to local file, default False.
auto_start (bool, optional) – If server process should be started automatically, default True.
access_logs (‘default’ or logger or File-like or None, optional) – Controls access logs for the gevent WSGIServer. The default string will cause access logs to go to stderr. The string logger will use the panoptes logger. A File-like will write to file. The default None will turn off all access logs.
error_logs (‘default’ or ‘logger’ or File-like or None, optional) – Same as access_logs except we use our logger as the default.
- Returns:
The process running the config server.
- Return type:
- panoptes.utils.config.server.get_config_entry()[source]¶
Get config entries from server.
Endpoint that responds to GET and POST requests and returns configuration item corresponding to provided key or entire configuration. The key entries should be specified in dot-notation, with the names corresponding to the entries stored in the configuration file. See the scalpl documentation for details on the dot-notation.
The endpoint should receive a JSON document with a single key named
"key"
and a value that corresponds to the desired key within the configuration.For example, take the following configuration:
{ 'location': { 'elevation': 3400.0, 'latitude': 19.55, 'longitude': 155.12, } }
To get the corresponding value for the elevation, pass a JSON document similar to:
'{"key": "location.elevation"}'
- Returns:
The json string for the requested object if object is found in config. Otherwise a json string with
status
andmsg
keys will be returned.- Return type:
- panoptes.utils.config.server.heartbeat()[source]¶
A simple echo service to be used for a heartbeat.
Defaults to looking for the ‘config_server.running’ bool value, although a different key can be specified in the POST.
- panoptes.utils.config.server.reset_config()[source]¶
Reset the configuration.
An endpoint that accepts a POST method. The json request object must contain the key
reset
(with any value).The method will reset the configuration to the original configuration files that were used, skipping the local (and saved file).
Note
If the server was originally started with a local version of the file, those will be skipped upon reload. This is not ideal but hopefully this method is not used too much.
- Returns:
A json string object containing the keys
success
andmsg
that indicate success or failure.- Return type:
- panoptes.utils.config.server.set_config_entry()[source]¶
Sets an item in the config.
Endpoint that responds to GET and POST requests and sets a configuration item corresponding to the provided key.
The key entries should be specified in dot-notation, with the names corresponding to the entries stored in the configuration file. See the scalpl documentation for details on the dot-notation.
The endpoint should receive a JSON document with a single key named
"key"
and a value that corresponds to the desired key within the configuration.For example, take the following configuration:
{ 'location': { 'elevation': 3400.0, 'latitude': 19.55, 'longitude': 155.12, } }
To set the corresponding value for the elevation, pass a JSON document similar to:
'{"location.elevation": "1000 m"}'
- Returns:
If method is successful, returned json string will be a copy of the set values. On failure, a json string with
status
andmsg
keys will be returned.- Return type:
Module contents¶
panoptes.utils.database package¶
Submodules¶
panoptes.utils.database.base module¶
- class panoptes.utils.database.base.AbstractPanDB(db_name=None, **kwargs)[source]¶
Bases:
object
- abstract clear_current(type)[source]¶
Clear the current record of a certain type
- Parameters:
type (str) – The type of entry in the current collection that should be cleared.
- abstract insert(collection, obj)[source]¶
Insert an object into the collection provided.
The obj to be stored in a collection should include the type and date metadata as well as a data key that contains the actual object data. If these keys are not provided then obj will be wrapped in a corresponding object that does contain the metadata.
- abstract insert_current(collection, obj, store_permanently=True)[source]¶
Insert an object into both the current collection and the collection provided.
- Parameters:
- Returns:
- identifier of inserted record. If store_permanently is True, will
be the identifier of the object in the collection, otherwise will be the identifier of object in the current collection. These may or may not be the same. Returns None if unable to insert into the collection.
- Return type:
- class panoptes.utils.database.base.PanDB(db_type='memory', db_name=None, *args, **kwargs)[source]¶
Bases:
object
Simple class to load the appropriate DB type based on the config.
We don’t actually create instances of this class, but instead create an instance of the ‘correct’ type of db.
- panoptes.utils.database.base.create_storage_obj(collection, data, obj_id)[source]¶
Wrap the data in a dict along with the id and a timestamp.
- panoptes.utils.database.base.get_db_class(module_name='file')[source]¶
Load the main DB class for the module of the given name.
Note
This is used by the PanDB constructor to determine the correct database type. Normal DB instantiation should be done via the PanDB() class with the desired db_type parameter set. See example in PanDB below.
panoptes.utils.database.file module¶
- class panoptes.utils.database.file.PanFileDB(db_name='panoptes', storage_dir='json_store', **kwargs)[source]¶
Bases:
AbstractPanDB
Stores collections as files of JSON records.
- clear_current(record_type)[source]¶
Clears the current record of the given type.
- Parameters:
record_type (str) – The record type, e.g. ‘weather’, ‘environment’, etc.
- insert(collection, obj)[source]¶
Insert an object into the collection provided.
The obj to be stored in a collection should include the type and date metadata as well as a data key that contains the actual object data. If these keys are not provided then obj will be wrapped in a corresponding object that does contain the metadata.
- insert_current(collection, obj, store_permanently=True)[source]¶
Insert an object into both the current collection and the collection provided.
- Parameters:
- Returns:
- identifier of inserted record. If store_permanently is True, will
be the identifier of the object in the collection, otherwise will be the identifier of object in the current collection. These may or may not be the same. Returns None if unable to insert into the collection.
- Return type:
panoptes.utils.database.memory module¶
- class panoptes.utils.database.memory.PanMemoryDB(**kwargs)[source]¶
Bases:
AbstractPanDB
In-memory store of serialized objects.
We serialize the objects in order to test the same code path used when storing in an external database.
- active_dbs = <WeakValueDictionary>¶
- clear_current(entry_type)[source]¶
Clear the current record of a certain type
- Parameters:
type (str) – The type of entry in the current collection that should be cleared.
- classmethod get_or_create(db_name=None, **kwargs)[source]¶
Returns the named db, creating if needed.
This method exists because PanDB gets called multiple times for the same database name. With mongo or a file store where the storage is external from the instance, that is not a problem, but with PanMemoryDB the instance is the store, so the instance must be shared.
- insert(collection, obj)[source]¶
Insert an object into the collection provided.
The obj to be stored in a collection should include the type and date metadata as well as a data key that contains the actual object data. If these keys are not provided then obj will be wrapped in a corresponding object that does contain the metadata.
- insert_current(collection, obj, store_permanently=True)[source]¶
Insert an object into both the current collection and the collection provided.
- Parameters:
- Returns:
- identifier of inserted record. If store_permanently is True, will
be the identifier of the object in the collection, otherwise will be the identifier of object in the current collection. These may or may not be the same. Returns None if unable to insert into the collection.
- Return type:
Module contents¶
panoptes.utils.images package¶
Submodules¶
panoptes.utils.images.bayer module¶
- class panoptes.utils.images.bayer.RGB(value)[source]¶
Bases:
IntEnum
Helper class for array index access.
- B = 2¶
- BLUE = 2¶
- G = 1¶
- G1 = 1¶
- GREEN = 1¶
- R = 0¶
- RED = 0¶
- panoptes.utils.images.bayer.get_pixel_color(x, y)[source]¶
Given a zero-indexed x,y position, return the corresponding color.
Note
See
get_rgb_data()
for a description of the RGGB pattern.- Returns:
one of ‘R’, ‘G1’, ‘G2’, ‘B’
- Return type:
- panoptes.utils.images.bayer.get_rgb_background(data, box_size=(79, 84), filter_size=(11, 11), estimator='mmm', interpolator='zoom', sigma=5, iters=10, exclude_percentile=100, return_separate=False, *args, **kwargs)[source]¶
Get the background for each color channel.
Note: This funtion does not perform any additional calibration, such as flat, bias, or dark correction. It is expected you have performed any necessary pre-processing to data before passing to this function.
By default this uses a box size of (79, 84), which gives an integer number of boxes. The size of the median filter box for the low resolution background is on the order of the stamp size.
Most of the options are described in the photutils.Background2D page: https://photutils.readthedocs.io/en/stable/background.html#d-background-and-noise-estimation
>>> from panoptes.utils.images.bayer import RGB >>> from panoptes.utils.images import fits as fits_utils >>> # Get our data and pre-process (basic bias subtract here). >>> fits_fn = getfixture('solved_fits_file') >>> camera_bias = 2048 >>> data = fits_utils.getdata(fits_fn) - camera_bias
>> The default is to return a single array for the background. >>> rgb_back = get_rgb_background(data) >>> rgb_back.mean() 136… >>> rgb_back.std() 36…
>>> # Can also return the Background2D objects, which is the input to save_rgb_bg_fits >>> rgb_backs = get_rgb_background(data, return_separate=True) >>> rgb_backs[RGB.RED] <photutils.background.background_2d.Background2D...>
>>> {color.name:int(rgb_back[color].mean()) for color in RGB} {'RED': 145, 'GREEN': 127, 'BLUE': 145}
- Parameters:
data (np.array) – The data to use if no fits_fn is provided.
box_size (tuple, optional) – The box size over which to compute the 2D-Background, default (79, 84).
filter_size (tuple, optional) – The filter size for determining the median, default (11, 12).
estimator (str, optional) – The estimator object to use, default ‘mmm’.
interpolator (str, optional) – The interpolater object to user, default ‘zoom’.
sigma (int, optional) – The sigma on which to filter values, default 5.
iters (int, optional) – The number of iterations to sigma filter, default 10.
exclude_percentile (int, optional) – The percentage of the data (per channel) that can be masked, default 100 (i.e. all).
return_separate (bool, optional) – If the function should return a separate array for color channel, default False.
*args – Description
**kwargs – Description
- Returns:
- Either a single numpy array representing the entire
background, or a list of masked numpy arrays in RGB order. The background for each channel has full interploation across all pixels, but the mask covers them.
- Return type:
`numpy.array`|list(Background2D)
- panoptes.utils.images.bayer.get_rgb_data(data, separate_green=False)[source]¶
Get the data split into separate channels for RGB.
data can be a 2D (W x H) or 3D (N x W x H) array where W=width and H=height of the data, with N=number of frames.
The return array will be a 3 x W x H or 3 x N x W x H array.
The Bayer array defines a superpixel as a collection of 4 pixels set in a square grid:
R G G B
ds9 and other image viewers define the coordinate axis from the lower left corner of the image, which is how a traditional x-y plane is defined and how most images would expect to look when viewed. This means that the (0, 0) coordinate position will be in the lower left corner of the image.
When the data is loaded into a numpy array the data is flipped on the vertical axis in order to maintain the same indexing/slicing features. This means the the
(0, 0)
coordinate position is in the upper-left corner of the array when output. When plotting this array one can use theorigin='lower'
option to view the array as would be expected in a normal image although this does not change the actual index.Image dimensions:
---------------------------- x | width | i | columns | 5208 y | height | j | rows | 3476
Bayer pattern as seen in ds9:
x / j 0 1 2 3 ... 5204 5205 5206 5207 -------------------------------------------- 3475 | R G1 R G1 R G1 R G1 3474 | G2 B G2 B G2 B G2 B 3473 | R G1 R G1 R G1 R G1 3472 | G2 B G2 B G2 B G2 B . | y / i . | . | 3 | R G1 R G1 R G1 R G1 2 | G2 B G2 B G2 B G2 B 1 | R G1 R G1 R G1 R G1 0 | G2 B G2 B G2 B G2 B
The RGGB super-pixels thus start in the upper-left.
Bayer pattern as seen in a numpy array:
x / j 0 1 2 3 ... 5204 5205 5206 5207 -------------------------------------------- 0 | G2 B G2 B G2 B G2 B 1 | R G1 R G1 R G1 R G1 2 | G2 B G2 B G2 B G2 B 3 | R G1 R G1 R G1 R G1 . | y / i . | . | 3472 | G2 B G2 B G2 B G2 B 3473 | R G1 R G1 R G1 R G1 3474 | G2 B G2 B G2 B G2 B 3475 | R G1 R G1 R G1 R G1
Here the RGGB super-pixels are flipped upside down.
In both cases the data is in the following format:
| row (y) | col (x) --------------| ------ R | odd i, | even j G1 | odd i, | odd j G2 | even i, | even j B | even i, | odd j
And a mask can therefore be generated as:
bayer[1::2, 0::2] = 1 # Red bayer[1::2, 1::2] = 1 # Green bayer[0::2, 0::2] = 1 # Green bayer[0::2, 1::2] = 1 # Blue
- panoptes.utils.images.bayer.get_rgb_masks(data, separate_green=False)[source]¶
Get the RGGB Bayer pattern for the given data.
Note
See
get_rgb_data()
for a description of the RGGB pattern.
- panoptes.utils.images.bayer.get_stamp_slice(x, y, stamp_size=(14, 14), ignore_superpixel=False, as_slices=True)[source]¶
Get the slice around a given position with fixed Bayer pattern.
Given an x,y pixel position, get the slice object for a stamp of a given size but make sure the first position corresponds to a red-pixel. This means that x,y will not necessarily be at the center of the resulting stamp.
>>> from panoptes.utils.images import bayer >>> # Make a super-pixel as represented in numpy (see full stamp below). >>> superpixel = np.array(['G2', 'B', 'R', 'G1']).reshape(2, 2) >>> superpixel array([['G2', 'B'], ['R', 'G1']], dtype='<U2') >>> # Tile it into a 5x5 grid of super-pixels, i.e. a 10x10 stamp. >>> stamp0 = np.tile(superpixel, (5, 5)) >>> stamp0 array([['G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B'], ['R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1'], ['G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B'], ['R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1'], ['G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B'], ['R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1'], ['G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B'], ['R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1'], ['G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B', 'G2', 'B'], ['R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1', 'R', 'G1']], dtype='<U2') >>> stamp1 = np.arange(100).reshape(10, 10) >>> stamp1 array([[ 0, 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]]) >>> x = 7 >>> y = 5 >>> pixel_index = (y, x) # y=rows, x=columns >>> stamp0[pixel_index] 'G1' >>> stamp1[pixel_index] 57 >>> slice0 = bayer.get_stamp_slice(x, y, stamp_size=(6, 6)) >>> slice0 (slice(2, 8, None), slice(4, 10, None)) >>> stamp0[slice0] array([['G2', 'B', 'G2', 'B', 'G2', 'B'], ['R', 'G1', 'R', 'G1', 'R', 'G1'], ['G2', 'B', 'G2', 'B', 'G2', 'B'], ['R', 'G1', 'R', 'G1', 'R', 'G1'], ['G2', 'B', 'G2', 'B', 'G2', 'B'], ['R', 'G1', 'R', 'G1', 'R', 'G1']], dtype='<U2') >>> stamp1[slice0] array([[24, 25, 26, 27, 28, 29], [34, 35, 36, 37, 38, 39], [44, 45, 46, 47, 48, 49], [54, 55, 56, 57, 58, 59], [64, 65, 66, 67, 68, 69], [74, 75, 76, 77, 78, 79]]) >>> # Return y_min, y_max, x_min, x_max >>> bayer.get_stamp_slice(x, y, stamp_size=(6, 6), as_slices=False) (2, 8, 4, 10)
The original index had a value of 57, which is within the center superpixel.
Notice that the resulting stamp has a super-pixel in the center and is bordered on all sides by a complete superpixel. This is required by default and an invalid size
We can use ignore_superpixel=True to get an odd-sized stamp.
>>> slice1 = bayer.get_stamp_slice(x, y, stamp_size=(5, 5), ignore_superpixel=True) >>> slice1 (slice(3, 8, None), slice(5, 10, None)) >>> stamp0[slice1] array([['G1', 'R', 'G1', 'R', 'G1'], ['B', 'G2', 'B', 'G2', 'B'], ['G1', 'R', 'G1', 'R', 'G1'], ['B', 'G2', 'B', 'G2', 'B'], ['G1', 'R', 'G1', 'R', 'G1']], dtype='<U2') >>> stamp1[slice1] array([[35, 36, 37, 38, 39], [45, 46, 47, 48, 49], [55, 56, 57, 58, 59], [65, 66, 67, 68, 69], [75, 76, 77, 78, 79]])
This puts the requested pixel in the center but does not offer any guarantees about the RGGB pattern.
- Parameters:
x (float) – X pixel position.
y (float) – Y pixel position.
stamp_size (tuple, optional) – The size of the cutout, default (14, 14).
ignore_superpixel (bool) – If superpixels should be ignored, default False.
as_slices (bool) – Return slice objects, default True. Otherwise returns: y_min, y_max, x_min, x_max
- Returns:
- A list of row and
column slice objects or a list defining the bounding box: y_min, y_max, x_min, x_max. Return type depends on the as_slices parameter and defaults to a list of two slices.
- Return type:
list(slice, slice) or list(int, int, int, int)
- panoptes.utils.images.bayer.save_rgb_bg_fits(rgb_bg_data, output_filename, header=None, fpack=True, overwrite=True)[source]¶
Save a FITS file containing a combined background as well as separate channels.
- Parameters:
rgb_bg_data (list[photutils.Background2D]) – The RGB background data as returned by calling panoptes.utils.images.bayer.get_rgb_background with return_separate=True.
output_filename (str) – The output name for the FITS file.
header (astropy.io.fits.Header) – FITS header to be saved with the file.
fpack (bool) – If the FITS file should be compressed, default True.
overwrite (bool) – If FITS file should be overwritten, default True.
panoptes.utils.images.cr2 module¶
- panoptes.utils.images.cr2.cr2_to_fits(cr2_fname: str | Path, fits_fname: str | None = None, overwrite: bool = False, headers: dict | None = None, fits_headers: dict | None = None, remove_cr2: bool = False, **kwargs) Path | None [source]¶
Convert a CR2 file to FITS.
This is a convenience function that first converts the CR2 to PGM via ~cr2_to_pgm. Also adds keyword headers to the FITS file.
Note
The intermediate PGM file is automatically removed
- Parameters:
cr2_fname (str) – Name of the CR2 file to be converted.
fits_fname (str, optional) – Name of the FITS file to output. Default is None, in which case the cr2_fname is used as the base.
overwrite (bool, optional) – Overwrite existing FITS, default False.
headers (dict, optional) – Header data added to the FITS file.
fits_headers (dict, optional) – Header data added to the FITS file without filtering.
remove_cr2 (bool, optional) – If CR2 should be removed after processing, default False.
**kwargs – Description
- Returns:
The full path to the generated FITS file.
- Return type:
- panoptes.utils.images.cr2.cr2_to_jpg(cr2_fname: Path, jpg_fname: str | None = None, title: str = '', overwrite: bool = False, remove_cr2: bool = False) Path | None [source]¶
Extract a JPG image from a CR2, return the new path name.
- panoptes.utils.images.cr2.cr2_to_pgm(cr2_fname, pgm_fname=None, overwrite=True, *args, **kwargs)[source]¶
Convert CR2 file to PGM
Converts a raw Canon CR2 file to a netpbm PGM file via dcraw. Assumes dcraw is installed on the system
Note
This is a blocking call
- Parameters:
convert (cr2_fname {str} -- Name of CR2 file to) –
script (**kwargs {dict} -- Additional keywords to pass to) –
- Keyword Arguments:
None (pgm_fname {str} -- Name of PGM file to output, if) – use same name as CR2 (default: {None})
(default (dcraw {str} -- Path to installed dcraw) – {‘dcraw’})
overwritten (overwrite {bool} -- A bool indicating if existing PGM should be) – (default: {True})
- Returns:
str – Filename of PGM that was created
- panoptes.utils.images.cr2.read_exif(fname, exiftool='exiftool')[source]¶
Read the EXIF information
Gets the EXIF information using exiftool
Note
Assumes the exiftool is installed
- Parameters:
file (fname {str} -- Name of) –
- Keyword Arguments:
(default (exiftool {str} -- Location of exiftool) – {‘/usr/bin/exiftool’})
- Returns:
dict – Dictionary of EXIF information
- panoptes.utils.images.cr2.read_pgm(fname, byteorder='>', remove_after=False)[source]¶
Return image data from a raw PGM file as numpy array.
Note
Format Spec: http://netpbm.sourceforge.net/doc/pgm.html Source: http://stackoverflow.com/questions/7368739/numpy-and-16-bit-pgm
Note
This is correctly processed as a Big endian even though the CR2 itself marks it as a Little endian. See the notes in Source page above as well as the comment about significant bit in the Format Spec
panoptes.utils.images.fits module¶
- class panoptes.utils.images.fits.ObservationPathInfo(unit_id: str | None = None, camera_id: str | None = None, field_name: str | None = None, sequence_time: str | datetime | Time | None = None, image_time: str | datetime | Time | None = None, path: str | Path | None = None)[source]¶
Bases:
object
Parse the location path for an image.
This is a small dataclass that offers some convenience methods for dealing with a path based on the image id.
This would usually be instantiated via path:
>>> from panoptes.utils.images.fits import ObservationPathInfo # noqa >>> bucket_path = 'gs://panoptes-images-background/PAN012/Hd189733/358d0f/20180824T035917/20180824T040118.fits' >>> path_info = ObservationPathInfo(path=bucket_path)
>>> path_info.id 'PAN012_358d0f_20180824T035917_20180824T040118'
>>> path_info.unit_id 'PAN012'
>>> path_info.sequence_id 'PAN012_358d0f_20180824T035917'
>>> path_info.image_id 'PAN012_358d0f_20180824T040118'
>>> path_info.as_path(base='/tmp', ext='jpg') PosixPath('/tmp/PAN012/358d0f/20180824T035917/20180824T040118.jpg')
>>> ObservationPathInfo(path='foobar') Traceback (most recent call last): ... ValueError: Invalid path received: self.path='foobar'
>>> # Works from a fits file directly, which reads header. >>> fits_fn = getfixture('unsolved_fits_file') >>> path_info = ObservationPathInfo.from_fits(fits_fn) >>> path_info.unit_id 'PAN001'
- as_path(base: str | Path | None = None, ext: str | None = None) Path [source]¶
Return a Path object.
- property id¶
Full path info joined with underscores
- panoptes.utils.images.fits.extract_metadata(header: Header) dict [source]¶
Get the metadata from a FITS image.
This function parses some of the more common headers (some from the update_observation_headers but others as well) and puts them into a dict with the obvious data types converted into objects (e.g. dates and times).
>>> # Check the headers >>> from panoptes.utils.images import fits as fits_utils >>> fits_fn = getfixture('unsolved_fits_file') >>> header = fits_utils.getheader(fits_fn) >>> metadata = extract_metadata(header) >>> metadata['unit']['name'] 'PAN001'
- Parameters:
header (astropy.io.fits.Header) – The Header object from a FITS file.
- panoptes.utils.images.fits.fits_to_jpg(fname=None, title=None, figsize=(10, 7.547169811320755), dpi=150, alpha=0.2, number_ticks=7, clip_percent=99.9, **kwargs)[source]¶
- panoptes.utils.images.fits.fpack(fits_fname, unpack=False, overwrite=True)[source]¶
Compress/Decompress a FITS file
Uses fpack (or funpack if unpack=True) to compress a FITS file
- Parameters:
fits_fname ({str}) – Name of a FITS file that contains a WCS.
unpack ({bool}, optional) – file should decompressed instead of compressed, default False.
- Returns:
Filename of compressed/decompressed file.
- Return type:
- panoptes.utils.images.fits.funpack(*args, **kwargs)[source]¶
Unpack a FITS file.
Note
This is a thin-wrapper around the ~fpack function with the unpack=True option specified. See ~fpack documentation for details.
- Parameters:
*args – Arguments passed to ~fpack.
**kwargs – Keyword arguments passed to ~fpack.
- Returns:
Path to uncompressed FITS file.
- Return type:
- panoptes.utils.images.fits.get_solve_field(fname: str | Path, replace: bool = True, overwrite: bool = True, timeout: float = 30, **kwargs) Dict [source]¶
Convenience function to wait for solve_field to finish.
This function merely passes the fname of the image to be solved along to solve_field, which returns a subprocess.Popen object. This function then waits for that command to complete, populates a dictonary with the EXIF informaiton and returns. This is often more useful than the raw solve_field function.
Example:
>>> from panoptes.utils.images import fits as fits_utils
>>> # Get our fits filename. >>> fits_fn = getfixture('unsolved_fits_file')
>>> # Perform the solve. >>> solve_info = fits_utils.get_solve_field(fits_fn)
>>> # Show solved filename. >>> solve_info['solved_fits_file'] '.../unsolved.fits'
>>> # Pass a suggested location. >>> ra = 15.23 >>> dec = 90 >>> radius = 5 # deg >>> solve_info = fits_utils.solve_field(fits_fn, ra=ra, dec=dec, radius=radius)
>>> # Pass kwargs to `solve-field` program. >>> solve_kwargs = {'--pnm': '/tmp/awesome.bmp'} >>> solve_info = fits_utils.get_solve_field(fits_fn, skip_solved=False, **solve_kwargs) >>> assert os.path.exists('/tmp/awesome.bmp')
- Parameters:
fname ({str}) – Name of FITS file to be solved.
replace (bool, optional) – Saves the WCS back to the original file, otherwise output base filename with .new extension. Default True.
overwrite (bool, optional) – Clobber file, default True. Required if replace=True.
timeout (int, optional) – The timeout for solving, default 30 seconds.
**kwargs ({dict}) – Options to pass to solve_field should start with –.
- Returns:
Keyword information from the solved field.
- Return type:
- panoptes.utils.images.fits.get_wcsinfo(fits_fname, **kwargs)[source]¶
Returns the WCS information for a FITS file.
Uses the wcsinfo astrometry.net utility script to get the WCS information from a plate-solved file.
- Parameters:
fits_fname ({str}) – Name of a FITS file that contains a WCS.
**kwargs – Args that can be passed to wcsinfo.
- Returns:
Output as returned from wcsinfo.
- Return type:
- Raises:
error.InvalidCommand – Raised if wcsinfo is not found (part of astrometry.net)
- panoptes.utils.images.fits.getdata(fn, *args, **kwargs)[source]¶
Get the FITS data.
Small wrapper around astropy.io.fits.getdata to auto-determine the FITS extension. This will return the data associated with the image.
>>> fits_fn = getfixture('solved_fits_file') >>> d0 = getdata(fits_fn) >>> d0 array([[2215, 2169, 2200, ..., 2169, 2235, 2168], [2123, 2191, 2133, ..., 2212, 2127, 2217], [2208, 2154, 2209, ..., 2159, 2233, 2187], ..., [2120, 2201, 2120, ..., 2209, 2126, 2195], [2219, 2151, 2199, ..., 2173, 2214, 2166], [2114, 2194, 2122, ..., 2202, 2125, 2204]], dtype=uint16) >>> d1, h1 = getdata(fits_fn, header=True) >>> (d0 == d1).all() True >>> h1['FIELD'] 'KIC 8462852'
- Parameters:
fn (str) – Path to FITS file.
*args – Passed to astropy.io.fits.getdata.
**kwargs – Passed to astropy.io.fits.getdata.
- Returns:
The FITS data.
- Return type:
np.ndarray
- panoptes.utils.images.fits.getheader(fn, *args, **kwargs)[source]¶
Get the FITS header.
Small wrapper around astropy.io.fits.getheader to auto-determine the FITS extension. This will return the header associated with the image. If you need the compression header information use the astropy module directly.
>>> fits_fn = getfixture('tiny_fits_file') >>> os.path.basename(fits_fn) 'tiny.fits' >>> header = getheader(fits_fn) >>> header['IMAGEID'] 'PAN001_XXXXXX_20160909T081152'
>>> # Works with fpacked files >>> fits_fn = getfixture('solved_fits_file') >>> os.path.basename(fits_fn) 'solved.fits.fz' >>> header = getheader(fits_fn) >>> header['IMAGEID'] 'PAN001_XXXXXX_20160909T081152'
- Parameters:
fn (str) – Path to FITS file.
*args – Passed to astropy.io.fits.getheader.
**kwargs – Passed to astropy.io.fits.getheader.
- Returns:
The FITS header for the data.
- Return type:
astropy.io.fits.header.Header
- panoptes.utils.images.fits.getval(fn, *args, **kwargs)[source]¶
Get a value from the FITS header.
Small wrapper around astropy.io.fits.getval to auto-determine the FITS extension. This will return the value from the header associated with the image (not the compression header). If you need the compression header information use the astropy module directly.
>>> fits_fn = getfixture('tiny_fits_file') >>> getval(fits_fn, 'IMAGEID') 'PAN001_XXXXXX_20160909T081152'
- panoptes.utils.images.fits.getwcs(fn, *args, **kwargs)[source]¶
Get the WCS for the FITS file.
Small wrapper around astropy.wcs.WCS.
>>> from panoptes.utils.images import fits as fits_utils >>> fits_fn = getfixture('solved_fits_file') >>> wcs = fits_utils.getwcs(fits_fn) >>> wcs.is_celestial True >>> fits_fn = getfixture('unsolved_fits_file') >>> wcs = fits_utils.getwcs(fits_fn) >>> wcs.is_celestial False
- Parameters:
fn (str) – Path to FITS file.
*args – Passed to astropy.io.fits.getheader.
**kwargs – Passed to astropy.io.fits.getheader.
- Returns:
The World Coordinate System information.
- Return type:
astropy.wcs.WCS
- panoptes.utils.images.fits.solve_field(fname, timeout=15, solve_opts=None, *args, **kwargs)[source]¶
Plate solves an image.
- Note: This is a low-level wrapper around the underlying solve-field
program. See get_solve_field for more typical usage and examples.
- panoptes.utils.images.fits.update_observation_headers(file_path, info)[source]¶
Update FITS headers with items from the Observation status.
>>> # Check the headers >>> from panoptes.utils.images import fits as fits_utils >>> fits_fn = getfixture('unsolved_fits_file') >>> # Show original value >>> fits_utils.getval(fits_fn, 'FIELD') 'KIC 8462852'
>>> info = {'field_name': 'Tabbys Star'} >>> update_observation_headers(fits_fn, info) >>> # Show new value >>> fits_utils.getval(fits_fn, 'FIELD') 'Tabbys Star'
- panoptes.utils.images.fits.write_fits(data, header, filename, exposure_event=None, **kwargs)[source]¶
Write FITS file to requested location.
>>> from panoptes.utils.images import fits as fits_utils >>> data = np.random.normal(size=100) >>> header = { 'FILE': 'delete_me', 'TEST': True } >>> filename = str(getfixture('tmpdir').join('temp.fits')) >>> fits_utils.write_fits(data, header, filename) >>> assert os.path.exists(filename)
>>> fits_utils.getval(filename, 'FILE') 'delete_me' >>> data2 = fits_utils.getdata(filename) >>> assert np.array_equal(data, data2)
- Parameters:
data (array_like) – The data to be written.
header (dict) – Dictionary of items to be saved in header.
filename (str) – Path to filename for output.
exposure_event (None|`threading.Event`, optional) – A threading.Event that can be triggered when the image is written.
kwargs (dict) – Options that are passed to the astropy.io.fits.PrimaryHDU.writeto method.
panoptes.utils.images.focus module¶
- panoptes.utils.images.focus.focus_metric(data, merit_function='vollath_F4', **kwargs)[source]¶
Compute the focus metric.
Computes a focus metric on the given data using a supplied merit function. The merit function can be passed either as the name of the function (must be defined in this module) or as a callable object. Additional keyword arguments for the merit function can be passed as keyword arguments to this function.
- Parameters:
data (numpy array) –
merit_function (str/callable) – panoptes.utils.images) or a callable object.
- Returns:
result of calling merit function on data
- Return type:
scalar
- panoptes.utils.images.focus.vollath_F4(data, axis=None)[source]¶
Compute F4 focus metric
Computes the F_4 focus metric as defined by Vollath (1998) for the given 2D numpy array. The metric can be computed in the y axis, x axis, or the mean of the two (default).
- Parameters:
data (numpy array) –
axis (str, optional, default None) – be ‘Y’/’y’, ‘X’/’x’ or None, which will calculate the F4 value for both axes and return the mean.
- Returns:
Calculated F4 value for y, x axis or both
- Return type:
float64
panoptes.utils.images.misc module¶
- panoptes.utils.images.misc.crop_data(data, box_width=200, center=None, data_only=True, wcs=None, **kwargs)[source]¶
Return a cropped portion of the image.
Shape is a box centered around the middle of the data
>>> from matplotlib import pyplot as plt >>> from astropy.wcs import WCS >>> from panoptes.utils.images.misc import crop_data >>> from panoptes.utils.images.plot import add_colorbar, get_palette >>> from panoptes.utils.images.fits import getdata >>> >>> fits_url = 'https://github.com/panoptes/panoptes-utils/raw/develop/tests/data/solved.fits.fz' >>> data, header = getdata(fits_url, header=True) >>> wcs = WCS(header) >>> # Crop a portion of the image by WCS and get Cutout2d object. >>> cropped = crop_data(data, center=(600, 400), box_width=100, wcs=wcs, data_only=False) >>> fig, ax = plt.subplots() >>> im = ax.imshow(cropped.data, origin='lower', cmap=get_palette()) >>> add_colorbar(im) >>> plt.show()
(
Source code
,png
,hires.png
,pdf
)- Parameters:
data (numpy.array) – Array of data.
box_width (int, optional) – Size of box width in pixels, defaults to 200px.
center (tuple(int, int), optional) – Crop around set of coords, default to image center.
data_only (bool, optional) – If True (default), return only data. If False return the Cutout2D object.
wcs (None|`astropy.wcs.WCS`, optional) – A valid World Coordinate System (WCS) that will be cropped along with the data if provided.
- Returns:
- A clipped (thumbnailed) version of the data if data_only=True, otherwise
a astropy.nddata.Cutout2D object.
- Return type:
np.array
- panoptes.utils.images.misc.make_timelapse(directory, fn_out=None, glob_pattern='20[1-9][0-9]*T[0-9]*.jpg', overwrite=False, timeout=60, **kwargs)[source]¶
Create a timelapse.
A timelapse is created from all the images in given
directory
- Parameters:
directory (str) – Directory containing image files.
fn_out (str, optional) – Full path to output file name, if not provided, defaults to directory basename.
glob_pattern (str, optional) – A glob file pattern of images to include, default ‘20[1-9][0-9]*T[0-9]*.jpg’, which corresponds to the observation images but excludes any pointing images. The pattern should be relative to the local directory.
overwrite (bool, optional) – Overwrite timelapse if exists, default False.
timeout (int) – Timeout for making movie, default 60 seconds.
**kwargs (dict) –
- Returns:
Name of output file
- Return type:
- Raises:
error.InvalidSystemCommand – Raised if ffmpeg command is not found.
FileExistsError – Raised if fn_out already exists and overwrite=False.
- panoptes.utils.images.misc.mask_saturated(data, saturation_level=None, threshold=0.9, bit_depth=None, dtype=None)[source]¶
Convert data to a masked array with saturated values masked.
>>> from matplotlib import pyplot as plt >>> from astropy.wcs import WCS >>> from panoptes.utils.images.misc import crop_data, mask_saturated >>> from panoptes.utils.images.plot import add_colorbar, get_palette >>> from panoptes.utils.images.fits import getdata >>> >>> fits_url = 'https://github.com/panoptes/panoptes-utils/raw/develop/tests/data/solved.fits.fz' >>> data, header = getdata(fits_url, header=True) >>> wcs = WCS(header) >>> # Crop a portion of the image by WCS and get Cutout2d object. >>> cropped = crop_data(data, center=(600, 400), box_width=100, wcs=wcs, data_only=False) >>> masked = mask_saturated(cropped.data, saturation_level=11535) >>> fig, ax = plt.subplots() >>> im = ax.imshow(masked, origin='lower', cmap=get_palette()) >>> add_colorbar(im) >>> fig.show()
(
Source code
,png
,hires.png
,pdf
)- Parameters:
data (array_like) – The numpy data array.
saturation_level (scalar, optional) – The saturation level. If not given then the saturation level will be set to threshold times the maximum pixel value.
threshold (float, optional) – The fraction of the maximum pixel value to use as the saturation level, default 0.9.
bit_depth (astropy.units.Quantity or int, optional) – The effective bit depth of the data. If given the maximum pixel value will be assumed to be 2**bit_depth, otherwise an attempt will be made to infer the maximum pixel value from the data type of the data. If data is not an integer type the maximum pixel value cannot be inferred and an IllegalValue exception will be raised.
dtype (numpy.dtype, optional) – The requested dtype for the masked array. If not given the dtype of the masked array will be same as data.
- Returns:
The masked numpy array.
- Return type:
numpy.ma.array
- Raises:
error.IllegalValue – Raised if bit_depth is an astropy.units.Quantity object but the units are not compatible with either bits or bits/pixel.
error.IllegalValue – Raised if neither saturation level or bit_depth are given, and data has a non integer data type.
panoptes.utils.images.plot module¶
- panoptes.utils.images.plot.add_colorbar(axes_image, size='5%', pad=0.05, orientation='vertical')[source]¶
Add a colorbar to the image.
This is a simple convenience function to add a colorbar to a plot generated by matplotlib.pyplot.imshow.
>>> from matplotlib import pyplot as plt >>> import numpy as np >>> from panoptes.utils.images.plot import add_colorbar >>> >>> x = np.arange(0.0, 100.0) >>> y = np.arange(0.0, 100.0) >>> X, Y = np.meshgrid(x, y) >>> >>> func = lambda x, y: x**2 + y**2 >>> z = func(X, Y) >>> >>> fig, ax = plt.subplots() >>> im1 = ax.imshow(z, origin='lower') >>> >>> # Add the colorbar to the Image object (not the Axes). >>> add_colorbar(im1) >>> >>> fig.show()
(
Source code
,png
,hires.png
,pdf
)A colorbar with sane settings.¶
- Parameters:
axes_image (matplotlib.image.AxesImage) – A matplotlib AxesImage.
- panoptes.utils.images.plot.add_pixel_grid(ax1, grid_height, grid_width, show_axis_labels=True, show_superpixel=False, major_alpha=0.5, minor_alpha=0.25)[source]¶
Adds a pixel grid to a plot, including features for the Bayer array superpixel.
>>> from matplotlib import pyplot as plt >>> import numpy as np >>> from panoptes.utils.images.plot import add_pixel_grid >>> >>> x = np.arange(-5, 5) >>> y = np.arange(-5, 5) >>> X, Y = np.meshgrid(x, y) >>> func = lambda x, y: x**2 - y**2 >>> >>> fig, ax = plt.subplots() >>> im1 = ax.imshow(func(X, Y), origin='lower', cmap='Greys') >>> >>> # Add the grid to the Axes object. >>> add_pixel_grid(ax, grid_height=10, grid_width=10, show_superpixel=True, show_axis_labels=False) >>> >>> fig.show()
(
Source code
,png
,hires.png
,pdf
)The Bayer array superpixel pattern. Grid height and size must be even.¶
- Parameters:
ax1 (matplotlib.axes.Axes) – The axes to add the grid to.
grid_height (int) – The height of the grid in pixels.
grid_width (int) – The width of the grid in pixels.
show_axis_labels (bool, optional) – Whether to show the axis labels. Default True.
show_superpixel (bool, optional) – Whether to show the superpixel pattern. Default False.
major_alpha (float, optional) – The alpha value for the major grid lines. Default 0.5.
minor_alpha (float, optional) – The alpha value for the minor grid lines. Default 0.25.
Module contents¶
- panoptes.utils.images.make_pretty_image(fname, title=None, img_type=None, link_path=None, **kwargs) Path | None [source]¶
Make a pretty image.
This will create a jpg file from either a CR2 (Canon) or FITS file.
- Parameters:
fname (str) – The path to the raw image.
title (None|str, optional) – Title to be placed on image, default None.
img_type (None|str, optional) – Image type of fname, one of ‘.cr2’ or ‘.fits’. The default is None, in which case the file extension of fname is used.
link_path (None|str, optional) – Path to location that image should be symlinked. The directory must exist.
script. (**kwargs {dict} -- Additional arguments to be passed to external) –
- Returns:
str – Filename of image that was created.
panoptes.utils.serial package¶
Submodules¶
panoptes.utils.serial.device module¶
- class panoptes.utils.serial.device.SerialDevice(port: str | None = None, name: str | None = None, reader_callback: Callable | None = None, serial_settings: SerialDeviceDefaults | dict | None = None, reader_queue_size: int = 50)[source]¶
Bases:
object
- property is_connected¶
True if serial port is open, False otherwise.
- property port¶
Name of the port.
- class panoptes.utils.serial.device.SerialDeviceDefaults(baudrate: int = 9600, timeout: float = 1.0, write_timeout: float = 1.0, bytesize: int = 8, parity: str = 'N', stopbits: int = 1, xonxoff: bool = False, rtscts: bool = False, dsrdtr: bool = False)[source]¶
Bases:
object
Dataclass for the serial port defaults.
This can be instantiated with changed values and then passed to the serial device via the apply_settings method.
Values are:
write_timeout, inter_byte_timeout, dsrdtr, baudrate, timeout, parity, bytesize, rtscts, stopbits, xonxoff
See https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.Serial.get_settings
- panoptes.utils.serial.device.find_serial_port(vendor_id, product_id, return_all=False)[source]¶
Finds the serial port that matches the given vendor and product id.
>>> from panoptes.utils.serial.device import find_serial_port >>> vendor_id = 0x2a03 # arduino >>> product_id = 0x0043 # Uno Rev 3 >>> find_serial_port(vendor_id, product_id) '/dev/ttyACM0' >>> # Raises error when not found. >>> find_serial_port(0x1234, 0x4321) Traceback (most recent call last): ... panoptes.utils.error.NotFound: NotFound: No serial ports...
- Parameters:
- Returns:
Either the path to the detected port or a list of all comports that match.
- Return type:
- panoptes.utils.serial.device.get_serial_port_info()[source]¶
Returns the serial ports defined on the system sorted by device name.
>>> from panoptes.utils.serial.device import get_serial_port_info >>> devices = get_serial_port_info() >>> devices [<serial.tools.list_ports_linux.SysFS object at 0x7f6c9cbd9460>] >>> devices[0].hwid 'USB VID:PID=2886:802D SER=3C788B875337433838202020FF122204 LOCATION=3-5:1.0'
- Returns: a list of PySerial’s ListPortInfo objects. See:
https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports_common.py
Module contents¶
Submodules¶
panoptes.utils.error module¶
- exception panoptes.utils.error.AlreadyExists(msg=None, exit=False)[source]¶
Bases:
PanError
Generic already exists class
- exception panoptes.utils.error.ArduinoDataError(msg=None, exit=False)[source]¶
Bases:
PanError
PanError raised when there is something very wrong with Arduino information.
- exception panoptes.utils.error.BadConnection(msg=None, exit=False)[source]¶
Bases:
PanError
PanError raised when a connection is bad
- exception panoptes.utils.error.BadSerialConnection(msg=None, exit=False)[source]¶
Bases:
PanError
PanError raised when serial command is bad
- exception panoptes.utils.error.CameraNotFound(msg=None, exit=False)[source]¶
Bases:
NotFound
Camera cannot be imported
- exception panoptes.utils.error.DomeNotFound(msg=None, exit=False)[source]¶
Bases:
NotFound
Dome device not found.
- exception panoptes.utils.error.GoogleCloudError(msg=None, exit=False)[source]¶
Bases:
PanError
Errors related to google cloud
- exception panoptes.utils.error.IllegalValue(msg=None, exit=False)[source]¶
Bases:
PanError
,ValueError
Errors from trying to hardware parameters to values not supported by a particular model
- exception panoptes.utils.error.InvalidCommand(msg=None, exit=False)[source]¶
Bases:
PanError
PanError raised if a system command does not run
- exception panoptes.utils.error.InvalidConfig(msg=None, exit=False)[source]¶
Bases:
PanError
PanError raised if config file is invalid
- exception panoptes.utils.error.InvalidDeserialization(msg='Problem deserializing', **kwargs)[source]¶
Bases:
PanError
Error for serialization errors
- exception panoptes.utils.error.InvalidMountCommand(msg=None, exit=False)[source]¶
Bases:
PanError
PanError raised if attempting to send command that doesn’t exist
- exception panoptes.utils.error.InvalidObservation(msg=None, exit=False)[source]¶
Bases:
NotFound
PanError raised if a field is invalid.
- exception panoptes.utils.error.InvalidSerialization(msg='Problem Serializing', **kwargs)[source]¶
Bases:
PanError
Error for serialization errors
- exception panoptes.utils.error.InvalidSystemCommand(msg='Problem running system command', **kwargs)[source]¶
Bases:
PanError
Error for a system level command malfunction
- exception panoptes.utils.error.MountNotFound(msg='Mount Not Found', **kwargs)[source]¶
Bases:
NotFound
Mount cannot be import
- exception panoptes.utils.error.NoObservation(msg='No valid observations found.', **kwargs)[source]¶
Bases:
PanError
Generic no Observation
- exception panoptes.utils.error.NotFound(msg=None, exit=False)[source]¶
Bases:
PanError
Generic not found class
- exception panoptes.utils.error.NotSupported(msg=None, exit=False)[source]¶
Bases:
PanError
,NotImplementedError
Errors from trying to use hardware features not supported by a particular model
- exception panoptes.utils.error.PanError(msg=None, exit=False)[source]¶
Bases:
Exception
Base class for Panoptes errors
- exception panoptes.utils.error.SolveError(msg=None, exit=False)[source]¶
Bases:
NotFound
Camera cannot be imported
- exception panoptes.utils.error.TheSkyXError(msg=None, exit=False)[source]¶
Bases:
PanError
Errors from TheSkyX
- exception panoptes.utils.error.TheSkyXKeyError(msg=None, exit=False)[source]¶
Bases:
TheSkyXError
Errors from TheSkyX because bad key passed
- exception panoptes.utils.error.TheSkyXTimeout(msg=None, exit=False)[source]¶
Bases:
TheSkyXError
Errors from TheSkyX because bad key passed
panoptes.utils.horizon module¶
- class panoptes.utils.horizon.Horizon(obstructions=None, default_horizon=30)[source]¶
Bases:
object
A simple class to define some coordinate points.
Accepts a list of lists where each list consists of two points corresponding to an altitude (0-90) and an azimuth (0-360).
The list of points for a given obstruction must be in clockwise ordering.
If azimuth is a negative number (but greater than -360) then 360 will be added to put it in the correct range.
The list are points that are obstruction points beyond the default horizon.
panoptes.utils.library module¶
- panoptes.utils.library.load_c_library(name, path=None, mode=0, **kwargs)[source]¶
Utility function to load a shared/dynamically linked library (.so/.dylib/.dll).
The name and location of the shared library can be manually specified with the library_path argument, otherwise the ctypes.util.find_library function will be used to try to locate based on library_name.
- Parameters:
name (str) – name of the library (without ‘lib’ prefix or any suffixes, e.g. ‘fli’).
path (str, optional) – path to the library e.g. ‘/usr/local/lib/libfli.so’.
mode (int, optional) – mode in which to load the library, see dlopen(3) man page for details. Should be one of ctypes.RTLD_GLOBAL, ctypes.RTLD_LOCAL, or ctypes.DEFAULT_MODE. Default is ctypes.DEFAULT_MODE.
- Returns:
ctypes.CDLL
- Raises:
pocs.utils.error.NotFound – raised if library_path not given & find_library fails to locate the library.
OSError – raises if the ctypes.CDLL loader cannot load the library.
- panoptes.utils.library.load_module(module_name)[source]¶
Dynamically load a module.
>>> from panoptes.utils.library import load_module >>> error = load_module('panoptes.utils.error') >>> error.__name__ 'panoptes.utils.error' >>> error.__package__ 'panoptes.utils'
- Parameters:
module_name (str) – Name of module to import.
- Returns:
an imported module name
- Return type:
module
- Raises:
error.NotFound – If module cannot be imported.
panoptes.utils.rs232 module¶
- class panoptes.utils.rs232.SerialData(*args, **kwargs)[source]¶
Bases:
object
SerialData wraps a PySerial instance for reading from and writing to a serial device.
Because POCS is intended to be very long running, and hardware may be turned off when unused or to force a reset, this wrapper may or may not have an open connection to the underlying serial device. Note that for most devices, is_connected will return true if the device is turned off/unplugged after a connection is opened; the code will only discover there is a problem when we attempt to interact with the device.
>>> from panoptes.utils.rs232 import SerialData >>> # Connect to our fake buffered device >>> device_listener = SerialData(port='loop://') >>> device_listener.is_connected True >>> device_listener.port 'loop://' >>> # Device sends event >>> bytes = device_listener.write('Hello World') >>> device_listener.read(bytes) 'Hello World'
- connect()[source]¶
If disconnected, then connect to the serial port.
- Raises:
error.BadSerialConnection if unable to open the connection. –
- disconnect()[source]¶
Closes the serial connection.
- Raises:
error.BadSerialConnection if unable to close the connection. –
- get_and_parse_reading(retry_limit=5)[source]¶
Reads a line of JSON text and returns the decoded value, along with the current time.
- Parameters:
retry_limit – Number of lines to read in an attempt to get one that parses as JSON.
- Returns:
A pair (tuple) of (timestamp, decoded JSON line). The timestamp is the time of completion of the readline operation.
- get_reading()[source]¶
Reads and returns a line, along with the timestamp of the read.
- Returns:
A pair (tuple) of (timestamp, line). The timestamp is the time of completion of the readline operation.
- property is_connected¶
True if serial port is open, False otherwise.
- property port¶
Name of the port.
- read(retry_limit=None, retry_delay=None)[source]¶
Reads next line of input using readline.
If no response is given, delay for retry_delay and then try to read again. Fail after retry_limit attempts.
- read_bytes(size=1)[source]¶
Reads size bytes from the serial port.
If a read timeout is set on self.ser, this may return less characters than requested. With no timeout it will block until the requested number of bytes is read.
- Parameters:
size – Number of bytes to read.
- Returns:
Bytes read from the port.
- reset_input_buffer()[source]¶
Clear buffered data from connected port/device.
Note that Wilfred reports that the input from an Arduino can seriously lag behind realtime (e.g. 10 seconds), and that clear_buffer may exist for that reason (i.e. toss out any buffered input from a device, and then read the next full line, which likely requires tossing out a fragment of a line).
- panoptes.utils.rs232.find_serial_port(vendor_id, product_id, return_all=False)[source]¶
Finds the serial port that matches the given vendor and product id.
>>> from panoptes.utils.rs232 import find_serial_port >>> vendor_id = 0x2a03 # arduino >>> product_id = 0x0043 # Uno Rev 3 >>> find_serial_port(vendor_id, product_id) '/dev/ttyACM0' >>> # Raises error when not found. >>> find_serial_port(0x1234, 0x4321) Traceback (most recent call last): ... panoptes.utils.error.NotFound: NotFound: No serial ports for vendor_id=4660 and product_id=17185
- Parameters:
- Returns:
Either the path to the detected port or a list of all comports that match.
- Return type:
- panoptes.utils.rs232.get_serial_port_info()[source]¶
Returns the serial ports defined on the system.
- Returns: a list of PySerial’s ListPortInfo objects. See:
https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports_common.py
panoptes.utils.serializers module¶
- class panoptes.utils.serializers.StringYAML(*, typ: List[str] | str | None = None, pure: Any = False, output: Any | None = None, plug_ins: Any | None = None)[source]¶
Bases:
YAML
- dump(data, stream=None, **kwargs)[source]¶
YAML class that can dump to a string.
By default the YAML parser doesn’t serialize directly to a string. This class is a small wrapper to output StreamIO as a string if no stream is provided.
See https://yaml.readthedocs.io/en/latest/example.html#output-of-dump-as-a-string.
Note
This class should not be used directly but instead is instantiated as part of the yaml convenience methods below.
- Parameters:
data (object) – An object, usually dict-like.
stream (None | stream, optional) – A stream object to write the YAML. If default None, return value as string.
**kwargs – Keywords passed to the dump function.
- Returns:
The serialized object string.
- Return type:
str
- panoptes.utils.serializers.deserialize_all_objects(obj)[source]¶
Recursively parse the incoming object for various data types.
This will currently attempt to parse and return, in the following order:
If
obj
is a dict with exactly two keys namedunit
andvalue
, then attempt to parse into a validastropy.unit.Quantity
.A boolean.
A datetime.datetime object as parsed by dateutil.parser.parse.
If a string ending with any of
['m', 'deg', 's']
, anastropy.unit.Quantity
Note
See the to/from_json/yaml methods, which use this function.
- Parameters:
obj (dict or str or object) – Object to check for quantities.
- Returns:
Same as obj but with objects converted to quantities.
- Return type:
dict
- panoptes.utils.serializers.from_json(msg)[source]¶
Convert a JSON string into a Python object.
Astropy quanitites will be converted from a
{"value": val, "unit": unit}
format. Additionally, the following units will be converted if the value ends with the exact string:deg
m
s
Time-like values are not parsed, however see example below.
Examples:
>>> from panoptes.utils.serializers import from_json >>> config_str = '{"name":"Mauna Loa","elevation":{"value":3397.0,"unit":"m"}}' >>> from_json(config_str) {'name': 'Mauna Loa', 'elevation': <Quantity 3397. m>} # Invalid values will be returned as is. >>> from_json('{"horizon":{"value":42.0,"unit":"degr"}}') {'horizon': {'value': 42.0, 'unit': 'degr'}} # The following will convert if final string: >>> from_json('{"horizon": "42.0 deg"}') {'horizon': <Quantity 42. deg>} >>> from_json('{"elevation": "1000 m"}') {'elevation': <Quantity 1000. m>} >>> from_json('{"readout_time": "10 s"}') {'readout_time': <Quantity 10. s>} # Be careful with short unit names in extended format! >>> horizon = from_json('{"horizon":{"value":42.0,"unit":"d"}}') >>> horizon['horizon'] <Quantity 42. d> >>> horizon['horizon'].decompose() <Quantity 3628800. s> >>> from panoptes.utils.time import current_time >>> time_str = to_json({"current_time": current_time().datetime}) >>> from_json(time_str)['current_time'] 2019-04-08T06:43:28.232406 >>> from astropy.time import Time >>> Time(from_json(time_str)['current_time']) <Time object: scale='utc' format='isot' value=2019-04-08T06:43:28.232>
- Parameters:
msg (str) – The JSON string representation of the object.
- Returns:
The loaded object.
- Return type:
dict
- panoptes.utils.serializers.from_yaml(msg, parse=True)[source]¶
Convert a YAML string into a Python object.
This is a thin-wrapper around ruamel.YAML.load that also parses the results looking for astropy.units.Quantity objects.
Comments are preserved as long as the object remains YAML (lost on conversion to JSON, for example).
See from_json for examples of astropy unit parsing.
Examples
Note how comments in the YAML are preserved.
>>> config_str = '''name: Testing PANOPTES Unit ... pan_id: PAN000 ... ... location: ... latitude: 19.54 deg ... longitude: -155.58 deg ... name: Mauna Loa Observatory # Can be anything ... ''' >>> config = from_yaml(config_str) >>> config['location']['latitude'] <Quantity 19.54 deg> >>> yaml_config = to_yaml(config) >>> yaml_config ''' name: Testing PANOPTES Unit ... pan_id: PAN000 # CHANGE NAME ... ... location: ... latitude: 19.54 deg ... longitude: value: -155.58 deg ... name: Mauna Loa Observatory # Can be anything ... ''' >>> yaml_config == config_str True
- Parameters:
msg (str) – The YAML string representation of the object.
parse (bool) – If objects should be parsed via _parse_all_objects, default True.
- Returns:
- The ordered dict representing the YAML string, with appropriate
object deserialization.
- Return type:
collections.OrderedDict
- panoptes.utils.serializers.serialize_all_objects(obj)[source]¶
Iterate the
obj
items and serialize each value.Note
See the to/from_json/yaml methods, which use this function.
- panoptes.utils.serializers.serialize_object(obj)[source]¶
Serialize the given object.
This is a custom serializer function used by
to_json
to serialize individual objects. Also called in a loop byserialize_all_objects
.>>> from panoptes.utils.serializers import serialize_object >>> from dateutil.parser import parse as date_parse >>> from astropy import units as u
>>> serialize_object(42 * u.meter) '42.0 m'
>>> party_time = date_parse('1999-12-31 11:59:59') >>> type(party_time) <class 'datetime.datetime'> >>> serialize_object(party_time) '1999-12-31T11:59:59.000'
Note
See the to/from_json/yaml methods, which use this function.
- Parameters:
obj (any) – The object to be serialized.
Returns:
- panoptes.utils.serializers.to_json(obj, filename=None, append=True, **kwargs)[source]¶
Convert a Python object to a JSON string.
Will handle datetime objects as well as astropy.unit.Quantity objects. Astropy quantities will be converted to a dict: {“value”: val, “unit”: unit}.
Examples:
>>> from panoptes.utils.serializers import to_json >>> from astropy import units as u >>> config = { "name": "Mauna Loa", "elevation": 3397 * u.meter } >>> to_json(config) '{"name": "Mauna Loa", "elevation": "3397.0 m"}' >>> to_json({"numpy_array": np.arange(10)}) '{"numpy_array": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}' >>> from panoptes.utils.time import current_time >>> to_json({"current_time": current_time()}) '{"current_time": "2019-04-08 22:19:28.402198"}'
- Parameters:
obj (object) – The object to be converted to JSON, usually a dict.
filename (str, optional) – Path to file for saving.
append (bool, optional) – Append to filename, default True. Setting False will clobber the file.
**kwargs – Keyword arguments passed to json.dumps.
- Returns:
The JSON string representation of the object.
- Return type:
str
- panoptes.utils.serializers.to_yaml(obj, **kwargs)[source]¶
Serialize a Python object to a YAML string.
This will properly serialize the following:
datetime.datetime
astropy.time.Time
astropy.units.Quantity
Examples
Also see the examples from_yaml.
>>> import os >>> os.environ['POCSTIME'] = '1999-12-31 23:49:49' >>> from panoptes.utils.time import current_time >>> t0 = current_time() >>> t0 <Time object: scale='utc' format='iso' value=1999-12-31 23:49:49.000> >>> to_yaml({'astropy time -> astropy time': t0}) "astropy time -> astropy time: '1999-12-31T23:49:49.000'\n" >>> to_yaml({'datetime -> astropy time': t0.datetime}) "datetime -> astropy time: '1999-12-31T23:49:49.000'\n" >>> # Can pass a `stream` parameter to save to file >>> with open('temp.yaml', 'w') as f: ... to_yaml({'my_object': 42}, stream=f)
- Parameters:
obj (dict) – The object to be converted to be serialized.
**kwargs – Arguments passed to ruamel.yaml.dump. See Examples.
- Returns:
The YAML string representation of the object.
- Return type:
str
panoptes.utils.time module¶
- class panoptes.utils.time.CountdownTimer(duration: int | float, name: str = '')[source]¶
Bases:
object
- expired()[source]¶
Return a boolean, telling if the timeout has expired.
- Returns:
If timer has expired.
- Return type:
- panoptes.utils.time.current_time(flatten=False, datetime=False, pretty=False)[source]¶
Convenience method to return the “current” time according to the system.
Note
If the
$POCSTIME
environment variable is set then this will return the time given in the variable. This is used for setting specific times during testing. After checking the value of POCSTIME the environment variable will also be incremented by one second so that subsequent calls to this function will generate monotonically increasing times.Operation of POCS from $POCS/bin/pocs_shell will clear the POCSTIME variable.
>>> os.environ['POCSTIME'] = '1999-12-31 23:59:59' >>> party_time = current_time(pretty=True) >>> party_time '1999-12-31 23:59:59' # Next call is one second later when using $POCSTIME. >>> y2k = current_time(pretty=True) >>> y2k '2000-01-01 00:00:00'
Note
The time returned from this function is not timezone aware. All times are UTC.
>>> from panoptes.utils.time import current_time >>> current_time() <Time object: scale='utc' format='datetime' value=2018-10-07 22:29:03.009873> >>> current_time(datetime=True) datetime.datetime(2018, 10, 7, 22, 29, 26, 594368) >>> current_time(pretty=True) '2018-10-07 22:29:51'
- Returns:
Object representing now.
- Return type:
astropy.time.Time
- panoptes.utils.time.flatten_time(t)[source]¶
Given an astropy time, flatten to have no extra chars besides integers.
>>> from astropy.time import Time >>> from panoptes.utils.time import flatten_time >>> t0 = Time('1999-12-31 23:59:59') >>> t0.isot '1999-12-31T23:59:59.000' >>> flatten_time(t0) '19991231T235959'
- Parameters:
t (astropy.time.Time) – The time to be flattened.
- Returns:
The flattened string representation of the time.
- Return type:
- panoptes.utils.time.wait_for_events(events, timeout=600, sleep_delay=<Quantity 5. s>, callback=None)[source]¶
Wait for event(s) to be set.
This method will wait for a maximum of timeout seconds for all the events to complete.
Checks every sleep_delay seconds for the events to be set.
If provided, the callback will be called every sleep_delay seconds. The callback should return True to continue waiting otherwise False to interrupt the loop and return from the function.
>>> import time >>> import threading >>> from panoptes.utils.time import wait_for_events >>> # Create some events, normally something like taking an image. >>> event0 = threading.Event() >>> event1 = threading.Event() >>> # Wait for 30 seconds but interrupt after 1 second by returning False from callback. >>> def interrupt_cb(): time.sleep(1); return False >>> # The function will return False if events are not set. >>> wait_for_events([event0, event1], timeout=30, callback=interrupt_cb) False >>> # Timeout will raise an exception. >>> wait_for_events([event0, event1], timeout=1) Traceback (most recent call last): File "<input>", line 1, in <module> File ".../panoptes-utils/src/panoptes/utils/time.py", line 254, in wait_for_events panoptes.utils.error.Timeout: Timeout: Timeout waiting for generic event >>> # Set the events in another thread for normal usage. >>> def set_events(): time.sleep(1); event0.set(); event1.set() >>> threading.Thread(target=set_events).start() >>> wait_for_events([event0, event1], timeout=30) True
- Parameters:
events (list(threading.Event)) – An Event or list of Events to wait on.
timeout (float|`astropy.units.Quantity`) – Timeout in seconds to wait for events, default 600 seconds.
sleep_delay (float, optional) – Time in seconds between event checks.
callback (callable) – A periodic callback that should return True to continue waiting or False to interrupt the loop. Can also be used for e.g. custom logging.
- Returns:
True if events were set, False otherwise.
- Return type:
- Raises:
error.Timeout – Raised if events have not all been set before timeout seconds.
panoptes.utils.utils module¶
- panoptes.utils.utils.altaz_to_radec(alt=None, az=None, location=None, obstime=None, **kwargs)[source]¶
Convert alt/az degrees to RA/Dec SkyCoord.
>>> from panoptes.utils.utils import altaz_to_radec >>> from astropy.coordinates import EarthLocation >>> from astropy import units as u >>> keck = EarthLocation.of_site('Keck Observatory') ...
>>> altaz_to_radec(alt=75, az=180, location=keck, obstime='2020-02-02T20:20:02.02') <SkyCoord (ICRS): (ra, dec) in deg (281.78..., 4.807...)>
>>> # Can use quantities or not. >>> alt = 4500 * u.arcmin >>> az = 180 * u.degree >>> altaz_to_radec(alt=alt, az=az, location=keck, obstime='2020-02-02T20:20:02.02') <SkyCoord (ICRS): (ra, dec) in deg (281.78..., 4.807...)>
>>> # Will use current time if none given. >>> altaz_to_radec(alt=35, az=90, location=keck) <SkyCoord (ICRS): (ra, dec) in deg (..., ...)>
>>> # Must pass a `location` instance. >>> altaz_to_radec() Traceback (most recent call last): File "<stdin>", line 1, in <module> ... assert location is not None AssertionError
- Parameters:
alt (astropy.units.Quantity or scalar) – Altitude.
az (astropy.units.Quantity or scalar) – Azimuth.
location (astropy.coordinates.EarthLocation, required) – A valid location.
obstime (None, optional) – Time for object, defaults to current_time
- Returns:
Coordinates corresponding to the AltAz.
- Return type:
astropy.coordinates.SkyCoord
- panoptes.utils.utils.get_free_space(directory=None)[source]¶
Return the amoung of freespace in gigabytes for given directory.
>>> from panoptes.utils.utils import get_free_space >>> get_free_space() <Quantity ... Gbyte>
>>> get_free_space(directory='/') <Quantity ... Gbyte>
- Parameters:
directory (str, optional) – Path to directory. If None defaults to root.
- Returns:
The number of gigabytes avialable in folder.
- Return type:
astropy.units.Quantity
- panoptes.utils.utils.get_quantity_value(quantity, unit=None)[source]¶
Thin-wrapper around the astropy.units.Quantity.to_value method.
If passed something other than a Quantity will simply return the original object.
>>> from astropy import units as u >>> from panoptes.utils.utils import get_quantity_value
>>> get_quantity_value(60 * u.second) 60.0
>>> # Can convert between units. >>> get_quantity_value(60 * u.minute, unit='second') 3600.0
>>> get_quantity_value(60 * u.minute, unit=u.second) 3600.0
>>> get_quantity_value(60) 60
- Parameters:
quantity (astropy.units.Quantity or scalar) – Quantity to extract numerical value from.
unit (astropy.units.Unit, optional) – unit to convert to.
- Returns:
numerical value of the Quantity after conversion to the specified unit.
- Return type:
- panoptes.utils.utils.listify(obj)[source]¶
Given an object, return a list.
Always returns a list. If obj is None, returns empty list, if obj is list, just returns obj, otherwise returns list with obj as single member.
If a dict object is passed then this function will return a list of only the values.
>>> listify(42) [42] >>> listify('foo') ['foo'] >>> listify(None) [] >>> listify(['a']) ['a'] >>> my_dict = dict(a=42, b='foo') >>> listify(my_dict) [42, 'foo'] >>> listify(my_dict.values()) [42, 'foo'] >>> listify(my_dict.keys()) ['a', 'b']
- Returns:
You guessed it.
- Return type:
Module contents¶
Changelog¶
0.2.41 - 2023-05-17¶
Fixed¶
Added tox to testing install option to fix auto PyPi builds.
0.2.39 - 2023-05-17¶
Fixed¶
Config server not properly saving entries to local file.
0.2.38 - 2022-08-16¶
Fixed¶
Config server doesn’t initially parse config file so it doesn’t need to deserialize on response.
0.2.37 - 2022-08-09¶
Added¶
The
panoptes-utils image watch <PATH>
command with default processing that will convertCR2
files toJPG
andFITS
and then plate-solve theFITS
files.GHA downloads a
CR2
file for testing.Plot directives for documentation.
Changed¶
Testing now includes
--test-solve
for plate-solving in GHA.Local tests only uses
memory
database.Rearranged some functions in the
panoptes.utils.images
namespace.
Removed¶
Unused stamp plotting functions.
Testing of config servers on GHA.
CountdownTimer.is_non_blocking predicate that wasn’t being used.
Extra serial protocol handlers.
0.2.36¶
Added¶
panoptes-utils
cli with sub-commands for converting CR2 to FITS and for plate-solving FITS.
Changed¶
Updating
pyscaffold
to4.2.3
, which allows forpyproject.toml
andtox
usage.Updating example SerialDevice notebook and fixing deps.
Fixed¶
Config server can now run on arbitrary ports better.
Removed¶
Removing unused functions: DelaySigTerm, strings_to_params.
0.2.35¶
Fixed¶
Fixes to support 420 namespaces and pyscaffold.
Don’t deserialize all strings as dates unless they actually look like a date.
0.2.34¶
Breaking¶
This release removes the Docker components from the repository, pushing some of the dependencies to
POCS
. This is an attempt to make this module slimmer and more useful.The
astrometry.net
plate solving tests have been removed since we no longer build and test against a Docker image. The plate solving will continue to transition to the self-containedplate-solver
repository.
0.2.33¶
Added¶
New
panoptes.utils.serial.device.SerialDevice
that will replace thers232.SerialData
. New class uses the threaded reader with optional callback for better asynchronous reading from a serial device. Example notebook included. #274ObservationPathInfo
allows for easy parsing of the standard PANOPTES file name scheme, as provided byPATH_MATCHER
. #282extract_metadata
pulls FITS headers into a common dictionary structure. #282Docker updates: Add
PYTHONUNBUFFERED
to docker files; Removeversion
anddeploy
keys from docker compose files. #284
Changed¶
The serial protocol handlers were moved to the
panoptes.utils.serial.handlers
namespace. #274Testing Dockerfile has
privileged
permission to get deviceloop
. #275Dockerfile: update
conda
in Dockerfile before installing environment; installpanoptes-utils
module in user-editable mode. #277Dockerfile: use
condaforge/miniforge3
as the base, which reduces image size. Push multi-stage builds for better caching. #278, #279Consistent multi-stage names in Dockerfile; added
jupyter_console
. #280Change behavior of
parse_config_directories
so thebase
entry must always exist or be missing (in which case the cwd is used). Remove themust_exist
param. #283
Fixed¶
Don’t nest storage objects for the
PanFileDB
. #280
0.2.32 - 2020-03-19¶
Added¶
panoptes.utils.rs232.find_serial_port()
can be used to look up a serial port from the vendor and product hex ids. #269
Changed¶
panoptes.utils.config.config.client.get_config()
changed sodefault
is the second parameter and function made less noisy overall. #272Updated to
Pillow>=8.1.1
for dependabot alert. #273.
Removed¶
panoptes.utils.logging
which is just replaced byfrom loguru import logger
. # 272
0.2.32 - 2020-02-28¶
Added¶
Added an
RGB
IntEnum for easy iterating and consistent array access of rgb data. #265Added
save_rgb_bg_fits
that will save a FITS files with seven extensions: combined rgb map, and then the background and rms maps for each color. #265
Changed¶
get_rgb_background
only accepts adata
argument and a filename can no longer be passed. #265Updated defaults for
get_rgb_background
. #265get_stamp_slice
hasas_slices
param added with defaultTrue
for legacy behavior. IfFalse
then just the four points are returned. #265Test coverage will skip noqa markers and pytest will run all tests. #265
Added ruamel.yaml to base dependencies instead of just
config
extras. #265
Fixed¶
The timeout parameter is now passed from get_solve_field to solve_field. #266
0.2.31 - 2020-01-31¶
Added¶
Docker musical chairs:
Add Dockerfile to be
panoptes-utils
but don’t use for testing. (#264)Generic
panoptes-utils
withpanoptes-config-server
as default command example. (#264)Move conda
environment
file intodocker
folder. (#264)
Bugs Fixed¶
parse_config_directories
no longer modifies dictionary in place. (#264)
Changed¶
Clean out the Contributing guide to point to POCS. (#264)
Removing
pillow<7
requirement. (#264)
Removed¶
Removing unused new cli experiment. (#264)
Removed all
PANDIR
andPANLOG
references. Closes #263. (#264)Removed
astroplan
from dependencies. (#264)
0.2.30 - 2021-01-14¶
Added¶
Conda environment file (@wtgee #260)
A cli script for
panoptes-utils
. This will eventually take over thepanoptes-config-server
and any other simple commands needed. For now can usepanoptes-utils tests run
for building and running tests.
Changed¶
Updated testing to use specific docker image. Updated run script for more specific options on starting testing config server. (@wtgee #260)
Different
extras
install options:config
,docs
,images
,testing
, andsocial
. (@wtgee #260)Use
loguru
for logging directly in all files, rather than needless import. (@wtgee #260)Make the
CountdownTimer.sleep()
less noisy. (#259)
Fixed¶
Plate solving extras option parsing correctly. (@wtgee #260)
Explicit imports for some of the utils. (@wtgee #260)
Removed¶
Remove Docker services except for testing. (@wtgee #260)
Removed unused files: (@wtgee #260)
Example data fetching notebook removed.
Hotspot script removed.
Data (astrometry index and IERS) download removed.
wait-for-it.sh
script removed.TheSkyX utils moving to POCS directly.
Removed
moving_average
.Removed pipeline functions for getting
image_id
andsequence_id
.
0.2.29 - 2020-10-21¶
Added¶
Added
oh-my-zsh
install file directly to ease some issues with GCP builds. (@wtgee #257)Added
source-extractor
to dependencies but with no custom config files. (@wtgee #257)Config Server:
Option to start a heartbeat or not. (@wtgee #248)
Changed¶
Reverting back to
python=3.7
for compatibility w/ GCP notebooks. (@wtgee #255)Freezing
astropy<=4.0.1
while we wait forastroplan
to get pushed. (@wtgee #255)Changed the horizon module to use numpy interpolation so we don’t need to explicitly install scipy. (@wtgee #248)
altaz_to_radec
accepts astropy quantities. (@wtgee #250)Downloaded helper script doesn’t have
python3
hardcoded. (@wtgee #250)Docker Tools (@wtgee #248):
Conda environment built from
resources/environment.yaml
. (@wtgee #252)Adds a “developer” dockerfile and compose file to install things for developers. (@wtgee #248)
Docker CMD will run ipython. (@wtgee #248)
docker-compose file will start a jupyter-lab instance. (@wtgee #248)
Fixed¶
Fixed the
oh-my-zsh
path for Docker install. (@wtgee #256)Return testing output from docker container, passint exit status. (@wtgee #256)
Removed¶
The
stars
module, which has been moved topanoptes-pipeline
. (@wtgee #251)The
metadata
module, which has been moved topanoptes-pipeline
. (@wtgee #252)Docker Tools (@wtgee #248):
Remove
source-extractor
frompanoptes-utils
and move topanoptes-pipeline
. (@wtgee #252)Remove
imagemagick
frompanoptes-utils
. This is used for adding titles to JPGs. (@wtgee #252)Don’t install a separate conda environment, just use the base to help reduce image size, complexity. (@wtgee #252)
Cleanup unused dependencies. (@wtgee @252)
Testing:
Adios travis! (@wtgee #252)
0.2.28 - 2020-09-15¶
Added¶
Add bit_depth argument to mask_saturated, no longer convert to float64 by default (@AnthonyHorton #244)
Changed¶
Single cloudbuild file for both
panoptes-base
andpanoptes-utils
. (#242)Add
astropy
channel.Remove the miniforge installer from the docker image and clean up build args. (@wtgee #245)
Changed relative to absolute imports. (@wtgee #246)
0.2.27 - 2020-09-12¶
Added¶
Config server startup controlled via envvars, incorporating
python-dotenv
(@wtgee #241):PANOPTES_CONFIG_HOST
andPANOPTES_CONFIG_PORT
PANOPTES_CONFIG_FILE
Changed¶
Config server updates (@wtgee #241):
Config server with project dir mounted can be started via
docker/docker-testing.yaml
.Config server flask instances are run on
gevent
wsgi server instead of development server.host
andport
are specified at the top-level command, e.g.panoptes-config-server --host foobar --port 9999 get
.host
andport
respect the above envvars above.Options changed from
auto-save
andignore-local
tosave-local
andload-local
.run
command adds the``config_server.running=True`` entry to the server.stop
command added that setsconfig_server.running=False
to break loop.
Testing (@wtgee #241):
All testing is started from
scripts/test-software.sh
.The
panoptes-config-server
is started as an external service, not in the pytest conf.Added a
tests/env
file that is used by the docker compose file for setting vars inside the running containers.Config server uses
tests/testing.yaml
for all testing.Testing logs are stored in
./logs
, relative to the project root outside the container.Coverage file is stored in
/var/panoptes/logs
.A
scripts/wait-for-it.sh
script added to check that config server is running properly before starting tests.
Fixed¶
scripts/setup-local-environment.sh
properly uses new base image if requested. (@wtgee #241)Docker images: fixed the created
ssh
directory for$PANUSER
. (@wtgee #240)
0.2.26 - 2020-08-21¶
This release is mostly cleanup and testing of our autobuild features.
Changed¶
Splitting the
panoptes-base
files into separate folder. (#238)Consolidate the GitHub Actions for building and publishing a release package. (#239)
Fixed¶
Fix Github Actions for building releases. (#238)
0.2.25 - 2020-08-20¶
Added¶
Google Cloud Build of Docker images.
panoptes-base
andpanoptes-utils
are built for each PR as well as on merges todevelop
andmaster
. (#237)
GitHub Actions
If a semantically tagged branch is pushed to GH, a release will automatically be generated and a package will be built and sent to PyPi. (#237)
Changed¶
Changelog fixes. (#237)
0.2.23 - 2020-08-16¶
Changed¶
Simplified docker docker images. (#227)
Consolidation of Dockerfile to support images:
panoptes-base
serves as a base image for all docker services.panoptes-utils:latest
installs editablepanoptes-utils
module from githubdevelop
branch.panoptes-utils:develop
is used for testing and can be built locally with thedocker/setup-local-environment.sh
script.Tests on GH and Travis use the
docker/setup-local-environment.sh
script for building test images.miniforge used to install a
conda
environment withconda-forge
as default channels. Forces 64bit support.Multi-arch builds are supported on
gcr.io
via thecloudbuild.yaml
file. Built withbuildx
plugin to docker. Currentlylinux/amd64
andlinux/arm64
.Extra zsh plugins in the docker image.
Properly disable auto-update of zsh.
Cleanup of entrypoint for better loading of environment.
Fixes¶
Pillow
fights.
Removes¶
Dependencies:
pyarrow
too hard to build on arm.hvplot
andholoviews
not needed in default install.
0.2.22 - 2020-07-25¶
Changed¶
Changed
dir
todirectory
in disk space check. (#226)Pass the git folder to the build context when making local docker images. (#226)
0.2.21 - 2020-07-05¶
Added¶
Added
arm64
build for Docker based offubuntu
image. (#223)
Changed¶
Docker
Changed base image to
ubuntu
. (#223)amd64
andarm64
images built by default. (#223)Ubuntu has changed
sextractor
tosource-extractor
(yay). (#223)
Config Server
Better parsing of directories entry in config server. (#222)
Make config server less noisy. (#222)
Bump PyYaml to latest for security warning. (#222)
Remove pendulum because too hard to build on arm processors. (#223)
0.2.20 - 2020-06-09¶
Moving to python 3.8.
Changed¶
Breaking Python minimum version changed to
3.8
. (#217)Running pytest locally will generate coverage report in terminal. (#218)
Lots of documentation. (#218)
Removing the environment section from the readme. (#218)
Config Server (#217)
Better logging.
Cleaning up doctests.
Removing all dynamic server items from this repo as they are not needed.
Wait for config_server to start.
Fixing starting within fixture.
Config items no longer assume any defaults for either directories or files. A config file name is always required and it should always be an absolute path. (#218)
Adding test file for config items. (#218)
panoptes-config-server
re-worked and now includesrun
,get
, andset
subcomamnds. (#221)
Testing (#218)
Log files are rotated for each testing run.
Fix env vars (mostly need to make sure the
export
option exists in theenv
file.Pytest commands moved to
setup.cfg
instead ofrun-tests.sh
Remove old markers
Setting
--strict-markers
options.Add
astrometry
marker for tests requiring solve andtheskyx
marker for running alongside TheSkyX.Coverage reports generated in xml and output in terminal.
Serializers update. (#217)
Make the parsing and serializing functions public.
Use pendulum for parsing times instead of astropy Time.
Better naming of public functions. (#218)
0.2.19 - 2020-06-04¶
Straight past 0.2.19
.
Changed¶
Removed
bin/panoptes-config-server
and created an entry_point insetup.cfg
. (#212)Removed old developer items in favor of those in
panoptes-pocs
. (#212)Consolidate docker files, consistent naming with other repos. (#210, #212)
0.2.17 - 2020-05-30¶
0.2.16
was released with an error and this is a hotfix.
Added¶
Added CR2 file testing to GitHub Actions. (#125, #205)
A
wait_for_events
generic utility, mostly pulled from POCS. (#92, #206) * Supports singlecallback
that can be used for interrupting, custom logging, etc. (#208)
Changed¶
Remove the
validate_collection
requirement from the database types, making any collection is now valid. (#204)Rearrange some of the
panoptes.utils.database
modules. (#204)
Removed¶
Remove
error.InvalidCollection
. (#204)Unused items in
conftest.py
. (#204)
0.2.15 - 2020-05-26¶
Changed¶
Convert to pyscaffold. (#198)
Proper namespace package
panoptes
.Move items to
src
folder.Fix version number.
Fix build.
Fix documentation #27.
Move all project config to
setup.cfg
.Base Docker image is run by root only.
Added a
testing
Dockerfile and cleaned uplatest
anddevelop
.
Removed¶
Breaking Removing all zmq based messaging services. (#200)
0.2.14 - 2020-05-23¶
Added¶
Add snappy decompression for parquet;
pyarrow
instead offastparquet
(#193)Password-less sudo for panoptes user on dev docker image (#193)
get_metadata
has an optional progress bar. (#194)Add
bayer.get_stamp_slice
for getting a stamp slice while respecting the superpixel. This was removed awhile ago and has been re-added and improved. (#196) * Adjusting the offsets so the center pixel is always:G2 B R G1
Bug fixes¶
Fix time-based search (#193)
Fix the build (#197) * Removed
versioneer
in favor ofsetuptools-scm
for workin version numbers. * Removed the MANIFEST.in * Added a simplepyproject.toml
.
Changed¶
Breaking Only support getting stars directly from the WCS, not the footprint. (#194) *
get_stars_from_footprint
->get_stars_from_wcs
* Better logging * Consistent column names for dtypes * Vmag bin comes from sql. * Allow for different RA/Dec column names. * Better catalog match function.sextractor
param changes. (#194)Breaking
panoptes.utils.logger
->panoptes.utils.logger
so we canfrom panoptes.utils import logger
(#197)- Breaking The
panoptes.utils.data.assets
module was removed and the Downloader
class is placed directly within thescripts/download-data.py
file. (#197)
- Breaking The
The
panopes-utils
module is not installed in editable mode in thelatest
docker image. (#197) * Slight clean up of latest.Dockerfile
0.2.13 - 2020-05-14¶
Bug fixes¶
Fix some passing of options between
get_solve_field
andsolve_field
that was leading to double parameter issues. (#189)
Changed¶
The
panoptes.utils.data
functions use static versions of the file rather than firestore. (#192)Updated development environment (#191)
get_metadata
filter the fields at the parquet level. (#194)
0.2.12 - 2020-04-29¶
Quick release to get the panoptes.utils.sources
into the package.
Bug fixes¶
panoptes.utils.sources
not included in package. (#187, #188)
Changed¶
Ability to pass credentials to underlying google client functions. (#187)
0.2.11 - 2020-04-29¶
Added¶
- Data
Added basic data access components for getting observation and image metadata. (#178, #181)
- Added a
search_observations
function for searching by various criteria. (#181) Uses anonymous credentials to connect to firestore.
Added a basic notebook demonstrating features.
- Added a
Adding
holoviews
andhvplot
as required dependencies.
Bug fixes¶
- FITS Utils fixes:
Fix docstring return types for some functions. (#173)
fpack
/funpack
andget_solve_field
were not properly overwriting FITS filesunder certain conditions when an uncompressed file of the same name was present alongside the compressed version. (#175)
Properly pass
args
andkwargs
toastropy.io.fits.getdata
. (#180)
Changed¶
- Docker
Changed developer tag from
dev
todevelop
. (#174)
- FITS Utils changes (#173):
Uncompressed file is always used for solve because we were occasionally seeing odd errors as described in dstndstn/astrometry.net#182. (#173)
- warning:
get_solve_field
willoverwrite
by default.
Better log output for solving.
Better checking for solved file at end (via
is_celestial
).Cleanup the cleanup of solve files, removing
remove_extras
option.Pass
kwargs
to underlyingwriteto
method forwrite_fits
. Needed for, e.g.overwrite
.Allow additional options to be passed to solve field functions without having to override all options. (#180)
- Changed default options in
get_solve_field
to usescale-low
andscale-high
instead ofradius
(which requires an
ra
anddec
). (#180)
- Changed default options in
Changed
bin/panoptes-dev
->bin/panoptes-develop
for naming consistency. (#175)- Data
BREAKING The
panoptes.utils.data.py
has moved into thepanoptes.utils.data
namespace with the relevant existingDownloader
class placed in theassets.py
module. (#181)Changed the
get_data
(and images and observations equivalent) toget_metadata
. (#181)
Removed¶
- FITS Utils removals (#173):
Removing unused and confusing
improve_wcs
.PanLogger class moved to POCS. (#186)
0.2.10 - 2020-04-13¶
Added¶
get_stars_from_footpr int
can accept aWCS
directly instead of just the output fromcalc_footprint()
. (#164)Ability to create different tags for the docker image. The
develop
directory is now used to create adevelop
image and is provided along withlatest
. (#165)get_rgb_backgrounds(return_separate-True)
will now return theBackground2D
objects. (#166)Added BigQuery pandas dependencies. (#168)
Added a developer image at
panoptes-utils:dev
, which is also auto-built along with thelatest
in the cloudbuild. Offers ajupyter-lab
instance along with a number of plotting modules. Can be easily started viapanoptes-dev
. (#170, #171)
Bug fixes¶
image_id_from_path
andsequence_id_from_path
can recognize a zero in thecamera_id
andNone
when no match. (#163)Fixed the bigquery client param for star lookup. (#164)
Unquote paths before id matching. (#169)
Do WCS match for all unmatched sources, not just matched sources. (#172)
Changed¶
Docker entrypoint no longer tries to activate service account if
$GOOGLE_APPLICATION_CREDENTIALS
is found. The python client libraries will recognize the env var so this means we can avoid installinggcloud
utilities just to activate. (#165)The
sources
module does not require a BigQuery client to be passed but can start it’s own. A warning is given if$GOOGLE_APPLICATION_CREDENTIALS
is not found. (#167)lookup_point_sources
updates: default vmag range expanded so less false positive matches [4,18). (#168)Removed TOC from changelog. (#170)
Sextractor param changes: (#171) * Threshold for detection changed from 3 pixels to 10 pixels. * Seeing changed from 0.7 arcsec to 15.3 arcsec. (Isn’t used.) * Removed
class_star
from sextractor results.
0.2.9 - 2020-03-27¶
Pointless version bump because of issue with PyPi.
0.2.8 - 2020-03-27¶
Thanks first-time contributer @preethi524! :tada:
Changed¶
Ability to return separate RGB backgrounds. (#162)
Increase coverage. (#161)
0.2.7 - 2020-03-22 (hotfix)¶
Added¶
Basic serialization of
Exception
. (#160)
Bug fixes¶
Add
args
andkwargs
toget_rgb_background
. (#160)
0.2.6 - 2020-03-22¶
Added¶
get_rgb_background
added to thebayer
module. (#158)getwcs
thin-wrapper added tofits
module. (#158)Added
sources
utils. (#158)
Bug fixes¶
Changed scope of test data files to
function
. (#158)
Changed¶
Docker
Change to
python:3.8-slim-buster
for base image. Onlyamd64
support for now. (#155)Simplified docker files. (#155)
Switching from Travis to GHA: (#155)
Travis builds docker image before testing.
Travis doesn’t upload coverage.
Don’t update module inside container during entrypoint.
Fixed user permissions for $HOME and $PANDIR. (#155)
The docker container only really likes it when user id
1000
is running the system.Remove GCP Cloud SQL proxy support.
Installed
sextractor
. (#158)Added
pandas
. (#158)Default
panoptes
user has passwordpanoptes
. (#158)
Removed¶
Docker (#155)
Remove anaconda
Polar alignment utils (#156)
0.2.5 - 2020-03-18¶
Added¶
Github Actions testing and coverage upload. (#145) * Log files for testing are created as an artifact.
PanLogger
helper class added. Mostly handles formatting but can also track handlers. (#145)
Bug fixes¶
Fixed top-level namespace so we can have other
panoptes
repos. (#150, fixes #137)
Changed¶
Data files for testing are copied before tests. Allows for reuse of unsolved fits file. (#144)
Fix astrometry data file directories in Docker images. (#144)
Removed¶
The docker image no longer updates
panoptes-utils
when usingrun-tests.sh
. (#145)
0.2.4 - 2020-03-11¶
Changed¶
Disallow zipped packages, which also interfere with namespace (#142)
Removed¶
photutils
dependency for rectangular apertures in theshow_stamps
method.
0.2.3 - 2020-03-08¶
Small point release to correct namespace and remove some bloat.
Changed¶
Fixed top-level namespace so we can have other
panoptes
repos. (#137)
Removed¶
Dependencies that will be deprecated soon and are causing bloat:
photutils
,scikit-image
. (#138)
Changed¶
Fixed top-level namespace so we can have other
panoptes
repos (#137, #150).
0.2.2 - 2020-03-05¶
Mostly some cleanup from the v0.2.0
release based on integrating all the changes into POCS.
Bug fixes¶
Misc bugs introduced as part of last release, including to
download-data.py
script.Custom exceptions now properly pass
kwargs
through to parent (#135).
Changed¶
New script for downloading data,
scripts/download-data.py
. This helped resolve some issues with the relative imports introduced inv0.2.0
and is cleaner. (#129)All dependencies are smashed into one “feature” in
setup.py
to makepip-tools
work well. This will fix the docker image problems introduced inv0.2.1
. (#136)
Removed¶
The
get_root_logger
and associated tests (#134).
0.2.0 - 2020-03-04¶
First big overhaul of the repository. Pulls in features that were duplicated or scattered across POCS and PIAA. Removes a lot of code that wasn’t being used or was otherwise clutter. Overhauls the logging system to use [loguru
](https://github.com/Delgan/loguru) so things are simplified. Updates to documentation.
Added¶
Config Server
See the description in the [README](README.md)
Versioneer for version strings (#123).
Read the docs config (#123).
Bug fixes¶
IERS Mirror (#65, #67)
Changed¶
Docker updates
See #68 and #75 for list.
Logging:
Switch to loguru. This simplifies our logging system. Also gives us access to the
trace
(lower thandebug
, good for hardware and other debug we don’t need to see during operation) andsuccess
(higher thaninfo
) levels, which would be nice to start implementing. (#123)Consistent use of relative imports. (#123)
Documentation updates. (#97, #119, #120, #123)
Repo cleanup. (#97, #123)
Using GitHub Actions for testing. (#100, #101)
Using pip-tools for dependency management.
0.1.0 - 2020-03-04¶
Changes and cleanup on the way to a (more) stable release. See 0.2.0
for list of changes.
0.0.8 - 2019-06-29¶
Bringing things in line with updates to POCS
for docker and panoptes-utils
use.
Added¶
Serial handlers move to panoptes-utils from POCS.
Tests and coverage.
improve_wcs
(moved from PIAA).~utils.fits.getdata
to match other fits convenience functions, allowing for fpack files.
Bug fixes¶
Serialization fixes.
Use our serialization everywhere (e.g. messaging)
Closes #panoptes/POCS/issues/818
Closes #panoptes/POCS/issues/103
Changed¶
Setup/Install:
Scripts are renamed to have
panoptes
prefix.Scripts are installed as part of setup.
Script improvements to make more robust and portable.
Docker Updates:
Don’t use anaconda.
Testing:
Overhaul of config_server use in testing.
Testing config file is separated from any regular config files.
Logging:
Silence some 3rd party logs.
0.0.7 - 2019-05-26¶
Added¶
Added bayer utilities. :camera:
Added Cloud SQL utilities. :cloud:
Changed¶
Breaking Changed namespace so no underscores, i.e.
from panoptes.utils import time
.Docker updates:
Use slim python images and not anaconda on amd64.
Adding zsh as default shell along with some customizations.
Entrypoint script properly authenticates to google cloud if possible.
Added amd64 only build scripts.
0.0.6 - 2019-04-29¶
Added¶
Docker containers created:
panoptes-base
for base OS and system packages, including astrometry.net and friends.panoptes-utils
for container containing base utilities.Script for building containers in GCR.
Consistent JSON and YAML serializers.
Configuration Server (Flask/JSON microservice).
Changed¶
Minimum Python version is 3.6
Default PanDB type is changed to
memory
.Documentation updates.
Bux fixes and code improvements.
0.0.5 - 2019-04-09¶
Added¶
Added a change log. Yay.
Changed¶
Drop
orjson
and revert tojson
for the JSON serializers.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Contributors¶
Wilfred Tyler Gee <wtylergee@gmail.com>
License¶
The MIT License (MIT)
Copyright (c) 2020 Project PANOPTES
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.