def get_solve_field(fname, replace=True, remove_extras=True, **kwargs): """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 Args: fname ({str}): Name of FITS file to be solved replace (bool, optional): Replace fname the solved file remove_extras (bool, optional): Remove the files generated by solver **kwargs ({dict}): Options to pass to `solve_field` Returns: dict: Keyword information from the solved field """ verbose = kwargs.get('verbose', False) skip_solved = kwargs.get('skip_solved', True) out_dict = {} output = None errs = None file_path, file_ext = os.path.splitext(fname) header = getheader(fname) wcs = WCS(header) # Check for solved file if skip_solved and wcs.is_celestial: if verbose: print("Solved file exists, skipping", "(pass skip_solved=False to solve again):", fname) out_dict.update(header) out_dict['solved_fits_file'] = fname return out_dict if verbose: print("Entering get_solve_field:", fname) # Set a default radius of 15 kwargs.setdefault('radius', 15) proc = solve_field(fname, **kwargs) try: output, errs = proc.communicate(timeout=kwargs.get('timeout', 30)) except subprocess.TimeoutExpired: proc.kill() raise error.Timeout("Timeout while solving") else: if verbose: print("Returncode:", proc.returncode) print("Output:", output) print("Errors:", errs) if proc.returncode == 3: raise error.SolveError('solve-field not found: {}'.format(output)) if not os.path.exists(fname.replace(file_ext, '.solved')): raise error.SolveError('File not solved') try: # Handle extra files created by astrometry.net new = fname.replace(file_ext, '.new') rdls = fname.replace(file_ext, '.rdls') axy = fname.replace(file_ext, '.axy') xyls = fname.replace(file_ext, '-indx.xyls') if replace and os.path.exists(new): # Remove converted fits os.remove(fname) # Rename solved fits to proper extension os.rename(new, fname) out_dict['solved_fits_file'] = fname else: out_dict['solved_fits_file'] = new if remove_extras: for f in [rdls, xyls, axy]: if os.path.exists(f): os.remove(f) except Exception as e: warn('Cannot remove extra files: {}'.format(e)) if errs is not None: warn("Error in solving: {}".format(errs)) else: try: out_dict.update(getheader(fname)) except OSError: if verbose: print("Can't read fits header for:", fname) return out_dict
def get_solve_field(fname, replace=True, overwrite=True, timeout=30, **kwargs): """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', '--overwrite': True} >>> solve_info = fits_utils.get_solve_field(fits_fn, **solve_kwargs, skip_solved=False) >>> assert os.path.exists('/tmp/awesome.bmp') Args: 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: dict: Keyword information from the solved field. """ skip_solved = kwargs.get('skip_solved', True) out_dict = {} output = None errs = None header = getheader(fname) wcs = WCS(header) # Check for solved file if skip_solved and wcs.is_celestial: logger.info( f"Skipping solved file (use skip_solved=False to solve again): {fname}" ) out_dict.update(header) out_dict['solved_fits_file'] = fname return out_dict # Set a default radius of 15 if overwrite: kwargs['--overwrite'] = True # Use unpacked version of file. was_compressed = False if fname.endswith('.fz'): logger.debug(f'Uncompressing {fname}') fname = funpack(fname) logger.debug(f'Using {fname} for solving') was_compressed = True logger.debug(f'Solving with: {kwargs!r}') proc = solve_field(fname, **kwargs) try: output, errs = proc.communicate(timeout=timeout) except subprocess.TimeoutExpired: proc.kill() output, errs = proc.communicate() raise error.Timeout(f'Timeout while solving: {output!r} {errs!r}') else: if proc.returncode != 0: logger.debug(f'Returncode: {proc.returncode}') for log in [output, errs]: if log and log > '': logger.debug(f'Output on {fname}: {log}') if proc.returncode == 3: raise error.SolveError(f'solve-field not found: {output}') new_fname = fname.replace('.fits', '.new') if replace: logger.debug(f'Overwriting original {fname}') os.replace(new_fname, fname) else: fname = new_fname try: header = getheader(fname) header.remove('COMMENT', ignore_missing=True, remove_all=True) header.remove('HISTORY', ignore_missing=True, remove_all=True) out_dict.update(header) except OSError: logger.warning(f"Can't read fits header for: {fname}") # Check it was solved. if WCS(header).is_celestial is False: raise error.SolveError( 'File not properly solved, no WCS header present.') # Remove WCS file. os.remove(fname.replace('.fits', '.wcs')) if was_compressed and replace: logger.debug(f'Compressing plate-solved {fname}') fname = fpack(fname) out_dict['solved_fits_file'] = fname return out_dict
def improve_wcs(fname, remove_extras=True, replace=True, timeout=30, **kwargs): """Improve the world-coordinate-system (WCS) of a FITS file. This will plate-solve an already-solved field, using a verification process that will also attempt a SIP distortion correction. Args: fname (str): Full path to FITS file. remove_extras (bool, optional): If generated files should be removed, default True. replace (bool, optional): Overwrite existing file, default True. timeout (int, optional): Timeout for the solve, default 30 seconds. **kwargs: Additional keyword args for `solve_field`. Can also include a `verbose` flag. Returns: dict: FITS headers, including solve information. Raises: error.SolveError: Description error.Timeout: Description """ verbose = kwargs.get('verbose', False) out_dict = {} output = None errs = None if verbose: print("Entering improve_wcs: {}".format(fname)) options = [ '--continue', '-t', '3', '-q', '0.01', '--no-plots', '--guess-scale', '--cpulimit', str(timeout), '--no-verify', '--crpix-center', '--match', 'none', '--corr', 'none', '--wcs', 'none', '-V', fname, ] proc = solve_field(fname, solve_opts=options, **kwargs) try: output, errs = proc.communicate(timeout=timeout) except subprocess.TimeoutExpired: proc.kill() raise error.Timeout("Timeout while solving") else: if verbose: print("Output: {}", output) print("Errors: {}", errs) if not os.path.exists(fname.replace('.fits', '.solved')): raise error.SolveError('File not solved') try: # Handle extra files created by astrometry.net new = fname.replace('.fits', '.new') rdls = fname.replace('.fits', '.rdls') axy = fname.replace('.fits', '.axy') xyls = fname.replace('.fits', '-indx.xyls') if replace and os.path.exists(new): # Remove converted fits os.remove(fname) # Rename solved fits to proper extension os.rename(new, fname) out_dict['solved_fits_file'] = fname else: out_dict['solved_fits_file'] = new if remove_extras: for f in [rdls, xyls, axy]: if os.path.exists(f): os.remove(f) except Exception as e: warn('Cannot remove extra files: {}'.format(e)) if errs is not None: warn("Error in solving: {}".format(errs)) else: try: out_dict.update(fits.getheader(fname)) except OSError: if verbose: print("Can't read fits header for {}".format(fname)) return out_dict