From a7d22329e2984e0218440ca8bc79bf294d27c9b4 Mon Sep 17 00:00:00 2001 From: Lan Le Date: Tue, 18 Jun 2024 09:04:10 +0200 Subject: [PATCH 1/4] feat: show peak info, move integrals value to bottom (#181) --- chem_spectra/lib/composer/ni.py | 221 ++++++++++++++---- .../jcamp/test_jcamp_ni_converter.py | 6 + 2 files changed, 182 insertions(+), 45 deletions(-) diff --git a/chem_spectra/lib/composer/ni.py b/chem_spectra/lib/composer/ni.py index 59edd683..80269c0a 100644 --- a/chem_spectra/lib/composer/ni.py +++ b/chem_spectra/lib/composer/ni.py @@ -10,6 +10,7 @@ import re # noqa: E402 import numpy as np # noqa: E402 from matplotlib import ticker # noqa: E402 +from matplotlib.patches import FancyArrowPatch import csv from chem_spectra.lib.composer.base import ( # noqa: E402, F401 @@ -275,6 +276,7 @@ def __fakto(self): def tf_img(self): plt.rcParams['figure.figsize'] = [16, 9] + plt.rcParams['figure.dpi'] = 200 plt.rcParams['font.size'] = 14 # PLOT data @@ -285,10 +287,10 @@ def tf_img(self): plt.xlim(xlim_left, xlim_right) y_max, y_min = np.max(self.core.ys), np.min(self.core.ys) h = y_max - y_min - plt.ylim( - y_min - h * 0.2, - y_max + h * 0.2, - ) + w = x_max - x_min + y_boundary_min = y_min - h * 0.2 + y_boundary_max = y_max + h * 0.5 + # PLOT peaks faktor = self.__fakto() path_data = [ @@ -406,9 +408,8 @@ def tf_img(self): markersize=50, ) - # ----- PLOT integration ----- + # ----- Calculate integration ----- refShift, refArea = self.refShift, self.refArea - itg_h = y_max + h * 0.1 if (len(self.all_itgs) == 0 and len(self.core.itg_table) > 0 and not self.core.params['integration'].get('edited') and ('originStack' not in self.core.params['integration'])): core_itg_table = self.core.itg_table[0] itg_table = core_itg_table.split('\n') @@ -421,41 +422,9 @@ def tf_img(self): xUStr = split_itg[1].strip() areaStr = split_itg[2].strip() self.all_itgs.append({'xL': float(xLStr), 'xU': float(xUStr), 'area': float(areaStr)}) # noqa: E501 - for itg in self.all_itgs: - # integration marker - xL, xU, area = itg['xL'] - refShift, itg['xU'] - refShift, itg['area'] * refArea # noqa: E501 - # integration curve - ks = calc_ks(self.core.ys, y_max, h) - iL, iU = get_curve_endpoint(self.core.xs, self.core.ys, xL, xU) - ref = ks[iL] - cxs = self.core.xs[iL:iU] - - if self.core.is_hplc_uv_vis: - # fill area under curve - cys = self.core.ys[iL:iU] - slope = cal_slope(cxs[0], cys[0], cxs[len(cxs)-1], cys[len(cys)-1]) # noqa: E501 - last_y = cys[0] - last_x = cxs[0] - aucys = [last_y] - for i in range(1, len(cys)): - curr_x = cxs[i] - curr_y = slope*(curr_x-last_x) + last_y - aucys.append(curr_y) - last_x = curr_x - last_y = curr_y - plt.fill_between(cxs, y1=cys, y2=aucys, alpha=0.2, color='#FF0000') # noqa: E501 - else: - # display integration - plt.plot([xL, xU], [itg_h, itg_h], color='#228B22') - plt.plot([xL, xL], [itg_h + h * 0.01, itg_h - h * 0.01], color='#228B22') # noqa: E501 - plt.plot([xU, xU], [itg_h + h * 0.01, itg_h - h * 0.01], color='#228B22') # noqa: E501 - plt.text((xL + xU) / 2, itg_h + h * 0.015, '{:0.2f}'.format(area), color='#228B22', ha='center', size=12) # noqa: E501 - cys = (ks[iL:iU] - ref) * 1.5 + (y_max - h * 0.4) - # if self.core.is_uv_vis: - # cys = (ref - ks[iL:iU]) * 0.5 + (y_max - h * 0.4) - plt.plot(cxs, cys, color='#228B22') + - # ----- PLOT multiplicity ----- + # ----- Calculate multiplicity ----- if (len(self.mpys) == 0 and len(self.core.mpy_itg_table) > 0 and not self.core.params['integration'].get('edited') and ('originStack' not in self.core.params['integration'])): core_mpy_pks_table = self.core.mpy_pks_table[0] mpy_pks_table = core_mpy_pks_table.split('\n') @@ -493,17 +462,27 @@ def tf_img(self): mpy_item['mpyType'] = typeStr mpy_item['peaks'] = tmp_dic_mpy_peaks[idxStr] self.mpys.append(mpy_item) - mpy_h = y_min - h * 0.08 + + # ----- PLOT integration ----- + itg_h = y_max + h * 0.6 + itg_h = itg_h + itg_h * 0.1 + itg_value_position_y = y_min - h * 0.25 + if (len(self.mpys) == 0): + itg_value_position_y = y_min - h * 0.05 + y_boundary_max = self.__draw_integrals(plt, refShift, refArea, y_max, h, itg_value_position_y, itg_h) + y_boundary_min = itg_value_position_y - h * 0.1 + + # ----- PLOT multiplicity ----- + mpy_h = y_min - h * 0.03 for mpy in self.mpys: xL, xU, area, typ, peaks = mpy['xExtent']['xL'] - refShift, mpy['xExtent']['xU'] - refShift, mpy['area'] * refArea, mpy['mpyType'], mpy['peaks'] # noqa: E501 plt.plot([xL, xU], [mpy_h, mpy_h], color='#DA70D6') plt.plot([xL, xL], [mpy_h + h * 0.01, mpy_h - h * 0.01], color='#DA70D6') # noqa: E501 plt.plot([xU, xU], [mpy_h + h * 0.01, mpy_h - h * 0.01], color='#DA70D6') # noqa: E501 - plt.text((xL + xU) / 2, mpy_h - h * 0.1, '({})'.format(typ), color='#DA70D6', ha='center', size=12) # noqa: E501 - plt.text((xL + xU) / 2, mpy_h - h * 0.06, '{:0.3f}'.format(calc_mpy_center(mpy['peaks'], refShift, mpy['mpyType'])), color='#DA70D6', ha='center', size=12) # noqa: E501 + plt.text((xL + xU) / 2, mpy_h - h * 0.01, '{:0.3f} ({})'.format(calc_mpy_center(mpy['peaks'], refShift, mpy['mpyType']), typ), color='#DA70D6', size=7, rotation=90., ha='right', va='top', rotation_mode='anchor') # noqa: E501 for p in peaks: x = p['x'] - plt.plot([x - refShift, x - refShift], [mpy_h, mpy_h + h * 0.05], color='#DA70D6') # noqa: E501 + plt.plot([x - refShift, x - refShift], [mpy_h, mpy_h + h * 0.02], color='#DA70D6') # noqa: E501 # PLOT label if (self.core.is_xrd): @@ -512,11 +491,15 @@ def tf_img(self): plt.xlabel((label), fontsize=18) elif (self.core.is_cyclic_volta): plt.xlabel("{}".format(self.core.label['x']), fontsize=18) + elif (self.core.non_nmr == False): + plt.xlabel("Chemical shift ({})".format(self.core.label['x'].lower()), fontsize=18) else: plt.xlabel("X ({})".format(self.core.label['x']), fontsize=18) if (self.core.is_cyclic_volta): plt.ylabel("{}".format(self.core.label['y']), fontsize=18) + elif (self.core.non_nmr == False): + plt.ylabel("Intensity ({})".format(self.core.label['y'].lower()), fontsize=18) else: plt.ylabel("Y ({})".format(self.core.label['y']), fontsize=18) plt.locator_params(nbins=self.__plt_nbins()) @@ -524,6 +507,14 @@ def tf_img(self): self.__generate_info_box(plt) + y_boundary_max = self.__draw_peaks(plt, x_peaks, y_peaks, h, w, y_boundary_max*1.5) + + + plt.ylim( + y_boundary_min, + y_boundary_max, + ) + # Save tf_img = tempfile.NamedTemporaryFile(suffix='.png') plt.savefig(tf_img, format='png') @@ -531,6 +522,147 @@ def tf_img(self): plt.clf() plt.cla() return tf_img + + + def __draw_integrals(self, plt, refShift, refArea, y_max, h, itg_value_position_y, itg_h): + y_boundary_max = y_max + for itg in self.all_itgs: + # integration marker + xL, xU, area = itg['xL'] - refShift, itg['xU'] - refShift, itg['area'] * refArea # noqa: E501 + # integration curve + ks = calc_ks(self.core.ys, y_max, h) + iL, iU = get_curve_endpoint(self.core.xs, self.core.ys, xL, xU) + ref = ks[iL] + cxs = self.core.xs[iL:iU] + + if self.core.is_hplc_uv_vis: + # fill area under curve + cys = self.core.ys[iL:iU] + slope = cal_slope(cxs[0], cys[0], cxs[len(cxs)-1], cys[len(cys)-1]) # noqa: E501 + last_y = cys[0] + last_x = cxs[0] + aucys = [last_y] + for i in range(1, len(cys)): + curr_x = cxs[i] + curr_y = slope*(curr_x-last_x) + last_y + aucys.append(curr_y) + last_x = curr_x + last_y = curr_y + plt.fill_between(cxs, y1=cys, y2=aucys, alpha=0.2, color='#FF0000') # noqa: E501 + else: + # display integration + plt.plot([xL, xU], [itg_value_position_y, itg_value_position_y], color='#228B22') + plt.plot([xL, xL], [itg_value_position_y + h * 0.01, itg_value_position_y - h * 0.01], color='#228B22') # noqa: E501 + plt.plot([xU, xU], [itg_value_position_y + h * 0.01, itg_value_position_y - h * 0.01], color='#228B22') # noqa: E501 + plt.text((xL + xU) / 2, itg_value_position_y - h * 0.01, '{:0.2f}'.format(area), color='#228B22', ha='right', rotation_mode='anchor', size=7, rotation=90.) # noqa: E501 + cys = (ks[iL:iU] - ref) * 1.5 + h * 0.15 + plt.plot(cxs, cys, color='#228B22') + try: + cys_max = np.max(cys) + y_boundary_max = max(y_boundary_max, cys_max + h*0.1) + except: + pass + + return y_boundary_max + + + def __draw_peaks(self, plt, x_peaks, y_peaks, h, w, y_boundary_max): + if self.core.non_nmr == True or len(x_peaks) == 0: + return y_boundary_max + + params = self.core.params + if params['ref_name'] is None or params['peaks_str'] is None or params['peaks_str'] == '': + return y_boundary_max + + ax = plt.gca() + differences = np.diff(x_peaks) + grouping_threshold = np.mean(differences) + groups_x = [] + groups_y = [] + current_group_x = [x_peaks[0]] + current_group_y = [y_peaks[0]] + + for i in range(1, len(x_peaks)): + if (x_peaks[i] - current_group_x[-1] < grouping_threshold): + current_group_x.append(x_peaks[i]) + current_group_y.append(y_peaks[i]) + else: + groups_x.append(current_group_x) + current_group_x = [x_peaks[i]] + groups_y.append(current_group_y) + current_group_y = [y_peaks[i]] + + groups_x.append(current_group_x) + groups_y.append(current_group_y) + max_values = [np.max(items) for items in groups_y] + x_boundary_min, x_boundary_max = np.min(x_peaks), np.max(x_peaks) + + highest_peak_anotation = 0 + + for i in range(len(groups_x)): + mygroup_x, mygroup_y = groups_x[i], groups_y[i] + len_group_x = len(mygroup_x) + if len_group_x > 1: + max_current_group = max_values[i] + h * 0.25 + if (i > 0): + prev_max_group = max_values[i-1] + h * 0.25 + my_gap = abs(max_current_group - prev_max_group) + if my_gap < h*0.1: + max_current_group = max_current_group + h * 0.45 + middle_idx = int(len_group_x/2) + gap_value = np.mean(mygroup_x) + x_text = 0 + for j in range(len_group_x): + x_pos = mygroup_x[j] + y_pos = mygroup_y[j] + h * 0.5 + x_float = '{:.2f}'.format(x_pos) + peak_label = '{x}'.format(x=x_float) + if j >= middle_idx: + x_text = -(w * 0.01) * (middle_idx - j) + elif j < middle_idx: + x_text = w * 0.01 * (j-middle_idx) + + ax.add_patch(FancyArrowPatch((x_pos, max_current_group), (x_pos, max_current_group + h * 0.05), linewidth=0.1)) + ax.add_patch(FancyArrowPatch((x_pos, max_current_group + h * 0.05), (gap_value + x_text, max_current_group + h * 0.11), linewidth=0.1)) + + x_boundary_min = min(gap_value + x_text, x_boundary_min) + x_boundary_max = max(gap_value + x_text, x_boundary_max) + + ax.annotate(peak_label, + xy=(gap_value + x_text, max_current_group + h * 0.11), xycoords='data', + xytext=(0, 12), textcoords='offset points', + arrowprops=dict(arrowstyle="-", linewidth=0.2), + rotation=90, size=6) + + highest_peak_anotation = max(highest_peak_anotation, max_current_group + h * 0.25) + else: + x_pos = mygroup_x[0] + y_pos = mygroup_y[0] + h * 0.18 + x_float = '{:.2f}'.format(x_pos) + peak_label = '{x}'.format(x=x_float) + ax.annotate(peak_label, + xy=(x_pos, y_pos), xycoords='data', + xytext=(0, 20), textcoords='offset points', + arrowprops=dict(arrowstyle="-", linewidth=0.2), + rotation=90, size=6) + + highest_peak_anotation = max(highest_peak_anotation, y_pos + h * 0.15) + + x_boundary_min = x_boundary_min - w * 0.01 + x_boundary_max = x_boundary_max + w * 0.02 + if self.core.ncl == '13C': + x_boundary_min = min(x_boundary_min, -10.0) + x_boundary_max = max(x_boundary_max, 195.0) + elif self.core.ncl == '1H': + x_boundary_min = min(x_boundary_min, -0.2) + x_boundary_max = max(x_boundary_max, 9.0) + plt.xlim( + x_boundary_max, + x_boundary_min, + ) + y_boundary_max = min(y_boundary_max, highest_peak_anotation) + + return y_boundary_max def __generate_info_box(self, plotlib): @@ -576,7 +708,6 @@ def __prepare_metadata_info_for_csv(self, csv_writer: csv.DictWriter): }) csv_writer.writerow({ }) - def tf_csv(self): if self.core.is_cyclic_volta == False: diff --git a/tests/lib/converter/jcamp/test_jcamp_ni_converter.py b/tests/lib/converter/jcamp/test_jcamp_ni_converter.py index d0595592..db80fdc6 100644 --- a/tests/lib/converter/jcamp/test_jcamp_ni_converter.py +++ b/tests/lib/converter/jcamp/test_jcamp_ni_converter.py @@ -20,4 +20,10 @@ def test_init_jcamp_ni_success(jcamp_file_1h): assert ni_converter is not None assert ni_converter.base == base_converter + +def test_init_jcamp_ni_nmr_label(jcamp_file_1h): + base_converter = JcampBaseConverter(jcamp_file_1h) + ni_converter = JcampNIConverter(base=base_converter) + + assert ni_converter.label == {'x': 'PPM', 'y': 'ARBITRARY'} \ No newline at end of file From 1f2f42c5e581bdfeabeb163b0ea8824ad726f8ba Mon Sep 17 00:00:00 2001 From: Lan Le Date: Tue, 18 Jun 2024 14:19:54 +0200 Subject: [PATCH 2/4] docs: update automatic startup info (#202) --- INSTALL.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index 928410ca..a3694953 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -149,6 +149,26 @@ logger.error('message to log') ``` Note: You need to use function as the same as your logger level, which named as lowercased of level's name, to write your log message to the logs file + +### 2.3. Automatic startup in crontab + +To make sure ChemSpectra is started on reboot you can use this BASH script in your root crontab (if required, adapt Chemotion ELN username and home directory): + +```sh +#!/bin/bash + +sudo -H -u production bash -c "cd /home/production/chem-spectra-app && \ + source /home/production/anaconda3/bin/activate chem-spectra && \ + gunicorn -w 4 -b 0.0.0.0:3007 server:app --daemon" + +# Remember to modify path according to your installation +docker run --detach --name msconvert_docker \ + --rm -it \ + -e WINEDEBUG=-all \ + -v /home/production/chem-spectra-app/chem_spectra/tmp:/data chambm/pwiz-skyline-i-agree-to-the-vendor-licenses \ + bash +``` + ## 3. Run test ``` From 1a3a42ffc87df3c3dee17b7106aecc3b93f9ae51 Mon Sep 17 00:00:00 2001 From: Lan Le Date: Fri, 21 Jun 2024 10:45:40 +0200 Subject: [PATCH 3/4] feat: process DSC layout (#197) --- chem_spectra/controller/helper/share.py | 2 + chem_spectra/lib/composer/ni.py | 74 ++++++++++-------- chem_spectra/lib/converter/jcamp/base.py | 4 + .../lib/converter/jcamp/data_type.json | 3 +- .../converter/jcamp/data_type.json.example | 3 +- chem_spectra/lib/converter/jcamp/ni.py | 4 +- chem_spectra/lib/converter/share.py | 4 + environment.yml | 45 +++++------ requirements.txt | 5 +- setup.py | 2 +- tests/fixtures/source/bagit/dsc/dsc.zip | Bin 0 -> 13972 bytes .../bagit/test_bagit_base_converter.py | 10 +++ tests/lib/converter/test_share.py | 6 ++ 13 files changed, 102 insertions(+), 60 deletions(-) create mode 100644 tests/fixtures/source/bagit/dsc/dsc.zip diff --git a/chem_spectra/controller/helper/share.py b/chem_spectra/controller/helper/share.py index 684ac6f8..3ae0ff55 100644 --- a/chem_spectra/controller/helper/share.py +++ b/chem_spectra/controller/helper/share.py @@ -107,6 +107,7 @@ def extract_params(request): list_file_names = request.form.getlist('list_file_names[]') data_type_mapping = request.form.get('data_type_mapping', default='') detector = request.form.get('detector', default=None) + dsc_meta_data = request.form.get('dsc_meta_data', default=None) params = { 'peaks_str': request.form.get('peaks_str', default=None), @@ -131,6 +132,7 @@ def extract_params(request): 'axesUnits': axesUnits, 'data_type_mapping': data_type_mapping, 'detector': detector, + 'dsc_meta_data': dsc_meta_data, } has_params = ( params.get('peaks_str') or diff --git a/chem_spectra/lib/composer/ni.py b/chem_spectra/lib/composer/ni.py index 80269c0a..96cd1089 100644 --- a/chem_spectra/lib/composer/ni.py +++ b/chem_spectra/lib/composer/ni.py @@ -141,6 +141,24 @@ def __gen_header_sec(self): result.append(key_str) return result + def __gen_header_user_input_meta_data(self): + if self.core.is_dsc: + dsc_meta_data = self.core.params.get('dsc_meta_data', None) + melting_point, tg_value = '', '' + if dsc_meta_data is not None: + melting_point = dsc_meta_data.get('meltingPoint', '') + tg_value = dsc_meta_data.get('tg', '') + else: + melting_point_arr = self.core.dic.get('MELTINGPOINT', ['']) + tg_value_arr = self.core.dic.get('TG', ['']) + melting_point = melting_point_arr[0] + tg_value = tg_value_arr[0] + return [ + f'##MELTINGPOINT={melting_point}\n', + f'##TG={tg_value}\n' + ] + return [] + def __get_xy_of_peak(self, peak): if peak is None: return '', '' @@ -214,6 +232,7 @@ def __compose(self): meta.extend(self.__gen_headers_spectrum_orig()) if self.core.is_sec: meta.extend(self.__gen_header_sec()) + meta.extend(self.__gen_header_user_input_meta_data()) meta.extend(self.gen_spectrum_orig()) meta.extend(self.__gen_headers_im()) meta.extend(self.__gen_headers_integration()) @@ -243,35 +262,12 @@ def __compose(self): return meta def __plt_nbins(self): - typ = self.core.typ - if 'NMR' == typ: - return 20 - elif 'INFRARED' == typ: - return 20 - elif 'RAMAN' == typ: - return 20 - elif 'UVVIS' == typ: - return 20 - elif 'THERMOGRAVIMETRIC ANALYSIS' == typ: - return 20 - elif 'MS' == typ: - return 20 return 20 def __fakto(self): typ = self.core.typ - if 'NMR' == typ: - return 1 - elif 'MS' == typ: - return 1 - elif 'INFRARED' == typ: + if 'INFRARED' == typ: return -1 - elif 'RAMAN' == typ: - return 1 - elif 'UVVIS' == typ: - return 1 - elif 'THERMOGRAVIMETRIC ANALYSIS' == typ: - return 1 return 1 def tf_img(self): @@ -666,16 +662,32 @@ def __draw_peaks(self, plt, x_peaks, y_peaks, h, w, y_boundary_max): def __generate_info_box(self, plotlib): - if not self.core.is_sec: + if not (self.core.is_sec or self.core.is_dsc): 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) + if self.core.is_sec: + sec_data_key = ['MN', 'MW', 'MP', 'D'] + 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) + else: + dsc_meta_data = self.core.params.get('dsc_meta_data', None) + melting_point, tg_value = '', '' + if dsc_meta_data is not None: + melting_point = dsc_meta_data.get('meltingPoint', '') + tg_value = dsc_meta_data.get('tg', '') + else: + melting_point_arr = self.core.dic.get('MELTINGPOINT', ['']) + tg_value_arr = self.core.dic.get('TG', ['']) + melting_point = melting_point_arr[0] + tg_value = tg_value_arr[0] + + melting_point_str = f"MELTING POINT={melting_point}" + tg_str = f"TG={tg_value}" + result.extend([melting_point_str, tg_str]) info_str = '\n'.join(result) props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) diff --git a/chem_spectra/lib/converter/jcamp/base.py b/chem_spectra/lib/converter/jcamp/base.py index 846c3610..78621ecd 100644 --- a/chem_spectra/lib/converter/jcamp/base.py +++ b/chem_spectra/lib/converter/jcamp/base.py @@ -34,6 +34,7 @@ def __init__(self, path, params=False): self.is_emissions = self.__is_emissions() self.is_dls_acf = self.__is_dls_acf() self.is_dls_intensity = self.__is_dls_intensity() + self.is_dsc = self.__is_dsc() self.non_nmr = self.__non_nmr() self.ncl = self.__ncl() self.simu_peaks = self.__read_simu_peaks() @@ -156,6 +157,9 @@ def __is_dls_acf(self): def __is_dls_intensity(self): return self.typ in ['DLS intensity'] + + def __is_dsc(self): + return self.typ in ['DIFFERENTIAL SCANNING CALORIMETRY'] def __ncl(self): try: diff --git a/chem_spectra/lib/converter/jcamp/data_type.json b/chem_spectra/lib/converter/jcamp/data_type.json index bd90604f..8cccc47f 100644 --- a/chem_spectra/lib/converter/jcamp/data_type.json +++ b/chem_spectra/lib/converter/jcamp/data_type.json @@ -14,6 +14,7 @@ "SORPTION-DESORPTION MEASUREMENT": ["SORPTION-DESORPTION MEASUREMENT"], "Emissions": ["Emissions", "EMISSIONS", "FLUORESCENCE SPECTRUM", "FL SPECTRUM"], "DLS ACF": ["DLS ACF"], - "DLS intensity": ["DLS INTENSITY", "DLS intensity"] + "DLS intensity": ["DLS INTENSITY", "DLS intensity"], + "DIFFERENTIAL SCANNING CALORIMETRY": ["DIFFERENTIAL SCANNING CALORIMETRY"] } } diff --git a/chem_spectra/lib/converter/jcamp/data_type.json.example b/chem_spectra/lib/converter/jcamp/data_type.json.example index 5c135ad6..26b5ff20 100644 --- a/chem_spectra/lib/converter/jcamp/data_type.json.example +++ b/chem_spectra/lib/converter/jcamp/data_type.json.example @@ -14,6 +14,7 @@ "SORPTION-DESORPTION MEASUREMENT": ["SORPTION-DESORPTION MEASUREMENT"], "Emissions": ["Emissions", "EMISSIONS", "FLUORESCENCE SPECTRUM", "FL SPECTRUM"], "DLS ACF": ["DLS ACF"], - "DLS intensity": ["DLS INTENSITY", "DLS intensity"] + "DLS intensity": ["DLS INTENSITY", "DLS intensity"], + "DIFFERENTIAL SCANNING CALORIMETRY": ["DIFFERENTIAL SCANNING CALORIMETRY"] } } diff --git a/chem_spectra/lib/converter/jcamp/ni.py b/chem_spectra/lib/converter/jcamp/ni.py index ce2bc6e8..7e016917 100644 --- a/chem_spectra/lib/converter/jcamp/ni.py +++ b/chem_spectra/lib/converter/jcamp/ni.py @@ -43,6 +43,7 @@ def __init__(self, base): self.is_emissions = base.is_emissions if hasattr(base, 'is_emissions') else False self.is_dls_acf = base.is_dls_acf if hasattr(base, 'is_dls_acf') else False self.is_dls_intensity = base.is_dls_intensity if hasattr(base, 'is_dls_intensity') else False + self.is_dsc = base.is_dsc if hasattr(base, 'is_dsc') else False self.non_nmr = base.non_nmr self.ncl = base.ncl self.is_dept = base.is_dept @@ -98,7 +99,8 @@ def __thres(self): "CYCLIC VOLTAMMETRY": THRESHOLD_XRD, "SORPTION-DESORPTION MEASUREMENT": THRESHOLD_XRD, "DLS intensity": THRESHOLD_XRD, - "Emissions": THRESHOLD_EMISSION + "Emissions": THRESHOLD_EMISSION, + "DIFFERENTIAL SCANNING CALORIMETRY": THRESHOLD_TGA, } if self.params.get('user_data_type_mapping'): diff --git a/chem_spectra/lib/converter/share.py b/chem_spectra/lib/converter/share.py index 7766d420..e98563d4 100644 --- a/chem_spectra/lib/converter/share.py +++ b/chem_spectra/lib/converter/share.py @@ -25,6 +25,7 @@ def parse_params(params): 'jcamp_idx': 0, 'axesUnits': None, 'detector': None, + 'dsc_meta_data': None, } select_x = params.get('select_x', None) @@ -68,6 +69,8 @@ def parse_params(params): user_data_type_mapping = params.get('data_type_mapping') detector = params.get('detector') detector = json.loads(detector) if detector else None + dsc_meta_data = params.get('dsc_meta_data') + dsc_meta_data = json.loads(dsc_meta_data) if dsc_meta_data else None if (cyclicvolta is not None): spectraList = cyclicvolta['spectraList'] if (len(spectraList) > 0): @@ -101,6 +104,7 @@ def parse_params(params): 'axesUnits': axesUnits, 'user_data_type_mapping': user_data_type_mapping, 'detector': detector, + 'dsc_meta_data': dsc_meta_data, } diff --git a/environment.yml b/environment.yml index 5addf1f1..c02dcb55 100644 --- a/environment.yml +++ b/environment.yml @@ -4,24 +4,25 @@ channels: dependencies: - _libgcc_mutex=0.1=main - _openmp_mutex=5.1=1_gnu - - ca-certificates=2024.3.11=h06a4308_0 + - ca-certificates=2023.08.22=h06a4308_0 - ld_impl_linux-64=2.38=h1181459_1 - - libffi=3.4.4=h6a678d5_1 + - libffi=3.4.4=h6a678d5_0 - libgcc-ng=11.2.0=h1234567_1 - libgomp=11.2.0=h1234567_1 - libstdcxx-ng=11.2.0=h1234567_1 - ncurses=6.4=h6a678d5_0 - - openssl=3.0.13=h7f8727e_1 - - pip=24.0=py38h06a4308_0 - - python=3.8.19=h955ad1f_0 + - openssl=3.0.11=h7f8727e_2 + - pip=23.3=py38h06a4308_0 + - python=3.8.18=h955ad1f_0 - readline=8.2=h5eee18b_0 - - setuptools=69.5.1=py38h06a4308_0 - - sqlite=3.45.3=h5eee18b_0 - - tk=8.6.14=h39e8969_0 - - wheel=0.43.0=py38h06a4308_0 - - xz=5.4.6=h5eee18b_1 - - zlib=1.2.13=h5eee18b_1 + - setuptools=68.0.0=py38h06a4308_0 + - sqlite=3.41.2=h5eee18b_0 + - tk=8.6.12=h1ccaba5_0 + - wheel=0.41.2=py38h06a4308_0 + - xz=5.4.2=h5eee18b_0 + - zlib=1.2.13=h5eee18b_0 - pip: + - adjusttext==1.1.1 - astroid==2.15.8 - atomicwrites==1.4.1 - attrs==23.2.0 @@ -32,15 +33,14 @@ dependencies: - click==8.1.7 - contourpy==1.1.1 - coverage==7.5.1 - - coverage-badge==1.1.1 - cycler==0.12.1 - - dill==0.3.8 + - dill==0.3.7 - entrypoints==0.3 - - exceptiongroup==1.2.1 + - exceptiongroup==1.1.3 - flake8==3.7.9 - flask==2.2.5 - flask-jwt-extended==4.5.2 - - fonttools==4.51.0 + - fonttools==4.43.1 - gunicorn==22.0.0 - idna==3.7 - importlib-metadata==3.6.0 @@ -58,12 +58,13 @@ dependencies: - netcdf4==1.5.3 - numpy==1.22.4 - olefile==0.46 - - packaging==24.0 + - packaging==23.2 - pandas==2.0.3 - pathlib2==2.3.4 - pillow==10.3.0 - - platformdirs==4.2.1 + - platformdirs==4.1.0 - pluggy==0.12.0 + - py==1.11.0 - pycodestyle==2.5.0 - pyflakes==2.1.1 - pyjwt==2.8.0 @@ -76,14 +77,14 @@ dependencies: - pytz==2023.3 - rdkit==2023.9.1 - regex==2019.4.9 - - requests==2.31.0 + - requests==2.32.2 - scipy==1.7.3 - six==1.11.0 - tomli==2.0.1 - - tomlkit==0.12.4 - - typing-extensions==4.11.0 - - tzdata==2024.1 - - urllib3==1.26.18 + - tomlkit==0.12.3 + - typing-extensions==4.9.0 + - tzdata==2023.3 + - urllib3==1.26.19 - werkzeug==3.0.3 - wrapt==1.16.0 - zipp==0.5.2 diff --git a/requirements.txt b/requirements.txt index 73621146..b5124d14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,6 @@ MarkupSafe==2.1.2 matplotlib==3.7.3 mccabe==0.6.1 more-itertools==7.2.0 -# -e git+https://github.com/ComPlat/nmrglue.git@c5a7d4d0073fedff68808b4e9c95836a8c20413e#egg=nmrglue -e git+https://github.com/ComPlat/nmrglue.git@e6e8a63b1848ae0525f07de0a6ec0cfdb900ba60#egg=nmrglue netCDF4==1.5.3 numpy==1.22.4 @@ -35,10 +34,10 @@ pytest==7.2.0 python-dateutil==2.8.2 pytz==2023.3 regex==2019.4.9 -requests==2.31.0 +requests==2.32.2 scipy==1.7.3 six==1.11.0 -urllib3==1.26.18 +urllib3==1.26.19 Werkzeug==3.0.3 zipp==0.5.2 pyopenms==2.6.0 diff --git a/setup.py b/setup.py index fc5db5fc..c18fe239 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='chem-spectra-app', - version='1.2.0', + version='1.2.1', packages=find_packages(), include_package_data=True, zip_safe=False, diff --git a/tests/fixtures/source/bagit/dsc/dsc.zip b/tests/fixtures/source/bagit/dsc/dsc.zip new file mode 100644 index 0000000000000000000000000000000000000000..63079a3965fc4e35792237fe98fd5defb53ea883 GIT binary patch literal 13972 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 literal 0 HcmV?d00001 diff --git a/tests/lib/converter/bagit/test_bagit_base_converter.py b/tests/lib/converter/bagit/test_bagit_base_converter.py index 9a0edbde..2e798168 100644 --- a/tests/lib/converter/bagit/test_bagit_base_converter.py +++ b/tests/lib/converter/bagit/test_bagit_base_converter.py @@ -10,6 +10,7 @@ emissions_layout_path = target_dir + 'emissions/emissions.zip' dls_acf_layout_path = target_dir + 'dls_acf/dls_acf.zip' dls_intensity_layout_path = target_dir + 'dls_intensity/dls_intensity.zip' +dsc_layout_path = target_dir + 'dsc/dsc.zip' def assertFileType(file, mimeStr): assert mimetypes.guess_type(file.name)[0] == mimeStr @@ -144,3 +145,12 @@ def test_bagit_has_one_file_no_combined_image(): converter = BagItConveter(td) assert converter.combined_image is None + +def test_bagit_convert_to_jcamp_dsc_layout(): + with tempfile.TemporaryDirectory() as td: + with zipfile.ZipFile(dsc_layout_path, 'r') as z: + z.extractall(td) + + converter = BagItConveter(td) + jcamp = converter.data[0] + assertJcampContent(jcamp, '##DATA TYPE=DIFFERENTIAL SCANNING CALORIMETRY') diff --git a/tests/lib/converter/test_share.py b/tests/lib/converter/test_share.py index cd19ba11..afde62d8 100644 --- a/tests/lib/converter/test_share.py +++ b/tests/lib/converter/test_share.py @@ -28,6 +28,7 @@ def expected_default_params(): 'jcamp_idx': 0, 'axesUnits': None, 'detector': None, + 'dsc_meta_data': None, } def test_parse_params_without_params(expected_default_params): @@ -222,6 +223,11 @@ def test_parse_params_detector(): def test_parse_solvent(): #TODO: need to be updated assert 1==1 + +def test_parse_dsc_meta_data(): + params = {'dsc_meta_data': '{"meltingPoint": "1.0", "tg": "1.0"}'} + parsed_data = parse_params(params) + assert parsed_data['dsc_meta_data'] == {"meltingPoint": "1.0", "tg": "1.0"} def test_reduce_pts_when_does_not_have_any_x(): array_data = [] From 8cf6e694ee9e33d090cfa864b8387d29fa084b5b Mon Sep 17 00:00:00 2001 From: Lan Le Date: Fri, 21 Jun 2024 11:25:26 +0200 Subject: [PATCH 4/4] fix: fixed cannot save nmrium --- chem_spectra/lib/converter/nmrium/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chem_spectra/lib/converter/nmrium/base.py b/chem_spectra/lib/converter/nmrium/base.py index 924b2f60..f32ee0ec 100644 --- a/chem_spectra/lib/converter/nmrium/base.py +++ b/chem_spectra/lib/converter/nmrium/base.py @@ -46,6 +46,8 @@ def __init__(self, file=None): self.simu_peaks = [] self.is_cyclic_volta = False + self.is_sec = False + self.is_dsc = False self.typ = '' self.threshold = 1.0