|
| 1 | +"""Runs RAT from the MATLAB API.""" |
| 2 | + |
| 3 | +import json |
| 4 | +import tempfile |
| 5 | +import warnings |
| 6 | +from pathlib import Path |
| 7 | + |
| 8 | +import numpy as np |
| 9 | + |
| 10 | +from ..events import EventTypes, PlotEventData, ProgressEventData, notify |
| 11 | +from ..outputs import Results |
| 12 | +from ..project import Project |
| 13 | +from ..wrappers import MatlabWrapper |
| 14 | + |
| 15 | +RUNNER = """function executeRAT() |
| 16 | +
|
| 17 | +cur_dir = pwd; |
| 18 | +cd('{rat_path}'); |
| 19 | +addPaths; |
| 20 | +cd(cur_dir); |
| 21 | +
|
| 22 | +project = jsonToProject('{project}'); |
| 23 | +controls = jsonToControls('{control}'); |
| 24 | +customControls = customControl(); |
| 25 | +customControls.update(controls); |
| 26 | +customControls.filePath = '{ipc_path}'; |
| 27 | +
|
| 28 | +for i=1:project.customFile.rowCount |
| 29 | + addpath(project.customFile.varTable{{i, 5}}); |
| 30 | +end |
| 31 | +eventManager.register(eventTypes.Message, @(x) logger('{msg_log_path}', x)); |
| 32 | +eventManager.register(eventTypes.Progress, @(x) logger('{progress_log_path}', x)); |
| 33 | +eventManager.register(eventTypes.Plot, @(x) logger('{plot_log_path}', x)); |
| 34 | +global RAT_PROGRESS_UPDATE_FREQ RAT_PROGRESS_UPDATE_COUNT |
| 35 | +RAT_PROGRESS_UPDATE_FREQ = {progress_event_freq}; |
| 36 | +RAT_PROGRESS_UPDATE_COUNT = -1; |
| 37 | +
|
| 38 | +[project, results] = RAT(project, customControls); |
| 39 | +
|
| 40 | +projectToJson(project, '{project}'); |
| 41 | +resultsToJson(results, '{result}'); |
| 42 | +eventManager.clear(); |
| 43 | +close all |
| 44 | +end |
| 45 | +""" |
| 46 | + |
| 47 | + |
| 48 | +CONTROL = """classdef customControl < controlsClass |
| 49 | + properties(Hidden = true) |
| 50 | + filePath = '' |
| 51 | + end |
| 52 | + methods |
| 53 | + function update(obj, controls) |
| 54 | + propNames = properties(controls); |
| 55 | + for i = 1:length(propNames) |
| 56 | + obj.(propNames{i}) = controls.(propNames{i}); |
| 57 | + end |
| 58 | + end |
| 59 | + function path = getIPCFilePath(obj) |
| 60 | + path = obj.filePath; |
| 61 | + end |
| 62 | + end |
| 63 | +end |
| 64 | +""" |
| 65 | + |
| 66 | +LOGGER = """function logger(logPath, data) |
| 67 | +
|
| 68 | + if isstruct(data) |
| 69 | + entry = plotDataToJson(data); |
| 70 | + elseif iscell(data) |
| 71 | + global RAT_PROGRESS_UPDATE_FREQ RAT_PROGRESS_UPDATE_COUNT; |
| 72 | + RAT_PROGRESS_UPDATE_COUNT = RAT_PROGRESS_UPDATE_COUNT + 1; |
| 73 | + if rem(RAT_PROGRESS_UPDATE_COUNT, RAT_PROGRESS_UPDATE_FREQ) ~= 0 |
| 74 | + return |
| 75 | + end |
| 76 | + entry = [data{1}, ',', num2str(data{2})]; |
| 77 | + else |
| 78 | + entry = strip(data, 'right'); |
| 79 | + end |
| 80 | + fid = fopen(logPath, "a"); |
| 81 | + cleanup = onCleanup(@() fclose(fid)); |
| 82 | + fprintf(fid, "%s\\n", entry); |
| 83 | +end |
| 84 | +
|
| 85 | +function encoded = plotDataToJson(data) |
| 86 | + % Encodes the results into a json file... |
| 87 | + tmpResults = struct(); |
| 88 | + for fn = fieldnames(data)' |
| 89 | + tmpResults.(fn{1}) = data.(fn{1}); |
| 90 | + end |
| 91 | + |
| 92 | + tmpResults.reflectivity = correctCellArray(tmpResults.reflectivity); |
| 93 | + tmpResults.shiftedData = correctCellArray(tmpResults.shiftedData); |
| 94 | + tmpResults.sldProfiles = makeCellJson(tmpResults.sldProfiles); |
| 95 | + tmpResults.resampledLayers = makeCellJson(tmpResults.resampledLayers); |
| 96 | + |
| 97 | + encoded = jsonencode(tmpResults,ConvertInfAndNaN=false); |
| 98 | + encoded = strrep(encoded, ']"', ']'); |
| 99 | + encoded = strrep(encoded, '"[', '['); |
| 100 | +end |
| 101 | +
|
| 102 | +function outputArray = makeCellJson(cellArray) |
| 103 | + % The jsonencode function flattens 2d cell arrays this is a workaround to |
| 104 | + % avoid flattening by converting to a string array with is not flattened. |
| 105 | + [row, col] = size(cellArray, [1, 2]); |
| 106 | + outputArray = strings([row, col]); |
| 107 | + for i=1:row |
| 108 | + for j=1:col |
| 109 | + entry = cellArray{i, j}; |
| 110 | + if size(entry, 1) == 1 |
| 111 | + entry = {entry}; |
| 112 | + end |
| 113 | + if col == 1 |
| 114 | + entry = {entry}; |
| 115 | + end |
| 116 | + outputArray(i, j) = jsonencode(entry); |
| 117 | + end |
| 118 | + end |
| 119 | + if row == 1 |
| 120 | + outputArray = {outputArray}; |
| 121 | + end |
| 122 | +
|
| 123 | +end |
| 124 | +
|
| 125 | +function cellArray = correctCellArray(cellArray) |
| 126 | + % Corrects array with single row so its written as 2D array in json |
| 127 | + [row, col] = size(cellArray, [1, 2]); |
| 128 | + for i=1:row |
| 129 | + for j=1:col |
| 130 | + if size(cellArray{i, j}, 1) == 1 |
| 131 | + cellArray{i, j} = {cellArray{i, j}}; |
| 132 | + end |
| 133 | + end |
| 134 | + end |
| 135 | +end |
| 136 | +""" |
| 137 | + |
| 138 | + |
| 139 | +def run_matlab_directly( |
| 140 | + project, controls, matlab_rat_path, ipc_path="", stdout=None, stderr=None, progress_event_freq=10 |
| 141 | +): |
| 142 | + """Run User provided MATLAB RAT for the given project and controls inputs. |
| 143 | +
|
| 144 | + Parameters |
| 145 | + ---------- |
| 146 | + project : RAT.Project or dict |
| 147 | + The project model (or equivalent json dict), which defines the physical system under study. |
| 148 | + controls : RAT.Controls or dict |
| 149 | + The controls model (or equivalent json dict), which defines algorithmic properties. |
| 150 | + matlab_rat_path : str |
| 151 | + The path to MATLAB RAT folder. |
| 152 | + ipc_path : str, optional |
| 153 | + IPC path for MATLAB to use. |
| 154 | + stdout : io.TextIOBase, optional |
| 155 | + Text stream for MATLAB console output. |
| 156 | + stderr : io.TextIOBase, optional |
| 157 | + Text stream for MATLAB console error output. |
| 158 | + progress_event_freq : int, default: 10 |
| 159 | + Update frequency of the progress event. |
| 160 | + """ |
| 161 | + if MatlabWrapper.loader is None: |
| 162 | + raise ImportError(MatlabWrapper.loader_error_message) from None |
| 163 | + |
| 164 | + engine = MatlabWrapper.loader.result() |
| 165 | + |
| 166 | + with tempfile.TemporaryDirectory() as tmp: |
| 167 | + project_file = Path(tmp, "project.json") |
| 168 | + control_file = Path(tmp, "controls.json") |
| 169 | + result_file = Path(tmp, "results.json") |
| 170 | + runner_file = Path(tmp, "executeRAT.m") |
| 171 | + custom_controls_file = Path(tmp, "customControl.m") |
| 172 | + msg_log_file = Path(tmp, "runner_msg_log.txt") |
| 173 | + progress_log_file = Path(tmp, "runner_progress_log.txt") |
| 174 | + plot_log_file = Path(tmp, "runner_plot_log.txt") |
| 175 | + Path(tmp, "logger.m").write_text(LOGGER) |
| 176 | + with open(custom_controls_file, "w") as f: |
| 177 | + f.write(CONTROL) |
| 178 | + |
| 179 | + with open(runner_file, "w") as f: |
| 180 | + f.write( |
| 181 | + RUNNER.format( |
| 182 | + project=project_file, |
| 183 | + control=control_file, |
| 184 | + result=result_file, |
| 185 | + rat_path=matlab_rat_path, |
| 186 | + ipc_path=ipc_path, |
| 187 | + msg_log_path=msg_log_file, |
| 188 | + progress_log_path=progress_log_file, |
| 189 | + plot_log_path=plot_log_file, |
| 190 | + progress_event_freq=progress_event_freq, |
| 191 | + ) |
| 192 | + ) |
| 193 | + |
| 194 | + controls.save(control_file) if not isinstance(controls, dict) else control_file.write_text(json.dumps(controls)) |
| 195 | + |
| 196 | + with warnings.catch_warnings(): # Avoid warning about relative paths |
| 197 | + warnings.simplefilter("ignore") |
| 198 | + project.save(project_file) if not isinstance(project, dict) else project_file.write_text( |
| 199 | + json.dumps(project) |
| 200 | + ) |
| 201 | + |
| 202 | + engine.addpath(tmp, nargout=0) |
| 203 | + future = engine.executeRAT(nargout=0, stdout=stdout, stderr=stderr, background=True) |
| 204 | + msg_cur_line = 0 |
| 205 | + plot_cur_line = 0 |
| 206 | + progress_cur_line = 0 |
| 207 | + while not future.done(): |
| 208 | + if msg_log_file.exists(): |
| 209 | + with open(msg_log_file, encoding="utf-8") as handle: |
| 210 | + handle.seek(msg_cur_line) |
| 211 | + text = handle.read() |
| 212 | + msg_cur_line = handle.tell() |
| 213 | + if text: |
| 214 | + notify(EventTypes.Message, text) |
| 215 | + if progress_log_file.exists(): |
| 216 | + with open(progress_log_file, encoding="utf-8") as handle: |
| 217 | + handle.seek(progress_cur_line) |
| 218 | + lines = handle.readlines() |
| 219 | + progress_cur_line = handle.tell() |
| 220 | + if lines: |
| 221 | + msg, percent = lines[-1].strip().rsplit(",", 1) |
| 222 | + progress_data = ProgressEventData() |
| 223 | + progress_data.message = msg |
| 224 | + progress_data.percent = float(percent) |
| 225 | + notify(EventTypes.Progress, progress_data) |
| 226 | + if plot_log_file.exists(): |
| 227 | + with open(plot_log_file, encoding="utf-8") as handle: |
| 228 | + handle.seek(plot_cur_line) |
| 229 | + lines = handle.readlines() |
| 230 | + plot_cur_line = handle.tell() |
| 231 | + if lines: |
| 232 | + plot_data = PlotEventData() |
| 233 | + plot_json = json.loads(lines[-1]) |
| 234 | + plot_data.modelType = plot_json["modelType"] |
| 235 | + plot_data.reflectivity = [np.array(ref) for ref in plot_json["reflectivity"]] |
| 236 | + plot_data.shiftedData = [np.array(sd) for sd in plot_json["shiftedData"]] |
| 237 | + plot_data.sldProfiles = [ |
| 238 | + [np.array(prof) for prof in profiles] for profiles in plot_json["sldProfiles"] |
| 239 | + ] |
| 240 | + plot_data.resampledLayers = [ |
| 241 | + [np.array(lay) for lay in layers] for layers in plot_json["resampledLayers"] |
| 242 | + ] |
| 243 | + plot_data.dataPresent = plot_json["dataPresent"] |
| 244 | + plot_data.subRoughs = plot_json["subRoughs"] |
| 245 | + plot_data.resample = plot_json["resample"] |
| 246 | + plot_data.contrastNames = plot_json["contrastNames"] |
| 247 | + notify(EventTypes.Plot, plot_data) |
| 248 | + engine.rmpath(tmp, nargout=0) |
| 249 | + if future.result() is not None: |
| 250 | + raise RuntimeError(future.result()) |
| 251 | + |
| 252 | + project = Project.load(project_file) |
| 253 | + results = Results.load(result_file) |
| 254 | + return project, results |
0 commit comments