diff --git a/docs/source/interfaces/Engineering Diffraction.rst b/docs/source/interfaces/Engineering Diffraction.rst index 89477dfa73a00c494143c17a0cd66574c8f5cce2..f25105692a3eb72adc7cf9b9449c3b47fb8a8c0f 100644 --- a/docs/source/interfaces/Engineering Diffraction.rst +++ b/docs/source/interfaces/Engineering Diffraction.rst @@ -121,7 +121,8 @@ a plot for each bank and cropped focusing generates a plot for the single bank o Clicking the focus button will begin the focusing algorithm for the selected run files. The button and plotting checkbox will be disabled until the fitting algorithm is complete. -The focused output files are saved in NeXus, GSS, and raw XYE format to: +The focused output files are saved in NeXus, GSS, and TOPAS format. The process will also output a CSV file containing +all numerical sample logs. All of these files are saved to: `<CHOSEN_OUTPUT_DIRECTORY>/Focus/` diff --git a/docs/source/release/v5.1.0/diffraction.rst b/docs/source/release/v5.1.0/diffraction.rst index d4c0041287b2310f43156cddeda2fe8b045647e2..6a86de8a8e6c03acb1590156efefb2db30928069 100644 --- a/docs/source/release/v5.1.0/diffraction.rst +++ b/docs/source/release/v5.1.0/diffraction.rst @@ -14,6 +14,10 @@ Powder Diffraction Engineering Diffraction ----------------------- +Improvements +^^^^^^^^^^^^ +- TOPAS files (`.abc`) have replaced the `.dat` files generated when focusing using the GUI. +- Focusing with the GUI will now generate a CSV containing the averaged values of all numerical sample logs. Single Crystal Diffraction -------------------------- diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/focus/model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/focus/model.py index 3ca4cb3ffb10fc824d4978a3b71ede70f1e950fd..a744c6995cc5cd64365faa0d8388afde7cc35219 100644 --- a/scripts/Engineering/gui/engineering_diffraction/tabs/focus/model.py +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/focus/model.py @@ -6,7 +6,8 @@ # SPDX - License - Identifier: GPL - 3.0 + from __future__ import (absolute_import, division, print_function) -from os import path +import csv +from os import path, makedirs from matplotlib import gridspec import matplotlib.pyplot as plt @@ -55,6 +56,7 @@ class FocusModel(object): # Save the output to the file system. self._save_output(instrument, sample_path, name, output_workspace_name, rb_num) output_workspaces.append(workspaces_for_run) + self._output_sample_logs(instrument, run_no, sample_workspace, rb_num) else: for sample_path in sample_paths: sample_workspace = path_handling.load_workspace(sample_path) @@ -64,6 +66,7 @@ class FocusModel(object): curves_workspace, None, full_calib_workspace, spectrum_numbers) output_workspaces.append([output_workspace_name]) self._save_output(instrument, sample_path, "cropped", output_workspace_name, rb_num) + self._output_sample_logs(instrument, run_no, sample_workspace, rb_num) # Plot the output if plot_output: for ws_names in output_workspaces: @@ -126,8 +129,8 @@ class FocusModel(object): rb_num) self._save_focused_output_files_as_gss(instrument, sample_path, bank, sample_workspace, rb_num) - self._save_focused_output_files_as_xye(instrument, sample_path, bank, sample_workspace, - rb_num) + self._save_focused_output_files_as_topas_xye(instrument, sample_path, bank, sample_workspace, + rb_num) def _save_focused_output_files_as_gss(self, instrument, sample_path, bank, sample_workspace, rb_num): @@ -153,19 +156,53 @@ class FocusModel(object): self._generate_output_file_name(instrument, sample_path, bank, ".nxs")) SaveNexus(InputWorkspace=sample_workspace, Filename=nexus_output_path) - def _save_focused_output_files_as_xye(self, instrument, sample_path, bank, sample_workspace, - rb_num): + def _save_focused_output_files_as_topas_xye(self, instrument, sample_path, bank, + sample_workspace, rb_num): xye_output_path = path.join( path_handling.get_output_path(), "Focus", - self._generate_output_file_name(instrument, sample_path, bank, ".dat")) - SaveFocusedXYE(InputWorkspace=sample_workspace, Filename=xye_output_path, SplitFiles=False) + self._generate_output_file_name(instrument, sample_path, bank, ".abc")) + SaveFocusedXYE(InputWorkspace=sample_workspace, + Filename=xye_output_path, + SplitFiles=False, + Format="TOPAS") if rb_num: xye_output_path = path.join( path_handling.get_output_path(), "User", rb_num, "Focus", - self._generate_output_file_name(instrument, sample_path, bank, ".dat")) + self._generate_output_file_name(instrument, sample_path, bank, ".abc")) SaveFocusedXYE(InputWorkspace=sample_workspace, Filename=xye_output_path, - SplitFiles=False) + SplitFiles=False, + Format="TOPAS") + + @staticmethod + def _output_sample_logs(instrument, run_number, workspace, rb_num): + def write_to_file(): + with open(output_path, "w", newline="") as logfile: + writer = csv.writer(logfile, ["Sample Log", "Avg Value"]) + for log in output_dict: + writer.writerow([log, output_dict[log]]) + + output_dict = {} + sample_run = workspace.getRun() + log_names = sample_run.keys() + # Collect numerical sample logs. + for name in log_names: + try: + output_dict[name] = sample_run.getPropertyAsSingleValue(name) + except ValueError: + logger.information(f"Could not convert {name} to a numerical value. It will not be included in the " + f"sample logs output file.") + focus_dir = path.join(path_handling.get_output_path(), "Focus") + if not path.exists(focus_dir): + makedirs(focus_dir) + output_path = path.join(focus_dir, (instrument + "_" + run_number + "_sample_logs.csv")) + write_to_file() + if rb_num: + focus_user_dir = path.join(path_handling.get_output_path(), "User", rb_num, "Focus") + if not path.exists(focus_user_dir): + makedirs(focus_user_dir) + output_path = path.join(focus_user_dir, (instrument + "_" + run_number + "_sample_logs.csv")) + write_to_file() @staticmethod def _generate_output_file_name(instrument, sample_path, bank, suffix): diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/focus/test/test_focus_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/focus/test/test_focus_model.py index 7dd31c555c2d73a993dfb77e288a0c8bc403c432..d117784272767ff41946ec001cff9adf9a8efce7 100644 --- a/scripts/Engineering/gui/engineering_diffraction/tabs/focus/test/test_focus_model.py +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/focus/test/test_focus_model.py @@ -7,9 +7,12 @@ from __future__ import (absolute_import, division, print_function) import unittest +import tempfile +import shutil from os import path -from mantid.py3compat.mock import patch +from mantid.py3compat.mock import patch, MagicMock +from mantid.simpleapi import CreateSampleWorkspace from Engineering.gui.engineering_diffraction.tabs.focus import model from Engineering.gui.engineering_diffraction.tabs.common import path_handling from Engineering.gui.engineering_diffraction.tabs.common.calibration_info import CalibrationInfo @@ -19,11 +22,15 @@ file_path = "Engineering.gui.engineering_diffraction.tabs.focus.model" class FocusModelTest(unittest.TestCase): def setUp(self): + self.test_dir = tempfile.mkdtemp() self.model = model.FocusModel() self.current_calibration = CalibrationInfo(vanadium_path="/mocked/out/anyway", sample_path="this_is_mocked_out_too", instrument="ENGINX") + def tearDown(self): + shutil.rmtree(self.test_dir) + @patch(file_path + ".path_handling.load_workspace") @patch(file_path + ".vanadium_corrections.Ads.doesExist") def test_focus_cancelled_if_van_wsp_missing(self, ads_exist, load): @@ -31,11 +38,12 @@ class FocusModelTest(unittest.TestCase): self.model.focus_run("307593", ["1", "2"], False, "ENGINX", "0", None) self.assertEqual(load.call_count, 0) + @patch(file_path + ".FocusModel._output_sample_logs") @patch(file_path + ".Ads") @patch(file_path + ".FocusModel._save_output") @patch(file_path + ".FocusModel._run_focus") @patch(file_path + ".path_handling.load_workspace") - def test_focus_run_for_each_bank(self, load_focus, run_focus, output, ads): + def test_focus_run_for_each_bank(self, load_focus, run_focus, output, ads, logs): ads.retrieve.return_value = "test_wsp" banks = ["1", "2"] load_focus.return_value = "mocked_sample" @@ -47,11 +55,12 @@ class FocusModelTest(unittest.TestCase): "305761_" + model.FOCUSED_OUTPUT_WORKSPACE_NAME + banks[-1], "test_wsp", "test_wsp", banks[-1], None) + @patch(file_path + ".FocusModel._output_sample_logs") @patch(file_path + ".Ads") @patch(file_path + ".FocusModel._save_output") @patch(file_path + ".FocusModel._run_focus") @patch(file_path + ".path_handling.load_workspace") - def test_focus_run_for_custom_spectra(self, load_focus, run_focus, output, ads): + def test_focus_run_for_custom_spectra(self, load_focus, run_focus, output, ads, logs): ads.retrieve.return_value = "test_wsp" spectra = "20-50" load_focus.return_value = "mocked_sample" @@ -63,6 +72,7 @@ class FocusModelTest(unittest.TestCase): "305761_" + model.FOCUSED_OUTPUT_WORKSPACE_NAME + "cropped", "test_wsp", "test_wsp", None, None, spectra) + @patch(file_path + ".FocusModel._output_sample_logs") @patch(file_path + ".Ads") @patch(file_path + ".FocusModel._save_output") @patch(file_path + ".FocusModel._plot_focused_workspaces") @@ -70,7 +80,7 @@ class FocusModelTest(unittest.TestCase): @patch(file_path + ".path_handling.load_workspace") @patch(file_path + ".vanadium_corrections.fetch_correction_workspaces") def test_focus_plotted_when_checked(self, fetch_van, load_focus, run_focus, plot_focus, output, - ads): + ads, logs): ads.doesExist.return_value = True fetch_van.return_value = ("mocked_integ", "mocked_curves") banks = ["1", "2"] @@ -80,6 +90,7 @@ class FocusModelTest(unittest.TestCase): self.assertEqual(1, plot_focus.call_count) + @patch(file_path + ".FocusModel._output_sample_logs") @patch(file_path + ".Ads") @patch(file_path + ".FocusModel._save_output") @patch(file_path + ".FocusModel._plot_focused_workspaces") @@ -87,7 +98,7 @@ class FocusModelTest(unittest.TestCase): @patch(file_path + ".path_handling.load_workspace") @patch(file_path + ".vanadium_corrections.fetch_correction_workspaces") def test_focus_not_plotted_when_not_checked(self, fetch_van, load_focus, run_focus, plot_focus, - output, ads): + output, ads, logs): fetch_van.return_value = ("mocked_integ", "mocked_curves") banks = ["1", "2"] load_focus.return_value = "mocked_sample" @@ -121,6 +132,36 @@ class FocusModelTest(unittest.TestCase): self.assertEqual(gss.call_count, 2) self.assertEqual(xye.call_count, 2) + @patch(file_path + ".logger") + @patch(file_path + ".path_handling.get_output_path") + @patch(file_path + ".csv") + def test_output_sample_logs_with_rb_number(self, mock_csv, mock_path, mock_logger): + mock_writer = MagicMock() + mock_csv.writer.return_value = mock_writer + mock_path.return_value = self.test_dir + ws = CreateSampleWorkspace() + + self.model._output_sample_logs("ENGINX", "00000", ws, "0") + + self.assertEqual(5, len(ws.getRun().keys())) + self.assertEqual(1, mock_logger.information.call_count) + self.assertEqual(8, mock_writer.writerow.call_count) + + @patch(file_path + ".logger") + @patch(file_path + ".path_handling.get_output_path") + @patch(file_path + ".csv") + def test_output_sample_logs_without_rb_number(self, mock_csv, mock_path, mock_logger): + mock_writer = MagicMock() + mock_csv.writer.return_value = mock_writer + mock_path.return_value = self.test_dir + ws = CreateSampleWorkspace() + + self.model._output_sample_logs("ENGINX", "00000", ws, None) + + self.assertEqual(5, len(ws.getRun().keys())) + self.assertEqual(1, mock_logger.information.call_count) + self.assertEqual(4, mock_writer.writerow.call_count) + if __name__ == '__main__': unittest.main()