|
| 1 | +#!/usr/bin/env python |
| 2 | +############################################################################## |
| 3 | +# |
| 4 | +# diffpy.pdfmorph by DANSE Diffraction group |
| 5 | +# Simon J. L. Billinge |
| 6 | +# (c) 2010 Trustees of the Columbia University |
| 7 | +# in the City of New York. All rights reserved. |
| 8 | +# |
| 9 | +# File coded by: |
| 10 | +# |
| 11 | +# See AUTHORS.txt for a list of people who contributed. |
| 12 | +# See LICENSE.txt for license information. |
| 13 | +# |
| 14 | +############################################################################## |
| 15 | + |
| 16 | + |
| 17 | +from __future__ import print_function |
| 18 | + |
| 19 | +import sys |
| 20 | +from pathlib import Path |
| 21 | + |
| 22 | +import numpy |
| 23 | + |
| 24 | +from diffpy.pdfmorph import __save_morph_as__ |
| 25 | +import diffpy.pdfmorph.tools as tools |
| 26 | + |
| 27 | + |
| 28 | +def single_morph_output(morph_inputs, morph_results, |
| 29 | + save_file=None, morph_file=None, xy_out=None, |
| 30 | + verbose=False, stdout_flag=False): |
| 31 | + """Helper function for printing details about a single morph. |
| 32 | + Handles both printing to terminal and printing to a file. |
| 33 | +
|
| 34 | + :param morph_inputs: Dictionary of parameters given by the user. |
| 35 | + :param morph_results: Dictionary of resulting data after morphing. |
| 36 | + :param save_file: name of file to print to. If None (defualt) print to terminal. |
| 37 | + :param morph_file: name of the morphed PDF file. Required when printing to a non-terminal file. |
| 38 | + :param xy_out: List of the form [x_morph_out, y_morph_out]. x_morph_out is a List of r values |
| 39 | + and y_morph_out is a List of gr values. |
| 40 | + :param verbose: print additional details about the morph when True (default False). |
| 41 | + :param stdout_flag: print to terminal when True (default False). |
| 42 | + """ |
| 43 | + |
| 44 | + # Input and output parameters |
| 45 | + morphs_in = "\n# Input morphing parameters:\n" |
| 46 | + morphs_in += "\n".join(f"# {key} = {morph_inputs[key]}" for key in morph_inputs.keys()) + "\n" |
| 47 | + |
| 48 | + morphs_out = "# Optimized morphing parameters:\n" |
| 49 | + morphs_out += "\n".join(f"# {key} = {morph_results[key]:.6f}" for key in morph_results.keys()) |
| 50 | + |
| 51 | + # Printing to terminal |
| 52 | + if stdout_flag: |
| 53 | + print(f"{morphs_in}\n{morphs_out}\n") |
| 54 | + |
| 55 | + # Saving to file |
| 56 | + if save_file is not None: |
| 57 | + path_name = str(Path(morph_file).resolve()) |
| 58 | + header = "# PDF created by pdfmorph\n" |
| 59 | + header += f"# from {path_name}" |
| 60 | + |
| 61 | + header_verbose = f"{morphs_in}\n{morphs_out}" |
| 62 | + |
| 63 | + if save_file != "-": |
| 64 | + with open(save_file, 'w') as outfile: |
| 65 | + # Print out a header (more if verbose) |
| 66 | + print(header, file=outfile) |
| 67 | + if verbose: |
| 68 | + print(header_verbose, file=outfile) |
| 69 | + |
| 70 | + # Print table with label |
| 71 | + print("\n# Labels: [r] [gr]", file=outfile) |
| 72 | + numpy.savetxt(outfile, numpy.transpose(xy_out)) |
| 73 | + |
| 74 | + if stdout_flag: |
| 75 | + # Indicate successful save |
| 76 | + save_message = f"# Morph saved to {save_file}\n" |
| 77 | + print(save_message) |
| 78 | + |
| 79 | + else: |
| 80 | + # Just print table with label if save is to stdout |
| 81 | + print("# Labels: [r] [gr]") |
| 82 | + numpy.savetxt(sys.stdout, numpy.transpose(xy_out)) |
| 83 | + |
| 84 | + |
| 85 | +def create_morphs_directory(save_directory): |
| 86 | + """Create a directory for saving multiple morphed PDFs. |
| 87 | +
|
| 88 | + Takes in a user-given path to a directory save_directory and create a subdirectory named Morphs. |
| 89 | + PDFmorph will save all morphs into the Morphs subdirectory while metadata about the morphs will |
| 90 | + be stored in save_directory outside Morphs. |
| 91 | +
|
| 92 | + :param save_directory: path to a directory. PDFmorph will save all generated files within |
| 93 | + this directory. |
| 94 | +
|
| 95 | + :return: Returns the absolute path to the Morph subdirectory as a string. |
| 96 | + """ |
| 97 | + # Make directory to save files in if it does not already exist |
| 98 | + Path(save_directory).mkdir(parents=True, exist_ok=True) |
| 99 | + |
| 100 | + # Morphs will be saved in the subdirectory "Morphs" |
| 101 | + morphs_subdirectory = Path(save_directory).joinpath("Morphs") |
| 102 | + morphs_subdirectory.mkdir(exist_ok=True) |
| 103 | + |
| 104 | + return str(morphs_subdirectory.resolve()) |
| 105 | + |
| 106 | + |
| 107 | +def get_multisave_names(target_list: list, save_names_file=None): |
| 108 | + """Create or import a dictionary that specifies names to save morphs as. |
| 109 | + First attempt to import names from a specified file. If names for certain morphs not found, |
| 110 | + use default naming scheme: 'Morph_with_Target_<target file name>.cgr'. |
| 111 | +
|
| 112 | + Used when saving multiple morphs. |
| 113 | +
|
| 114 | + :param target_list: List of target PDFs used for each morph. |
| 115 | + :param save_names_file: name of file to import save names dictionary from (default None). |
| 116 | +
|
| 117 | + :return: Returns a dictionary containing names to save each morph as. Keys are the target |
| 118 | + PDF file names used to produce that morph. |
| 119 | + """ |
| 120 | + |
| 121 | + # Dictionary storing save file names |
| 122 | + save_names = {} |
| 123 | + |
| 124 | + # Import names from a serial file |
| 125 | + if save_names_file is not None: |
| 126 | + # Names should be stored properly in save_names_file |
| 127 | + save_names = tools.deserialize(save_names_file) |
| 128 | + # Apply default naming scheme to missing targets |
| 129 | + for target_file in target_list: |
| 130 | + if target_file.name not in save_names.keys(): |
| 131 | + save_names.update({target_file.name: |
| 132 | + {__save_morph_as__: f"Morph_with_Target_{target_file.stem}.cgr"}}) |
| 133 | + return save_names |
| 134 | + |
| 135 | + |
| 136 | +def multiple_morph_output(morph_inputs, morph_results, target_files, |
| 137 | + field=None, field_list=None, |
| 138 | + save_directory=None, morph_file=None, target_directory=None, |
| 139 | + verbose=False, stdout_flag=False): |
| 140 | + """Helper function for printing details about a series of multiple morphs. |
| 141 | + Handles both printing to terminal and printing to a file. |
| 142 | +
|
| 143 | + :param morph_inputs: Dictionary of parameters given by the user. |
| 144 | + :param morph_results: Dictionary of resulting data after morphing. |
| 145 | + :param target_files: List of PDF files that acted as targets to morphs. |
| 146 | + :param save_directory: name of directory to save morphs in. |
| 147 | + :param field: name of field if data was sorted by a particular field. Otherwise, leave |
| 148 | + blank. |
| 149 | + :param field_list: List of field values for each target PDF. Generated by |
| 150 | + diffpy.pdfmorph.tools.field_sort(). |
| 151 | + :param morph_file: name of the morphed PDF file. Required to give summary data after saving |
| 152 | + to a directory. |
| 153 | + :param target_directory: name of the directory containing the target PDF files. Required to give |
| 154 | + summary data after saving to a directory. |
| 155 | + :param verbose: print additional summary details when True (default False). |
| 156 | + :param stdout_flag: print to terminal when True (default False). |
| 157 | + """ |
| 158 | + |
| 159 | + # Input parameters used for every morph |
| 160 | + inputs = "\n# Input morphing parameters:\n" |
| 161 | + inputs += "\n".join(f"# {key} = {morph_inputs[key]}" for key in morph_inputs.keys()) |
| 162 | + |
| 163 | + # Verbose to get output for every morph |
| 164 | + verbose_outputs = "" |
| 165 | + if verbose: |
| 166 | + # Output for every morph (information repeated in a succinct table below) |
| 167 | + for target in morph_results.keys(): |
| 168 | + output = f"\n# Target: {target}\n" |
| 169 | + output += "# Optimized morphing parameters:\n" |
| 170 | + output += "\n".join(f"# {param} = {morph_results[target][param]:.6f}" for param in morph_results[target]) |
| 171 | + verbose_outputs += f"{output}\n" |
| 172 | + |
| 173 | + # Get items we want to put in table |
| 174 | + tabulated_results = tabulate_results(morph_results) |
| 175 | + |
| 176 | + # Table labels |
| 177 | + labels = "\n# Labels: [Target]" |
| 178 | + if field is not None: |
| 179 | + labels += f" [{field}]" |
| 180 | + for param in tabulated_results.keys(): |
| 181 | + if len(tabulated_results[param]) > 0: |
| 182 | + labels += f" [{param}]" |
| 183 | + |
| 184 | + # Corresponding table |
| 185 | + table = f"{labels}\n" |
| 186 | + for idx in range(len(target_files)): |
| 187 | + row = f"{target_files[idx]}" |
| 188 | + if field_list is not None: |
| 189 | + row += f" {field_list[idx]}" |
| 190 | + for param in tabulated_results.keys(): |
| 191 | + if len(tabulated_results[param]) > idx: |
| 192 | + row += f" {tabulated_results[param][idx]:0.6f}" |
| 193 | + table += f"{row}\n" |
| 194 | + table = table[:-1] # Remove extra indent |
| 195 | + |
| 196 | + # Printing summary to terminal |
| 197 | + if stdout_flag: |
| 198 | + print(f"{inputs}\n{verbose_outputs}{table}\n") |
| 199 | + |
| 200 | + # Saving summary as a file |
| 201 | + if save_directory is not None: |
| 202 | + morph_path_name = str(Path(morph_file).resolve()) |
| 203 | + target_path_name = str(Path(target_directory).resolve()) |
| 204 | + |
| 205 | + header = "# Data generated by pdfmorph\n" |
| 206 | + header += f"# from morphing {morph_path_name}\n" |
| 207 | + header += f"# with target directory {target_path_name}" |
| 208 | + reference_table = Path(save_directory).joinpath("Morph_Reference_Table.csv") |
| 209 | + with open(reference_table, 'w') as reference: |
| 210 | + print(f"{header}\n{inputs}\n{verbose_outputs}{table}", file=reference) |
| 211 | + |
| 212 | + if stdout_flag: |
| 213 | + # Indicate successful save |
| 214 | + save_message = f"# Morphs saved in the directory {save_directory}\n" |
| 215 | + print(save_message) |
| 216 | + |
| 217 | + |
| 218 | +def tabulate_results(multiple_morph_results): |
| 219 | + """Helper function to make a data table summarizing details about the results of multiple morphs. |
| 220 | +
|
| 221 | + :param multiple_morph_results: collection of Dictionaries. Each Dictionary summarizes the results |
| 222 | + of a single morph. |
| 223 | +
|
| 224 | + :return: Returns a Dictionary tabulated_results. Keys in tabulated_results are the table's column names |
| 225 | + and each corresponding value is a List of data for that column. |
| 226 | + """ |
| 227 | + |
| 228 | + # We only care about the following parameters in our data tables |
| 229 | + relevant_parameters = ["Scale", "Smear", "Stretch", "Pearson", "Rw"] |
| 230 | + |
| 231 | + # Keys in this table represent column names and the value will be a list of column data |
| 232 | + tabulated_results = {} |
| 233 | + for param in relevant_parameters: |
| 234 | + tabulated_results.update({param: tools.get_values_from_dictionary_collection(multiple_morph_results, param)}) |
| 235 | + return tabulated_results |
0 commit comments