Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

201 move reporting to the lib #202

Merged
merged 51 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ff1980c
first summary element
andped10 Nov 15, 2024
b23a342
refinement section
andped10 Nov 15, 2024
4c3a0d7
test rename
andped10 Nov 15, 2024
779fdda
Merge branch 'develop' into 201-move-reporting-to-the-lib
andped10 Nov 15, 2024
efbc9e5
utils with counting
andped10 Nov 15, 2024
edb273e
experiments section
andped10 Nov 18, 2024
b8dbe1b
html summary
andped10 Nov 18, 2024
7506932
skeleton parameter
andped10 Nov 18, 2024
4167271
replace in template
andped10 Nov 18, 2024
02ec5ab
paramaters as table
andped10 Nov 18, 2024
2b9f8a7
html formatting
andped10 Nov 18, 2024
ec22bc2
html formatting
andped10 Nov 18, 2024
0181639
summary formatting
andped10 Nov 18, 2024
4c57068
constained parameters
andped10 Nov 18, 2024
70ca601
constraints in summary
andped10 Nov 18, 2024
837089b
summary
andped10 Nov 18, 2024
9c2f3b6
cleaning project info
andped10 Nov 18, 2024
2e45144
pdf summary
andped10 Nov 18, 2024
cdfcef1
summary update
andped10 Nov 18, 2024
489e1ab
code cleaning
andped10 Nov 18, 2024
b3d1c72
figures in summary
andped10 Nov 18, 2024
f1f95db
only figures when requested
andped10 Nov 20, 2024
2768a35
legends and eps files
andped10 Nov 20, 2024
514cd4c
back to jpg
andped10 Nov 20, 2024
f28206a
notebook for project object
andped10 Nov 20, 2024
bb4f21c
text adjustments project notebook
andped10 Nov 20, 2024
1c69d5f
link to pdf
andped10 Nov 20, 2024
60b0e14
always add project information
andped10 Nov 21, 2024
c169b8d
pr response
andped10 Nov 21, 2024
2afb8dc
reduce code complexity
andped10 Nov 21, 2024
600e4ff
trying to understand CodeFactor
andped10 Nov 21, 2024
3dfeeb9
more CodeFactor
andped10 Nov 21, 2024
497e978
trying to make CodaFactor happy
andped10 Nov 21, 2024
10d3927
CodaFactor problems
andped10 Nov 21, 2024
c49469e
CodeFactor
andped10 Nov 21, 2024
c2a69ca
codefactor
andped10 Nov 21, 2024
6c522ef
cidefactor
andped10 Nov 21, 2024
ef48a0c
codefactor
andped10 Nov 21, 2024
d6c62b3
codefactor
andped10 Nov 21, 2024
b1ed9bd
code factor
andped10 Nov 21, 2024
1542bee
codefactor
andped10 Nov 21, 2024
fa73379
code factor
andped10 Nov 21, 2024
01f8adb
CodeFactor
andped10 Nov 21, 2024
47602d9
code factor
andped10 Nov 21, 2024
2b1032b
code factor
andped10 Nov 21, 2024
fe36b1b
codefactor
andped10 Nov 21, 2024
eaf4caa
codafactor not using free
andped10 Nov 21, 2024
32333e0
code factor
andped10 Nov 21, 2024
d5ac329
code factor
andped10 Nov 21, 2024
334152e
code facoter
andped10 Nov 21, 2024
0286c6e
count functionality back to utils
andped10 Nov 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ dependencies = [
"refnx>=0.1.15",
"refl1d>=0.8.14",
"orsopy>=0.0.4",
"pint==0.23" # Only to ensure that unit is reported as dimensionless rather than empty string
"pint==0.23", # Only to ensure that unit is reported as dimensionless rather than empty string
"xhtml2pdf>=0.2.16"
]

[project.optional-dependencies]
Expand Down
4 changes: 1 addition & 3 deletions src/easyreflectometry/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,8 @@ def remove_material(self, index: int) -> None:

def _default_info(self):
return dict(
name='ExampleProject',
name='DefaultEasyReflectometryProject',
short_description='Reflectometry, 1D',
samples='None',
experiments='None',
modified=datetime.datetime.now().strftime('%d.%m.%Y %H:%M'),
)

Expand Down
3 changes: 3 additions & 0 deletions src/easyreflectometry/summary/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .summary import Summary

__all__ = [Summary]
149 changes: 149 additions & 0 deletions src/easyreflectometry/summary/html_templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
HTML_TEMPLATE = """<!DOCTYPE html>

<html>

<style>
th, td {
padding-right: 18px;
}
th {
text-align: left;
}
</style>

<body>
<table>

<!-- Summary title -->
<tr>
<td><h1>Summary</h1></td>
</tr>

<!-- Project -->
project_information_section

<tr></tr>

<!-- Sample -->
<tr>
<td><h2>Sample</h2></td>
</tr>
sample_section

<tr></tr>

<!-- Experiments -->
<tr>
<td><h2>Experiments</h2></td>
</tr>
experiments_section

<tr></tr>

<!-- Analysis -->
<tr>
<td><h2>Refinement</h2></td>
</tr>
refinement_section

</table>

figures_section

</body>
</html>"""


HTML_PROJECT_INFORMATION_TEMPLATE = """
<tr>
<td><h3>Project information</h3></td>
</tr>

<tr>
<th>Title</th>
<th>project_title</th>
</tr>
<tr>
<td>Description</td>
<td>project_description</td>
</tr>
<tr>
<td>No. of experiments</td>
<td>num_experiments</td>
</tr>
"""

HTML_PARAMETER_HEADER_TEMPLATE = """
<tr>
<th>parameter_name</th>
<th>parameter_value</th>
<th>parameter_unit</th>
<th>parameter_error</th>
</tr>
"""

HTML_PARAMETER_TEMPLATE = """
<tr>
<td>parameter_name</td>
<td>parameter_value</td>
<td>parameter_unit</td>
<td>parameter_error</td>
</tr>
"""

HTML_DATA_COLLECTION_TEMPLATE = """
<tr>
<th>Experiment datablock</th>
<th>experiment_name</th>
</tr>
<tr>
<td>Measured intensity range:</td>
<td>[range_min, range_max]</td>
</tr>
<tr>
<td>No. of data points</td>
<td>num_data_points</td>
</tr>
<tr>
<td>Resolution function</td>
<td>resolution_function</td>
</tr>

"""

HTML_REFINEMENT_TEMPLATE = """
<tr>
<td>Calculation engine</td>
<td>calculation_engine</td>
</tr>
<tr>
<td>Minimization engine</td>
<td>minimization_engine</td>
</tr>
<!-- <tr> -->
<!-- <td>Goodness-of-fit: reduced <i>&chi;</i><sup>2</sup></td> -->
<!-- <td>goodness_of_fit</td> -->
<!-- </tr> -->
<tr>
<td>No. of parameters:</td>
<td>num_total_params</td>
</tr>
<tr>
<td>No. of free parameters:</td>
<td>num_free_params</td>
</tr>
<tr>
<td>No. of fixed parameters:</td>
<td>num_fixed_params</td>
</tr>
<tr>
<td>No. of constraints</td>
<td>num_constriants</td>
</tr>
"""

HTML_FIGURES_TEMPLATE = """
<img src="path_sld_plot" alt="SLD plot" width="640" height="480">
<br>
<img src="path_fit_experiment_plot" alt="Fit experiment plot" width="640" height="480">
"""
189 changes: 189 additions & 0 deletions src/easyreflectometry/summary/summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import matplotlib.pyplot as plt
import numpy as np
from easyscience import global_object
from xhtml2pdf import pisa

from easyreflectometry import Project
from easyreflectometry.utils import count_fixed_parameters
from easyreflectometry.utils import count_free_parameters
from easyreflectometry.utils import count_parameter_user_constraints

from .html_templates import HTML_DATA_COLLECTION_TEMPLATE
from .html_templates import HTML_FIGURES_TEMPLATE
from .html_templates import HTML_PARAMETER_HEADER_TEMPLATE
from .html_templates import HTML_PARAMETER_TEMPLATE
from .html_templates import HTML_PROJECT_INFORMATION_TEMPLATE
from .html_templates import HTML_REFINEMENT_TEMPLATE
from .html_templates import HTML_TEMPLATE


class Summary:
def __init__(self, project: Project):
self._project = project

def compile_html_summary(self, figures: bool = False) -> str:
html = HTML_TEMPLATE
project_information_section = self._project_information_section()
if project_information_section == '': # no project information
project_information_section = '<td>No project information</td>'
html = html.replace('project_information_section', project_information_section)

html = html.replace('sample_section', self._sample_section())

experiments_section = self._experiments_section()
if experiments_section == '': # no experiments
experiments_section = '<td>No experiments</td>'
html = html.replace('experiments_section', experiments_section)

html = html.replace('refinement_section', self._refinement_section())

if figures:
html = html.replace('figures_section', self._figures_section())
else:
html = html.replace('figures_section', '')

return html

def save_html_summary(self, filename: str) -> None:
html = self.compile_html_summary(figures=True)
with open(filename, 'w') as f:
f.write(html)

def save_pdf_summary(self, filename: str) -> None:
html = self.compile_html_summary(figures=True)

with open(filename, 'w+b') as result_file:
pisa_status = pisa.CreatePDF(
html,
dest=result_file,
)

if pisa_status.err:
print('An error occured when generating PDF summary!')

def save_sld_plot(self, filename: str) -> None:
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)

sld = self._project.sld_data_for_model_at_index(0)
ax.plot(sld.x, sld.y)

ax.set_xlabel('z (Å)')
ax.set_ylabel('SLD (Å⁻²)')
fig.savefig(filename, dpi=300)
plt.close()

def save_fit_experiment_plot(self, filename: str) -> None:
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)

model = self._project.model_data_for_model_at_index(0)
ax.plot(model.x, np.log10(model.y), color='blue')

try:
experiment = self._project.experimental_data_for_model_at_index(0)
ax.plot(experiment.x, np.log10(experiment.y), color='red')
except IndexError:
pass

ax.set_xlabel('Q (Å⁻¹)')
ax.set_ylabel('Reflectivity')
fig.savefig(filename, dpi=300)
plt.close()

def _project_information_section(self) -> str:
html_project = ''
if self._project.created:
html_project = HTML_PROJECT_INFORMATION_TEMPLATE
name = self._project._info['name']
short_description = self._project._info['short_description']
html_project = html_project.replace('project_title', f'{name}')
html_project = html_project.replace('project_description', f'{short_description}')
html_project = html_project.replace('num_experiments', f'{len(self._project.experiments)}')
return html_project

def _sample_section(self) -> str:
html_parameters = []

html_parameter = HTML_PARAMETER_HEADER_TEMPLATE
html_parameter = html_parameter.replace('parameter_name', 'Name')
html_parameter = html_parameter.replace('parameter_value', 'Value')
html_parameter = html_parameter.replace('parameter_unit', 'Unit')
html_parameter = html_parameter.replace('parameter_error', 'Error')
html_parameters.append(html_parameter)

for parameter in self._project.parameters:
path = global_object.map.find_path(
self._project._models[self._project.current_model_index].unique_name, parameter.unique_name
)
if 0 < len(path):
name = f'{global_object.map.get_item_by_key(path[-2]).name} {global_object.map.get_item_by_key(path[-1]).name}'
else:
name = parameter.name
value = parameter.value
unit = parameter.unit
error = parameter.error

html_parameter = HTML_PARAMETER_TEMPLATE
html_parameter = html_parameter.replace('parameter_name', f'{name}')
html_parameter = html_parameter.replace('parameter_value', f'{value}')
html_parameter = html_parameter.replace('parameter_unit', f'{unit}')
html_parameter = html_parameter.replace('parameter_error', f'{error}')
html_parameters.append(html_parameter)

html_parameters_str = '\n'.join(html_parameters)

return html_parameters_str

def _experiments_section(self) -> str:
html_experiments = []

for idx, experiment in self._project.experiments.items():
experiment_name = experiment.name
num_data_points = len(experiment.x)
resolution_function = self._project.models[idx].resolution_function.as_dict()['smearing']
if resolution_function == 'PercentageFhwm':
precentage = self._project.models[idx].resolution_function.as_dict()['constant']
resolution_function = f'{resolution_function} {precentage}%'
range_min = min(experiment.y)
range_max = max(experiment.y)
range_units = 'Å⁻¹'
html_experiment = HTML_DATA_COLLECTION_TEMPLATE
html_experiment = html_experiment.replace('experiment_name', f'{experiment_name}')
html_experiment = html_experiment.replace('range_min', f'{range_min}')
html_experiment = html_experiment.replace('range_max', f'{range_max}')
html_experiment = html_experiment.replace('range_units', f'{range_units}')
html_experiment = html_experiment.replace('num_data_points', f'{num_data_points}')
html_experiment = html_experiment.replace('resolution_function', f'{resolution_function}')
html_experiments.append(html_experiment)

html_experiments_str = '\n'.join(html_experiments)

return html_experiments_str

def _refinement_section(self) -> str:
html_refinement = HTML_REFINEMENT_TEMPLATE
num_free_params = count_free_parameters(self._project)
num_fixed_params = count_fixed_parameters(self._project)
num_params = num_free_params + num_fixed_params
# goodness_of_fit = self._project.status.goodnessOfFit
# goodness_of_fit = goodness_of_fit.split(' → ')[-1]
num_constraints = count_parameter_user_constraints(self._project)

html_refinement = html_refinement.replace('calculation_engine', f'{self._project._calculator.current_interface_name}')
html_refinement = html_refinement.replace('minimization_engine', f'{self._project.minimizer.name}')
# html = html.replace('goodness_of_fit', f'{goodness_of_fit}')
html_refinement = html_refinement.replace('num_total_params', f'{num_params}')
html_refinement = html_refinement.replace('num_free_params', f'{num_free_params}')
html_refinement = html_refinement.replace('num_fixed_params', f'{num_fixed_params}')
html_refinement = html_refinement.replace('num_constriants', f'{num_constraints}')
return html_refinement

def _figures_section(self) -> None:
html_figures = HTML_FIGURES_TEMPLATE
self.save_sld_plot(self._project.path / 'sld_plot.jpg')
self.save_fit_experiment_plot(self._project.path / 'fit_experiment_plot.jpg')

html_figures = html_figures.replace('path_sld_plot', str(self._project.path / 'sld_plot.jpg'))
html_figures = html_figures.replace('path_fit_experiment_plot', str(self._project.path / 'fit_experiment_plot.jpg'))
return html_figures
26 changes: 26 additions & 0 deletions src/easyreflectometry/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,29 @@ def collect_unique_names_from_dict(structure_dict: dict, unique_names: Optional[
if key == 'unique_name':
unique_names.append(value)
return unique_names


def count_free_parameters(project) -> int:
count = 0
parameters = project.parameters
for parameter in parameters:
if parameter.free:
count = count + 1
return count
andped10 marked this conversation as resolved.
Show resolved Hide resolved


def count_fixed_parameters(project) -> int:
count = 0
parameters = project.parameters
for parameter in parameters:
if not parameter.free:
count = count + 1
return count


def count_parameter_user_constraints(project) -> int:
count = 0
parameters = project.parameters
for parameter in parameters:
count = count + len(parameter.user_constraints.keys())
return count
Loading
Loading