Skip to content

Commit

Permalink
Merge pull request #33 from RIMS-Code/excel_workup_writer
Browse files Browse the repository at this point in the history
Excel workup writer, bump version
  • Loading branch information
trappitsch authored May 9, 2022
2 parents 1e14d51 + 8792be9 commit ca54d58
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 5 deletions.
4 changes: 2 additions & 2 deletions .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[bumpversion]
current_version = 2.0.0.dev5
current_version = 2.0.0.dev6
commit = False
tag = True
tag = false
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<build>\d+))?
serialize =
{major}.{minor}.{patch}.{release}{build}
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
project = "RIMSEval"
author = "Reto Trappitsch"
copyright = f"2021, {author}"
version = "2.0.0.dev5"
version = "2.0.0.dev6"
release = version


Expand Down
14 changes: 14 additions & 0 deletions docs/dev/api/data_io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ for processing `LST` files.



.. py:currentmodule:: rimseval.data_io.excel_writer
------------
Excel Writer
------------

**************************
:func:`workup_file_writer`
**************************

.. autofunction:: workup_file_writer



.. py:currentmodule:: rimseval.data_io.export
----------------
Expand Down
41 changes: 41 additions & 0 deletions docs/pkg/special.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,47 @@ The ``rimseval`` package has some special functions incorporated,
which allow you to perform various analysis on your data
and your RIMS setup.

------------------------
Excel Workup file writer
------------------------

Sometimes,
it is useful to further process multiple evaluated spectra
in an Excel file.
You can use the
:meth:`rimseval.data_io.excel_writer.workup_file_writer`
to create a workbook to continue working on your data.
This workbook will contain integrals,
:math:`\delta`-values.

Here is an example usage for a file
that would contain zirconium data.
Note that :math:`\delta`-values would usually
be normalized to the most abundant :sup:`90`\Zr,
however, we would like to normalize here
to :sup:`94`\Zr.
This is accomplished by setting the according
normalization isotope of ``iniabu``.

The following code assumes that ``crd`` is
an instance of ``CRDFileProcessor``
and that a mass calibration has been performed
and integrals have been set for zirconium isotopes.

.. code-block:: python
from pathlib import Path
from rimseval.data_io import excel_writer
from rimseval.utilities import ini
# set Zr normalization isotope to Zr-94
ini.norm_isos = {"Zr": "Zr-94"}
# Write the excel file
my_output_file = Path("workup_file.xlsx")
excel_writer.workup_file_writer(crd, my_output_file)
-------------------
Hist. ions per shot
-------------------
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ requires = [
"numba~=0.55.1",
"numpy~=1.21.5",
"PyQt6",
"scipy"
"scipy",
"xlsxwriter"
]
requires-python=">=3.8"
classifiers = [ "License :: OSI Approved :: MIT License",]
Expand Down
2 changes: 1 addition & 1 deletion rimseval/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
]

# Package information
__version__ = "2.0.0.dev5"
__version__ = "2.0.0.dev6"

__title__ = "rimseval"
__description__ = (
Expand Down
237 changes: 237 additions & 0 deletions rimseval/data_io/excel_writer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
"""Write Excel Files from the files that we have, e.g., a workup file."""

from datetime import datetime
from pathlib import Path

from iniabu.utilities import item_formatter
import xlsxwriter
from xlsxwriter.utility import xl_rowcol_to_cell

from .. import CRDFileProcessor
from ..utilities import ini


def workup_file_writer(
crd: CRDFileProcessor, fname: Path, timestamp: bool = False
) -> None:
"""Write out an Excel workup file.
This is for the user to write out an excel workup file, which will already be
filled with the integrals of the given CRD file.
:param crd: CRD file processor file to write out.
:param fname: File name for the file to write out to.
:param timestamp: Create a column for the time stamp? Defaults to ``False``
"""
if crd.def_integrals is None:
return
else:
int_names, _ = crd.def_integrals

# format names
for it, name in enumerate(int_names):
formatted_name = item_formatter(name)
if "-" in formatted_name: # only format if it's an isotope!
int_names[it] = item_formatter(name)

# get normalizing isotope
norm_names = []
for name in int_names:
try:
ele = name.split("-")[0]
norm_names.append(ini._get_norm_iso(ele))
except IndexError: # norm isotope does not exist
norm_names.append(None)

fname = fname.with_suffix(".xlsx").absolute() # ensure correct format

wb = xlsxwriter.Workbook(str(fname))
ws = wb.add_worksheet()

# formats
fmt_title = wb.add_format({"bold": True, "font_size": 14})
fmt_hdr_0 = wb.add_format({"bold": True, "italic": True, "left": True})
fmt_hdr = wb.add_format({"bold": True, "italic": True})
fmt_std_abus = wb.add_format({"bold": True, "color": "red"})
fmt_counts_0 = wb.add_format({"num_format": "0", "left": True})
fmt_counts = wb.add_format({"num_format": "0"})
fmt_counts_unc = wb.add_format(
{"num_format": "0", "color": "gray", "valign": "left"}
)
fmt_delta_0 = wb.add_format({"num_format": "0", "left": True})
fmt_delta = wb.add_format({"num_format": "0"})
fmt_delta_unc = wb.add_format(
{"num_format": "0", "color": "gray", "valign": "left"}
)
fmt_timestamp = wb.add_format({"num_format": "YYYY-MM-DD HH:MM:SS"})

# cell widths for data
wdth_counts = 8
wdth_delta = 12
wdth_delta_unc = 6

# rows to define
abu_row = 4
num_eqn_rows = 999

# special headers and user definitions
general_headers = ["Remarks", "File Name", "# Shots"]
general_headers_widths = [20, 20, 10]
fname_col = 1
shots_col = 2

if timestamp:
general_headers.append("Timestamp")
general_headers_widths.append(18)

# write the title
ws.write(0, 0, f"Workup {datetime.today().date()}", fmt_title)

# write data header and abundances
hdr_row = abu_row + 1 # row to start header of the data in

int_col = len(general_headers) # start of integral column
delta_col = int_col + 2 * len(int_names) # start of delta column

for col, hdr in enumerate(general_headers):
ws.write(hdr_row, col, hdr, fmt_hdr)
ws.set_column(col, col, general_headers_widths[col])

for col, name in enumerate(int_names):
# write abundances
try:
abu_col = ini.iso[name].abu_rel
ws.write(abu_row, 2 * col + int_col, abu_col, fmt_std_abus)
except IndexError:
pass
# write integral header
name = iso_format_excel(name)

fmt_hdr_use = fmt_hdr_0 if col == 0 else fmt_hdr

ws.write(hdr_row, 2 * col + int_col, name, fmt_hdr_use)
ws.write(hdr_row, 2 * col + 1 + int_col, "±1σ", fmt_hdr)

# integral column width
ws.set_column(int_col, int_col + 2 * len(int_names) - 1, wdth_counts)

col = delta_col
for it, name in enumerate(int_names):
norm_iso_name = norm_names[it]
fmt_hdr_use = fmt_hdr_0 if col == delta_col else fmt_hdr
if (
norm_iso_name is not None and norm_iso_name in int_names
): # norm isotope valid
ws.write(
hdr_row,
col,
f"δ({iso_format_excel(name)}/{iso_format_excel(norm_iso_name)})",
fmt_hdr_use,
)
ws.write(hdr_row, col + 1, "±1σ", fmt_hdr)

# set width
ws.set_column(col, col, wdth_delta)
ws.set_column(col + 1, col + 1, wdth_delta_unc)

col += 2

# WRITE DATA
data_row = hdr_row + 1

# file naame
ws.write(data_row, fname_col, crd.fname.name)

# write the number of shots
ws.write(data_row, shots_col, crd.nof_shots, fmt_counts)

for col in range(data_row + 1, num_eqn_rows + data_row):
ws.write_blank(col, shots_col, None, fmt_counts)

# write the timestamp if requested
if timestamp:
ws.write(data_row, shots_col + 1, crd.timestamp, fmt_timestamp)

for col in range(data_row + 1, num_eqn_rows + data_row):
ws.write_blank(col, shots_col + 1, None, fmt_timestamp)

# write integrals
for col, dat in enumerate(crd.integrals):
fmt_counts_use = fmt_counts_0 if col == 0 else fmt_counts
ws.write(data_row, 2 * col + int_col, dat[0], fmt_counts_use)
ws.write(data_row, 2 * col + int_col + 1, dat[1], fmt_counts_unc)

for col in range(data_row + 1, num_eqn_rows + data_row): # boarder for integrals
ws.write_blank(col, int_col, None, fmt_counts_0)

# write delta equations
col = delta_col
for it, _ in enumerate(int_names):
norm_iso_name = norm_names[it]
if (
norm_iso_name is not None and norm_iso_name in int_names
): # norm isotope valid
for eq_row in range(num_eqn_rows):
# get cell values, nominators and denominators
nom_iso = xl_rowcol_to_cell(data_row + eq_row, 2 * it + int_col)
nom_iso_unc = xl_rowcol_to_cell(data_row + eq_row, 2 * it + int_col + 1)
den_iso = xl_rowcol_to_cell(
data_row + eq_row,
2 * int_names.index(norm_iso_name) + int_col,
col_abs=True,
)
den_iso_unc = xl_rowcol_to_cell(
data_row + eq_row,
2 * int_names.index(norm_iso_name) + int_col + 1,
col_abs=True,
)
# get standard abundances cell names
nom_std = xl_rowcol_to_cell(abu_row, 2 * it + int_col, row_abs=True)
den_std = xl_rowcol_to_cell(
abu_row,
2 * int_names.index(norm_iso_name) + int_col,
row_abs=True,
col_abs=True,
)

# decide if we write a boarder or not
fmt_delta_use = fmt_delta_0 if col == delta_col else fmt_delta

# write the values for the delta formula
ws.write(
data_row + eq_row,
col,
f'=IF({nom_iso}<>"",'
f'(({nom_iso}/{den_iso})/({nom_std}/{den_std})-1)*1000, "")',
fmt_delta_use,
)
# equation for uncertainty
ws.write(
data_row + eq_row,
col + 1,
f'=IF({nom_iso}<>"",'
f"1000*SQRT("
f"(({nom_iso_unc}/{den_iso})/({nom_std}/{den_std}))^2+"
f"(({nom_iso}*{den_iso_unc}/{den_iso}^2)/({nom_std}/{den_std}))^2"
f'), "")',
fmt_delta_unc,
)

# increment column
col += 2

# close the workbook
wb.close()


def iso_format_excel(iso: str) -> str:
"""Format isotope name from `iniabu` to format as written to Excel.
:param iso: Isotope, formatted according to `iniabu`, e.g., "Si-28"
:return: Excel write-out format.
"""
iso_split = iso.split("-")
if len(iso_split) != 2:
return iso
else:
return f"{iso_split[1]}{iso_split[0]}"
44 changes: 44 additions & 0 deletions tests/func/data_io/test_excel_writer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Test Excel writer function."""

from pathlib import Path

import numpy as np

from rimseval import CRDFileProcessor
import rimseval.data_io.excel_writer as exw


def test_workup_file_writer_no_integrals(crd_file):
"""Assure nothing is done if no integrals were set."""
_, _, _, fname = crd_file
crd = CRDFileProcessor(Path(fname))

# create a file for excel outptut - which won't get generated
ex_fname = Path("test.xlsx")
exw.workup_file_writer(crd, ex_fname)

assert not ex_fname.is_file()


def test_workup_file_run_through(crd_file, tmpdir):
"""Run through excel writer and assure it creates an xlsx file."""
_, _, _, fname = crd_file

crd = CRDFileProcessor(Path(fname))
crd.spectrum_full()

# set some random mass cal from 1 to 2
crd.def_mcal = np.array([[crd.tof.min(), 1.0], [crd.tof.max(), 2.0]])
crd.mass_calibration()

# now set the integrals to include everything
crd.def_integrals = (
["Fe54", "Fe56", "FeO"],
np.array([[0.9, 1.2], [1.2, 2.0], [2.0, 2.1]]),
)
crd.integrals_calc()

# create a file for excel outptut - which won't get generated
ex_fname = tmpdir.join("test.xlsx")
exw.workup_file_writer(crd, Path(ex_fname), timestamp=True)
assert ex_fname.isfile()

0 comments on commit ca54d58

Please sign in to comment.