Skip to content

Commit b53381b

Browse files
authored
Merge pull request #82 from Sparks29032/morph_sequence
Add functionality for multiple morphs in one command
2 parents 6fdf12b + 63c1b8e commit b53381b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+211756
-248
lines changed

.github/workflows/main.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,3 @@ jobs:
5353
coverage run run_tests.py
5454
coverage report -m
5555
codecov
56-

diffpy/pdfmorph/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
"""
1818

1919
# obtain version information
20-
__version__ = '0.0.1'
20+
from diffpy.pdfmorph.version import __version__
2121

2222
# top-level import
2323
from diffpy.pdfmorph.pdfmorph_api import pdfmorph, morph_default_config, plot_morph
2424

25+
# key used when saving multiple morphs
26+
__save_morph_as__ = "save_morph_as"
27+
2528
# End of file

diffpy/pdfmorph/morph_helpers/transformrdftopdf.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"""
1919

2020

21+
import numpy
2122
from diffpy.pdfmorph.morphs.morph import *
2223

2324

@@ -50,12 +51,12 @@ def morph(self, x_morph, y_morph, x_target, y_target):
5051
Morph.morph(self, x_morph, y_morph, x_target, y_target)
5152
morph_baseline = self.baselineslope * self.x_morph_in
5253
target_baseline = self.baselineslope * self.x_target_in
53-
self.y_target_out = self.y_target_in / self.x_target_in + target_baseline
54-
if self.x_target_in[0] == 0:
55-
self.y_target_out[0] = 0
56-
self.y_morph_out = self.y_morph_in / self.x_morph_in + morph_baseline
57-
if self.x_morph_in[0] == 0:
58-
self.y_morph_out[0] = 0
54+
with numpy.errstate(divide='ignore', invalid='ignore'):
55+
self.y_target_out = self.y_target_in / self.x_target_in + target_baseline
56+
self.y_target_out[self.x_target_in == 0] = 0
57+
with numpy.errstate(divide='ignore', invalid='ignore'):
58+
self.y_morph_out = self.y_morph_in / self.x_morph_in + morph_baseline
59+
self.y_morph_out[self.x_target_in == 0] = 0
5960
return self.xyallout
6061

6162

diffpy/pdfmorph/pdfmorph_io.py

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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

Comments
 (0)