Skip to content

Commit

Permalink
MNT: Updated config file handling to be more sustainable
Browse files Browse the repository at this point in the history
  • Loading branch information
mptino committed Oct 9, 2024
1 parent a2aad0a commit 369d3ef
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 87 deletions.
111 changes: 111 additions & 0 deletions shapelets/core/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
########################################################################################################################
# Copyright 2023 the authors (see AUTHORS file for full list). #
# #
# This file is part of shapelets. #
# #
# Shapelets is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General #
# Public License as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) #
# any later version. #
# #
# Shapelets is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied #
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
# details. #
# #
# You should have received a copy of the GNU Lesser General Public License along with shapelets. If not, see #
# <https://www.gnu.org/licenses/>. #
########################################################################################################################

import ast
import configparser
import os
import sys

from ..astronomy.galaxy import decompose_galaxies, get_postage_stamps, load_fits_data

from ..self_assembly.misc import process_output, read_image
from ..self_assembly.quant import defectid, orientation, rdistance

METHODS_ASTRONOMY = ['galaxy_decompose']
METHODS_SELFASSEMBLY = ['response_distance', 'orientation', 'identify_defects']

def do_analysis(config: configparser.ConfigParser, working_dir: str, image_dir: str, output_dir: str) -> None:
r"""
Main function that handles usage of the configuration file method by appropriately directing the parameters in the config file for the requested analysis.
Parameters
----------
* config : configparser.ConfigParser
* The parsed configuration file
* working_dir : str
* The absolute path (working directory) where the entry point was invoked from
* image_dir : str
* The directory containing the image (or .FITS file) for analysis
* output_dir : str
* The output directory where results from the analysis will be saved
"""
method = config.get('general', 'method')

# shapelets.astronomy submodule #
# ----------------------------- #

# galaxy decomposition: https://doi.org/10.1046/j.1365-8711.2003.05901.x
if method == 'galaxy_decompose':
fits_name = config.get('general', 'fits_name')
fits_path = os.path.join(working_dir, 'images', fits_name)

shapelet_order = config.get('galaxy_decompose', 'shapelet_order', fallback = 'default')
compression_order = config.get('galaxy_decompose', 'compression_order', fallback = 'default')

output_base_path = output_dir+fits_path[fits_path.rfind('/'):-5]
n_max = int([shapelet_order, 10][shapelet_order == 'default'])
compression_factor = int([compression_order, 25][compression_order == 'default'])

fits_data = load_fits_data(fits_path)
(galaxy_stamps, star_stamps, noiseless_data) = get_postage_stamps(fits_data, output_base_path)
decompose_galaxies(galaxy_stamps, star_stamps, noiseless_data, n_max, compression_factor, output_base_path)

sys.exit()


# shapelets.self_assembly #
# ----------------------- #

image_name = config.get('general', 'image_name')
image = read_image(image_name = image_name, image_path = image_dir)

# response distance: https://doi.org/10.1088/1361-6528/ad1df4
if method == 'response_distance':
shapelet_order = config.get('response_distance', 'shapelet_order', fallback = 'default')
if shapelet_order != 'default':
shapelet_order = ast.literal_eval(shapelet_order)

num_clusters = config.get('response_distance', 'num_clusters', fallback = 20)
num_clusters = ast.literal_eval(num_clusters)

ux = config.get('response_distance', 'ux', fallback = 'default')
uy = config.get('response_distance', 'uy', fallback = 'default')
if ux != 'default':
ux = ast.literal_eval(ux)
if uy != 'default':
uy = ast.literal_eval(uy)

rd_field = rdistance(image = image, num_clusters = num_clusters, shapelet_order = shapelet_order, ux = ux, uy = uy)

process_output(image = image, image_name = image_name, save_path = output_dir, output_from = 'response_distance', d = rd_field, num_clusters = num_clusters)

# local pattern orientation: https://doi.org/10.1088/1361-6528/ad1df4
elif method == 'orientation':
pattern_order = config.get('orientation', 'pattern_order')

mask, dilate, blended, maxval = orientation(image = image, pattern_order = pattern_order)

process_output(image = image, image_name = image_name, save_path = output_dir, output_from = 'orientation', mask = mask, dilate = dilate, orientation = blended, maxval = maxval)

# defect identification method: https://doi.org/10.1088/1361-6528/ad1df4
elif method == 'identify_defects':
pattern_order = config.get('identify_defects', 'pattern_order')

centroids, clusterMembers, defects = defectid(image = image, pattern_order = pattern_order)

process_output(image = image, image_name = image_name, save_path = output_dir, output_from = 'identify_defects', centroids = centroids, clusterMembers = clusterMembers, defects = defects)
105 changes: 18 additions & 87 deletions shapelets/core/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,112 +15,43 @@
# <https://www.gnu.org/licenses/>. #
########################################################################################################################

import ast
import configparser
import os

from ..astronomy.galaxy import *

from ..self_assembly.misc import *
from ..self_assembly.quant import *
from ..self_assembly.wavelength import *
from .analysis import do_analysis, METHODS_ASTRONOMY, METHODS_SELFASSEMBLY

def run(config_file: str, working_dir: str) -> None:
r"""
Main run function that handles input configuration file.
Main run function that parses the configuration file, ensures it exists in the provided (working) directory, and sets up output directory for post-analysis.
Parameters
----------
* config_file : str
* The name of the configuration file in working_dir
* working_dir : str
* The absolute path (working directory) where the entry point was invoked from
Notes
-----
Differentiation between submodule use is based on image_name or fits_name provided in config file. Note that this may need to be changed in the future if more astronomy functionality is added.
"""
config = configparser.ConfigParser()
config_file = os.path.join(working_dir, config_file)
if not os.path.exists(config_file):
raise RuntimeError(f"Configuration file {config_file} does not exist. Check config filename spelling and ensure that it is located in {working_dir}.")
else:
config.read(config_file)

method = config.get('general', 'method')

image_path = os.path.join(working_dir, 'images')
save_path = os.path.join(working_dir, 'output')
if not os.path.exists(image_path):
raise RuntimeError(f"Path '{image_path}' does not exist.")
if not os.path.exists(save_path):
os.mkdir(save_path)

## self_assembly submodule use ##

# parsing input image of nanostructure
if config.get('general', 'image_name', fallback=None):

# read image to be analyzed
image_name = config.get('general', 'image_name')
image = read_image(image_name = image_name, image_path = image_path)

if method == 'response_distance':
shapelet_order = config.get('response_distance', 'shapelet_order', fallback = 'default')
if shapelet_order != 'default':
shapelet_order = ast.literal_eval(shapelet_order)

num_clusters = config.get('response_distance', 'num_clusters', fallback = 20)
num_clusters = ast.literal_eval(num_clusters)

ux = config.get('response_distance', 'ux', fallback = 'default')
uy = config.get('response_distance', 'uy', fallback = 'default')
if ux != 'default':
ux = ast.literal_eval(ux)
if uy != 'default':
uy = ast.literal_eval(uy)

rd_field = rdistance(image = image, num_clusters = num_clusters, shapelet_order = shapelet_order, ux = ux, uy = uy)

process_output(image = image, image_name = image_name, save_path = save_path, output_from = 'response_distance', d = rd_field, num_clusters = num_clusters)

elif method == 'orientation':
pattern_order = config.get('orientation', 'pattern_order')

mask, dilate, blended, maxval = orientation(image = image, pattern_order = pattern_order)

process_output(image = image, image_name = image_name, save_path = save_path, output_from = 'orientation', mask = mask, dilate = dilate, orientation = blended, maxval = maxval)

elif method == 'identify_defects':
pattern_order = config.get('identify_defects', 'pattern_order')

centroids, clusterMembers, defects = defectid(image = image, pattern_order = pattern_order)

process_output(image = image, image_name = image_name, save_path = save_path, output_from = 'identify_defects', centroids = centroids, clusterMembers = clusterMembers, defects = defects)
config_file = os.path.join(working_dir, config_file)

else:
raise ValueError("method parameter {method} from configuration file not recognized by shapelets.")
if not os.path.exists(config_file):
raise RuntimeError(f"Configuration file {config_file} does not exist. Check filename spelling and ensure it is located in {working_dir}.")

## astronomy submodule use ##

# retrieving .fits path (if .fits file is provided)
elif config.get('general', 'fits_name', fallback=None):
fits_path = os.path.join(working_dir, 'images', config.get('general', 'fits_name'))
config.read(config_file)

if method == 'galaxy_decompose':
shapelet_order = config.get('galaxy_decompose', 'shapelet_order', fallback = 'default')
compression_order = config.get('galaxy_decompose', 'compression_order', fallback = 'default')
all_methods = METHODS_ASTRONOMY + METHODS_SELFASSEMBLY

output_base_path = save_path+fits_path[fits_path.rfind('/'):-5]
n_max = int([shapelet_order, 10][shapelet_order == 'default'])
compression_factor = int([compression_order, 25][compression_order == 'default'])
if config.get('general', 'method') not in all_methods:
raise RuntimeError(f"The method '{config.get('general', 'method')}' provided in configuration file '{config_file}' is not recognized by shapelets. Available options are: {', '.join(m for m in all_methods)}.")

image_dir = os.path.join(working_dir, 'images')
if not os.path.exists(image_dir):
raise RuntimeError(f"Path '{image_dir}' does not exist.")

output_dir = os.path.join(working_dir, 'output')
if not os.path.exists(output_dir):
os.mkdir(output_dir)

fits_data = load_fits_data(fits_path)
(galaxy_stamps, star_stamps, noiseless_data) = get_postage_stamps(fits_data, output_base_path)
decompose_galaxies(galaxy_stamps, star_stamps, noiseless_data, n_max, compression_factor, output_base_path)
else:
raise ValueError("method parameter {method} from configuration file not recognized by shapelets.")

else:
raise NameError("No image (from image_name) or FITS (from fits_name) listed in configuration file.")
do_analysis(config, working_dir, image_dir, output_dir)

0 comments on commit 369d3ef

Please sign in to comment.