Skip to content

Commit

Permalink
Features of version 1.2.0 (#198)
Browse files Browse the repository at this point in the history
* feat: storing processing time into tempfile (#194)

* feat: update to handle shift data for cv layout (#173)

* feat: read mw, mp, d, mn values for sec (#192)
  • Loading branch information
baolanlequang authored Jun 11, 2024
1 parent e32473b commit 5a0e33d
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 17 deletions.
4 changes: 3 additions & 1 deletion chem_spectra/controller/transform_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,10 @@ def combine_images():
list_files.append(file_container)

params = extract_params(request)
extras = request.form.get('extras', default=None)

transform_model = TraModel(None, params=params, multiple_files=list_files)
tf_combine = transform_model.tf_combine(list_file_names=params['list_file_names'])
tf_combine = transform_model.tf_combine(list_file_names=params['list_file_names'], extraParams=extras)
if (not tf_combine):
abort(400)

Expand Down
11 changes: 10 additions & 1 deletion chem_spectra/lib/composer/ms.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,22 @@ def __gen_config(self):

def __gen_ms_spectra(self):
msspcs = []
ms_tempfile = tempfile.TemporaryFile()
for idx, dt in enumerate(self.core.datatables):
msspc = [
'##PAGE={}\n'.format(idx + 1),
'##NPOINTS={}\n'.format(dt['pts']),
TEXT_MS_DATA_TABLE,
]
msspcs = msspcs + msspc + dt['dt']
my_content = msspc + dt['dt']
file_content = ''.join(my_content)
ms_tempfile.write(file_content.encode('utf-8'))

ms_tempfile.seek(0)
lines = ms_tempfile.readlines()
decoded_lines = [line.decode('utf-8').strip() for line in lines]
msspcs = '\n'.join(decoded_lines)
ms_tempfile.close()
return msspcs

def __compose(self):
Expand Down
68 changes: 66 additions & 2 deletions chem_spectra/lib/composer/ni.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ def __gen_header_cyclic_voltammetry(self):
'$$ === CHEMSPECTRA CYCLIC VOLTAMMETRY ===\n',
]

def __gen_header_sec(self):
core_dic = self.core.dic
sec_data_key = ['MN', 'MW', 'MP', 'D']
result = []
for key in sec_data_key:
dic_value = core_dic.get(key, [])
key_str = f"##{key}={dic_value[0]}\n" if len(dic_value) > 0 else f'##{key}=\n'
result.append(key_str)
return result

def __get_xy_of_peak(self, peak):
if peak is None:
return '', ''
Expand Down Expand Up @@ -201,6 +211,8 @@ def __compose(self):
meta.extend(self.gen_headers_root())

meta.extend(self.__gen_headers_spectrum_orig())
if self.core.is_sec:
meta.extend(self.__gen_header_sec())
meta.extend(self.gen_spectrum_orig())
meta.extend(self.__gen_headers_im())
meta.extend(self.__gen_headers_integration())
Expand Down Expand Up @@ -285,6 +297,12 @@ def tf_img(self):
]
codes, verts = zip(*path_data)
marker = mpath.Path(verts, codes)

circle = mpath.Path.unit_circle()
cirle_verts = np.concatenate([circle.vertices, verts])
cirle_codes = np.concatenate([circle.codes, codes])
cut_star_marker = mpath.Path(cirle_verts, cirle_codes)

x_peaks = []
y_peaks = []
if self.core.edit_peaks:
Expand All @@ -296,6 +314,7 @@ def tf_img(self):

x_peckers = []
y_peckers = []
x_peaks_ref, y_peaks_ref = [], []
if self.core.is_cyclic_volta:
x_peaks = []
y_peaks = []
Expand Down Expand Up @@ -328,8 +347,13 @@ def tf_img(self):
x_peaks.extend([x_max_peak])
y_peaks.extend([y_max_peak])
else:
x_peaks.extend([x_max_peak, x_min_peak])
y_peaks.extend([y_max_peak, y_min_peak])
is_ref = peak.get('isRef', False) if 'isRef' in peak else False
if is_ref:
x_peaks_ref.extend([x_max_peak, x_min_peak])
y_peaks_ref.extend([y_max_peak, y_min_peak])
else:
x_peaks.extend([x_max_peak, x_min_peak])
y_peaks.extend([y_max_peak, y_min_peak])

if 'pecker' in peak and peak['pecker'] is not None:
pecker = peak['pecker']
Expand All @@ -346,6 +370,15 @@ def tf_img(self):
peak_label = 'x: {x}\ny: {y}'.format(x=x_float, y=y_float)
plt.text(x_pos, y_pos, peak_label)

# display x value of ref peak for cyclic voltammetry
for i in range(len(x_peaks_ref)):
x_pos = x_peaks_ref[i]
y_pos = y_peaks_ref[i] + h * 0.1
x_float = '{:.2e}'.format(x_pos)
y_float = '{:.2e}'.format(y_peaks_ref[i])
peak_label = 'x: {x}\ny: {y}'.format(x=x_float, y=y_float)
plt.text(x_pos, y_pos, peak_label)

plt.plot(
x_peaks,
y_peaks,
Expand All @@ -364,6 +397,15 @@ def tf_img(self):
markersize=50,
)

plt.plot(
x_peaks_ref,
y_peaks_ref,
'r',
ls='',
marker=cut_star_marker,
markersize=50,
)

# ----- PLOT integration -----
refShift, refArea = self.refShift, self.refArea
itg_h = y_max + h * 0.1
Expand Down Expand Up @@ -479,6 +521,9 @@ def tf_img(self):
plt.ylabel("Y ({})".format(self.core.label['y']), fontsize=18)
plt.locator_params(nbins=self.__plt_nbins())
plt.grid(False)

self.__generate_info_box(plt)

# Save
tf_img = tempfile.NamedTemporaryFile(suffix='.png')
plt.savefig(tf_img, format='png')
Expand All @@ -487,6 +532,25 @@ def tf_img(self):
plt.cla()
return tf_img


def __generate_info_box(self, plotlib):
if not self.core.is_sec:
return
core_dic = self.core.dic
sec_data_key = ['MN', 'MW', 'MP', 'D']
result = []
for key in sec_data_key:
dic_value = core_dic.get(key, [])
key_str = f"{key}={dic_value[0]}" if len(dic_value) > 0 else None
if key_str is not None:
result.append(key_str)

info_str = '\n'.join(result)
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
ax = plotlib.gca()
ax.text(0.05, 0.95, info_str, fontsize=14, verticalalignment='top', bbox=props, transform=ax.transAxes)


def __prepare_metadata_info_for_csv(self, csv_writer: csv.DictWriter):
csv_writer.writerow({
'Anodic E(V)': 'Measurement type',
Expand Down
16 changes: 14 additions & 2 deletions chem_spectra/lib/converter/share.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,35 @@ def parse_params(params):
peaks_str = params.get('peaks_str', None)
delta = 0.0
mass = params.get('mass', 0)
mass = mass if mass else 0
scan = params.get('scan', None)
thres = params.get('thres', None)
clear = params.get('clear', False)
clear = clear if clear else False
integration = params.get('integration')
integration = json.loads(integration) if integration else default_itg
multiplicity = params.get('multiplicity')
multiplicity = json.loads(multiplicity) if multiplicity else default_mpy
ext = params.get('ext', '')
ext = ext if ext else ''
fname = params.get('fname', '').split('.')
fname = fname[:-2] if (len(fname) > 2 and (fname[-2] in ['edit', 'peak'])) else fname[:-1]
fname = '.'.join(fname)
waveLength = params.get('waveLength')
waveLength = json.loads(waveLength) if waveLength else default_wavelength
axesUnits = params.get('axesUnits')
axesUnits = json.loads(axesUnits) if axesUnits else None

jcamp_idx = params.get('jcamp_idx', 0)
jcamp_idx = jcamp_idx if jcamp_idx else 0
axesUnitsJson = params.get('axesUnits')
axesUnitsDic = json.loads(axesUnitsJson) if axesUnitsJson else None
axesUnits = None
if axesUnitsDic != None and 'axes' in axesUnitsDic:
axes = axesUnitsDic.get('axes', [{'xUnit': '', 'yUnit': ''}])
try:
axesUnits = axes[jcamp_idx]
except:
pass

cyclicvolta = params.get('cyclic_volta')
cyclicvolta = json.loads(cyclicvolta) if cyclicvolta else None
listMaxMinPeaks = None
Expand Down
10 changes: 4 additions & 6 deletions chem_spectra/lib/shared/calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,9 @@ def cal_cyclic_volta_shift_prev_offset_at_index(cyclic_data, index=0):
return 0.0

spectra = spectra_list[index]
analysed_data = spectra['list']
arr_has_ref_value = list(filter(lambda x: ('isRef' in x and x['isRef'] == True), analysed_data))
if len(arr_has_ref_value) > 0:
shift = spectra['shift']
if 'prevValue' in shift:
offset = shift['prevValue']
hasRefPeak = spectra.get('hasRefPeak', False) == True
shift = spectra['shift']
if 'prevValue' in shift:
offset = shift['prevValue'] if hasRefPeak else -shift['prevValue']

return offset
49 changes: 47 additions & 2 deletions chem_spectra/model/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from chem_spectra.lib.composer.base import BaseComposer # noqa: F401
from chem_spectra.lib.converter.nmrium.base import NMRiumDataConverter
import matplotlib.pyplot as plt # noqa: E402
import matplotlib.path as mpath # noqa: E402
import numpy as np # noqa: E402

from chem_spectra.model.concern.property import decorate_sim_property

Expand Down Expand Up @@ -268,13 +270,45 @@ def tf_nmrium(self):
nicp = NIComposer(converter)
tf_jcamp = nicp.tf_jcamp()
return tf_jcamp

def tf_combine(self, list_file_names=None):

def __get_cyclic_volta_ref_peaks(self, curve_idx, extraParams):
x_peaks, y_peaks = [], []
try:
extras_dict = json.loads(extraParams) if extraParams else None
cyclic_volta_str = extras_dict['cyclicvolta'] if extras_dict else None
cyclic_volta = json.loads(cyclic_volta_str) if cyclic_volta_str else None
spectra_list = cyclic_volta['spectraList'] if cyclic_volta else None
spectra_extra = spectra_list[curve_idx] if spectra_list and curve_idx < len(spectra_list) else None
list_peaks = spectra_extra['list'] if spectra_extra else []
x_peaks, y_peaks = [], []
for peak in list_peaks:
min_peak, max_peak, isRef = peak['min'], peak['max'], peak['isRef']
if isRef == True:
x_peaks.extend([min_peak['x'], max_peak['x']])
y_peaks.extend([min_peak['y'], max_peak['y']])
except:
pass

return x_peaks, y_peaks

def tf_combine(self, list_file_names=None, extraParams=None):
if not self.multiple_files:
return False

path_data = [
(mpath.Path.MOVETO, (0, 5)),
(mpath.Path.LINETO, (0, 20)),
]
codes, verts = zip(*path_data)

circle = mpath.Path.unit_circle()
cirle_verts = np.concatenate([circle.vertices, verts])
cirle_codes = np.concatenate([circle.codes, codes])
cut_star_marker = mpath.Path(cirle_verts, cirle_codes)

plt.rcParams['figure.figsize'] = [16, 9]
plt.rcParams['font.size'] = 14
plt.rcParams['legend.loc'] = 'upper left'
curve_idx = self.params.get('jcamp_idx', 0)

xlabel, ylabel = '', ''
Expand Down Expand Up @@ -315,6 +349,17 @@ def tf_combine(self, list_file_names=None):
core_label_x = nicp.core.label['x']
core_label_y = nicp.core.label['y']
if nicp.core.is_cyclic_volta:
x_peaks, y_peaks = self.__get_cyclic_volta_ref_peaks(curve_idx, extraParams)

plt.plot(
x_peaks,
y_peaks,
'r',
ls='',
marker=cut_star_marker,
markersize=50,
)

if core_label_x not in dic_x_label:
xlabel_set.append(core_label_x)
dic_x_label[core_label_x] = 1
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

setup(
name='chem-spectra-app',
version='1.1.2',
version='1.2.0',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
Expand Down
Binary file added tests/fixtures/source/sec/SEC-ExFile.zip
Binary file not shown.
24 changes: 24 additions & 0 deletions tests/lib/composer/test_ni_composer.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import json
import pytest
import numpy as np
import tempfile
import zipfile
from chem_spectra.lib.converter.jcamp.base import JcampBaseConverter
from chem_spectra.lib.converter.jcamp.ni import JcampNIConverter
from chem_spectra.lib.composer.ni import NIComposer
from chem_spectra.controller.helper.file_container import FileContainer
from chem_spectra.lib.converter.bagit.base import BagItBaseConverter as BagItConveter

source_nmr = './tests/fixtures/source/1H.dx'
source_nmr_edit = './tests/fixtures/source/1H.edit.jdx'
source_ir = './tests/fixtures/source/IR.dx'
source_dir_molfile = './tests/fixtures/source/molfile/c60h57fn4.mol'
source_sec = './tests/fixtures/source/sec/SEC-ExFile.zip'

@pytest.fixture
def data_schema():
Expand Down Expand Up @@ -37,6 +41,10 @@ def molfile_data():
molfile_str = molfile.read()
return molfile_str

@pytest.fixture
def bagit_file_sec():
return source_sec

def test_init_ni_composer_failed():
with pytest.raises(Exception) as error:
_ = NIComposer(None)
Expand Down Expand Up @@ -250,3 +258,19 @@ def test_ni_composer_generate_nmrium_with_molfile(jcamp_file_1h, molfile_data):
assert 'id' in molecule_data.keys()
assert molecule_data['label'] == 'P1'
assert molecule_data['molfile'] == molfile_data

def test_ni_composer_generate_jcamp(bagit_file_sec):
with tempfile.TemporaryDirectory() as td:
with zipfile.ZipFile(bagit_file_sec, 'r') as z:
z.extractall(td)

converter = BagItConveter(td)
for jcamp_file in converter.data:
base_converter = JcampBaseConverter(jcamp_file.name)
ni_converter = JcampNIConverter(base=base_converter)
ni_composer = NIComposer(core=ni_converter)
headers = ni_composer._NIComposer__gen_header_sec()
assert headers in [
['##MN=1.287E+3\n', '##MW=1.465E+3\n', '##MP=1.345E+3\n', '##D=1.139E+0\n'],
['##MN=\n', '##MW=\n', '##MP=\n', '##D=\n']
]
Loading

0 comments on commit 5a0e33d

Please sign in to comment.