Skip to content

Commit

Permalink
Merge pull request #92 from kif/free_dnn
Browse files Browse the repository at this point in the history
new application free_dnn
  • Loading branch information
kif authored Sep 11, 2024
2 parents 8ce4458 + 427054b commit ab7e205
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 9 deletions.
6 changes: 6 additions & 0 deletions doc/source/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Change-log
##########

FreeSAS v2024.9.0 11/09/2021
============================
- Rg+Dmax inferance from a Dense Neural Network (free_dnn)
- automatic packaging using github-actions
- Supports Python 3.7-3.12, including numpy2

FreeSAS v0.9.0 16/07/2021
=========================
- Command line tools upgrade
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ classifiers = [
dependencies = [
'numpy',
'scipy',
'matplotlib'
'matplotlib',
'silx'
]
[build-system]
build-backend = 'mesonpy'
Expand All @@ -49,7 +50,8 @@ requires = [
"numpy; platform_machine != 'ppc64le'",
'pyproject-metadata>=0.5.0',
'tomli>=1.0.0',
'scipy'
'scipy',
'silx'
]

[project.urls]
Expand All @@ -67,6 +69,7 @@ cormapy = 'freesas.app.cormap:main'
supycomb = 'freesas.app.supycomb:main'
free_bift = 'freesas.app.bift:main'
extract_ascii = 'freesas.app.extract_ascii:main'
free_dnn = 'freesas.app.dnn:main'

[project.gui-scripts]
freesas = 'freesas.app.plot_sas:main'
Expand Down
74 changes: 74 additions & 0 deletions src/freesas/app/dnn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/python3
# coding: utf-8
#
# Project: freesas
# https://github.com/kif/freesas
#
# Copyright (C) 2020 European Synchrotron Radiation Facility, Grenoble, France
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

__author__ = ["Jérôme Kieffer", "Mayank Yadav"]
__license__ = "MIT"
__copyright__ = "2024, ESRF"
__date__ = "11/09/2024"

import sys
import logging
from freesas.sas_argparser import SASParser
from freesas.fitting import run_dnn

logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger("free_dnn")

if sys.version_info < (3, 6):
logger.error("This code uses F-strings and requires Python 3.6+")


def build_parser() -> SASParser:
"""Build parser for input and return list of files.
:return: parser
"""
description = (
"Assess the radius of gyration (Rg) and the diameter of the particle (Dmax) using a Dense Neural-Network"
" for a set of scattering curves"
)
epilog = """free_dnn is an alternative implementation of
`gnnom` (https://doi.org/10.1016/j.str.2022.03.011).
As this tool used a different training set, some results are likely to differ.
"""
parser = SASParser(prog="free_gpa", description=description, epilog=epilog)
file_help_text = "dat files of the scattering curves"
parser.add_file_argument(help_text=file_help_text)
parser.add_output_filename_argument()
parser.add_output_data_format("native", "csv", "ssf", default="native")
parser.add_q_unit_argument()

return parser



def main() -> None:
"""Entry point for free_gpa app"""
parser = build_parser()
run_dnn(parser=parser, logger=logger)


if __name__ == "__main__":
main()
3 changes: 2 additions & 1 deletion src/freesas/app/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ py.install_sources([
'cormap.py',
'extract_ascii.py',
'plot_sas.py',
'supycomb.py'
'supycomb.py',
'dnn.py'
],
pure: false, # Will be installed next to binaries
subdir: 'freesas/app' # Folder relative to site-packages to install to
Expand Down
122 changes: 120 additions & 2 deletions src/freesas/fitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "29/11/2023"
__date__ = "11/09/2024"
__status__ = "development"
__docformat__ = "restructuredtext"

Expand All @@ -26,7 +26,7 @@
load_scattering_data,
convert_inverse_angstrom_to_nanometer,
)
from .sas_argparser import GuinierParser
from .sas_argparser import GuinierParser, SASParser


def set_logging_level(verbose_flag: int) -> None:
Expand Down Expand Up @@ -110,6 +110,28 @@ def get_guinier_header(
else:
return ""

def get_dnn_header(
linesep: str, output_format: Optional[str] = None
) -> str:
"""Return appropriate header line for selected output format
:param output_format: output format from string parser
:param linesep: correct linesep for chosen destination
:return: a one-line string"""
# pylint: disable=R1705
if output_format == "csv":
return (
",".join(
(
"File",
"Rg",
"Dmax",
)
)
+ linesep
)
else:
return ""


def rg_result_to_output_line(
rg_result: RG_RESULT,
Expand Down Expand Up @@ -161,6 +183,44 @@ def rg_result_to_output_line(
else:
return f"{afile} {rg_result}{linesep}"

def dnn_result_to_output_line(
dnn_result: tuple,
afile: Path,
linesep: str,
output_format: Optional[str] = None,
) -> str:
"""Return result line formatted according to selected output format
:param dnn_result: Result of an dnn inference, 2 tuple
:param afile: The name of the file that was processed
:param output_format: The chosen output format
:param linesep: correct linesep for chosen destination
:return: a one-line string including linesep"""
# pylint: disable=R1705
if output_format == "csv":
return (
",".join(
[
f"{afile}",
f"{dnn_result[0]:6.4f}",
f"{dnn_result[1]:6.4f}",
]
)
+ linesep
)
elif output_format == "ssv":
return (
" ".join(
[
f"{dnn_result[0]:6.4f}",
f"{dnn_result[1]:6.4f}",
f"{afile}",
]
)
+ linesep
)
else:
return f"{afile} {dnn_result[0]} {dnn_result[1]}{linesep}"


def run_guinier_fit(
fit_function: Callable[[ndarray], RG_RESULT],
Expand Down Expand Up @@ -220,3 +280,61 @@ def run_guinier_fit(
)
output_destination.write(res)
output_destination.flush()
def run_dnn(
parser: SASParser,
logger: logging.Logger,
) -> None:
"""
reads in the data, infer the DNN and creates the result
:param parser: a function that returns the output of argparse.parse()
:param logger: a Logger
"""
from .dnn import Rg_Dmax # heavy import

args = parser.parse_args()
set_logging_level(args.verbose)
files = collect_files(args.file)
logger.debug("%s input files", len(files))

with get_output_destination(args.output) as output_destination:
linesep = get_linesep(output_destination)

output_destination.write(
get_dnn_header(
linesep,
args.format,
)
)

for afile in files:
logger.info("Processing %s", afile)
try:
data = load_scattering_data(afile)
except OSError:
logger.error("Unable to read file %s", afile)
except ValueError:
logger.error("Unable to parse file %s", afile)
else:
if args.unit == "Å":
data = convert_inverse_angstrom_to_nanometer(data)
q, I = data.T[:2]
try:
dnn_result = Rg_Dmax(q, I)
except (
InsufficientDataError,
NoGuinierRegionError,
ValueError,
IndexError,
) as err:
sys.stderr.write(
f"{afile}, {err.__class__.__name__}: {err} {os.linesep}"
)
else:
res = dnn_result_to_output_line(
dnn_result,
afile,
linesep,
args.format,
)
output_destination.write(res)
output_destination.flush()
8 changes: 4 additions & 4 deletions version.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
__authors__ = ["Jérôme Kieffer"]
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "04/12/2023"
__date__ = "11/09/2024"
__status__ = "production"
__docformat__ = 'restructuredtext'
__all__ = ["date", "version_info", "strictversion", "hexversion", "debianversion",
Expand All @@ -68,11 +68,11 @@
"alpha": "a",
"beta": "b",
"candidate": "rc"}
MAJOR = 0
MAJOR = 2024
MINOR = 9
MICRO = 9
MICRO = 0
RELEV = "dev" # <16
SERIAL = 1 # <16
SERIAL = 0 # <16

date = __date__

Expand Down

0 comments on commit ab7e205

Please sign in to comment.