diff --git a/shapelets/core/analysis.py b/shapelets/core/analysis.py new file mode 100644 index 0000000..bda351c --- /dev/null +++ b/shapelets/core/analysis.py @@ -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 # +# . # +######################################################################################################################## + +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) \ No newline at end of file diff --git a/shapelets/core/run.py b/shapelets/core/run.py index 8377042..9e08ab0 100644 --- a/shapelets/core/run.py +++ b/shapelets/core/run.py @@ -15,19 +15,14 @@ # . # ######################################################################################################################## -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 ---------- @@ -35,92 +30,28 @@ def run(config_file: str, working_dir: str) -> None: * 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.") \ No newline at end of file + do_analysis(config, working_dir, image_dir, output_dir) \ No newline at end of file