From bfcd3a8c6a06541f745fadd7e3ecaea0ead5924e Mon Sep 17 00:00:00 2001 From: David Hammer <dhammer@mailbox.org> Date: Fri, 17 Dec 2021 16:02:16 +0100 Subject: [PATCH] Specify frameFilter without eval --- src/calng/base_correction.py | 88 ++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/src/calng/base_correction.py b/src/calng/base_correction.py index 033ba715..b3243e30 100644 --- a/src/calng/base_correction.py +++ b/src/calng/base_correction.py @@ -180,7 +180,8 @@ class BaseCorrection(PythonDevice): "dataFormat.outputAxisOrder", "dataFormat.outputImageDtype", "dataFormat.overrideInputAxisOrder", - "frameFilter", + "frameFilter.type", + "frameFilter.spec", "preview.enable", "preview.index", "preview.selectionMode", @@ -266,7 +267,7 @@ class BaseCorrection(PythonDevice): .defaultValue([]) .commit(), - STRING_ELEMENT(expected) + NODE_ELEMENT(expected) .key("frameFilter") .displayedName("Frame filter") .description( @@ -274,15 +275,46 @@ class BaseCorrection(PythonDevice): "filter will be discarded before any processing happens and will not " "get to dataOutput or preview. Note that this filter goes by frame " "index rather than cell ID or pulse ID; set accordingly. Handle with " - "care - an invalid filter can prevent all processing. The filter is " - "specified as a string which is evaluated into numpy uint16 array. A " - "valid filter could for eaxmple be 'np.arange(0, 352, 2)'." + "care - an invalid filter can prevent all processing. How the filter " + "is specified depends on frameFilter.type. See frameFilter.current to " + "inspect the currently set frame filter array (if any)." + ) + .commit(), + + STRING_ELEMENT(expected) + .key("frameFilter.type") + .displayedName("Filter definition type") + .description( + "Controls how frameFilter.spec is used. The default value of 'none' " + "means that no filter is set (regardless of frameFilter.spec). " + "'arange' allows between one and three integers separated by ',' which " + "are parsed and passed directly to numpy.arange. 'commaseparated' " + "reads a list of integers separated by commas." ) + .options("none,arange,commaseparated") + .assignmentOptional() + .defaultValue("none") + .reconfigurable() + .commit(), + + STRING_ELEMENT(expected) + .key("frameFilter.spec") .assignmentOptional() .defaultValue("") .reconfigurable() .commit(), + VECTOR_UINT32_ELEMENT(expected) + .key("frameFilter.current") + .displayedName("Current filter") + .description( + "This read-only value is used to display the contents of the current " + "frame filter. An empty array means no filtering is done." + ) + .readOnly() + .initialValue([]) + .commit(), + UINT32_ELEMENT(expected) .key("outputShmemBufferSize") .displayedName("Output buffer size limit") @@ -787,20 +819,50 @@ class BaseCorrection(PythonDevice): self.log.DEBUG(f"Corrections for preview: {str(preview)}") def _update_frame_filter(self): - """Parse frameFilter string (if set) and update cached filter array. Will set + """Parse frameFilter string (if set) and update cached filter array. May update dataFormat.filteredFrames, so one will typically want to call _update_buffers afterwards.""" - filter_string = self.get("frameFilter") - if filter_string.strip() == "": + # TODO: add some validation to preReconfigure + self.log.DEBUG("Updating frame filter") + filter_type = self.get("frameFilter.type") + filter_string = self.get("frameFilter.spec") + + if filter_type == "none" or filter_string.strip() == "": self._frame_filter = None + elif filter_type == "arange": + try: + numbers = tuple(int(part) for part in filter_string.split(",")) + except (ValueError, TypeError): + self.log_status_warn( + f"Invalid frame filter specification: {filter_string}" + ) + else: + self._frame_filter = np.arange(*numbers, dtype=np.uint16) + elif filter_type == "commaseparated": + try: + self._frame_filter = np.fromstring( + filter_string, sep=",", dtype=np.uint16 + ) + except ValueError: + # note: only in the future will numpy actually give ValueError + self.log_status_warn( + f"Invalid frame filter specification: {filter_string}" + ) + else: + self.log_status_warn(f"Invalid frame filter type '{filter_type}'") + + if self._frame_filter is None: self.set("dataFormat.filteredFrames", self.get("dataFormat.memoryCells")) + self.set("frameFilter.current", []) else: - self._frame_filter = np.array(eval(filter_string), dtype=np.uint16) self.set("dataFormat.filteredFrames", self._frame_filter.size) - if self._frame_filter.min() < 0 or self._frame_filter.max() >= self.get( - "dataFormat.memoryCells" - ): - self.log_status_warn("Invalid frame filter set, expect exceptions!") + self.set("frameFilter.current", list(map(int, self._frame_filter))) + + if self._frame_filter is not None and ( + self._frame_filter.min() < 0 + or self._frame_filter.max() >= self.get("dataFormat.memoryCells") + ): + self.log_status_warn("Invalid frame filter set, expect exceptions!") def _update_buffers(self): """(Re)initialize buffers / kernel runner according to expected data shapes""" -- GitLab