diff --git a/src/calng/base_correction.py b/src/calng/base_correction.py index bd88ad7df4d59ae3602358ecd6045dcd3ab95c75..575022fa7e33f102a356bc86321999f93d8e2523 100644 --- a/src/calng/base_correction.py +++ b/src/calng/base_correction.py @@ -91,10 +91,10 @@ class BaseCorrection(PythonDevice): raise NotImplementedError() def _successfully_loaded_constant_to_runner(self, constant): - field_name = self._constant_to_correction_name[constant] - key = f"corrections.{field_name}.available" - if not self.unsafe_get(key): - self.set(key, True) + for field_name in self._constant_to_correction_names[constant]: + key = f"corrections.{field_name}.available" + if not self.unsafe_get(key): + self.set(key, True) self._update_correction_flags() self.log_status_info(f"Done loading {constant.name} to GPU") @@ -575,11 +575,10 @@ class BaseCorrection(PythonDevice): self._correction_applied_hash = Hash() # note: does not handle one constant enabling multiple correction steps # (do we need to generalize "decide if this correction step is available"?) - self._constant_to_correction_name = { - constant: name - for (name, _, constants) in self._correction_steps - for constant in constants - } + self._constant_to_correction_names = {} + for (name, _, constants) in self._correction_steps: + for constant in constants: + self._constant_to_correction_names.setdefault(constant, set()).add(name) self._buffer_lock = threading.Lock() self._last_processing_started = 0 # used for processing time and timeout diff --git a/src/calng/corrections/AgipdCorrection.py b/src/calng/corrections/AgipdCorrection.py index 1eaf7aa9fa0336626baacf4ec9be5c894b175a2f..71cf1f2e0e12dd8c654e3bcc2f7d635b5f824eac 100644 --- a/src/calng/corrections/AgipdCorrection.py +++ b/src/calng/corrections/AgipdCorrection.py @@ -34,6 +34,7 @@ class AgipdGainMode(enum.IntEnum): FIXED_LOW_GAIN = 3 +# note: if this is extended further, bump up the dtype passed to kernel class CorrectionFlags(enum.IntFlag): NONE = 0 THRESHOLD = 1 @@ -42,6 +43,8 @@ class CorrectionFlags(enum.IntFlag): REL_GAIN_PC = 8 GAIN_XRAY = 16 BPMASK = 32 + FORCE_MG_IF_BELOW = 64 + FORCE_HG_IF_BELOW = 128 class AgipdGpuRunner(base_kernel_runner.BaseGpuRunner): @@ -59,6 +62,8 @@ class AgipdGpuRunner(base_kernel_runner.BaseGpuRunner): bad_pixel_mask_value=np.nan, gain_mode=AgipdGainMode.ADAPTIVE_GAIN, g_gain_value=1, + mg_hard_threshold=2000, + hg_hard_threshold=2000, ): global cupy import cupy @@ -95,6 +100,8 @@ class AgipdGpuRunner(base_kernel_runner.BaseGpuRunner): self.rel_gain_xray_map_gpu = cupy.empty(self.map_shape, dtype=np.float32) self.bad_pixel_map_gpu = cupy.empty(self.gm_map_shape, dtype=np.uint32) self.set_bad_pixel_mask_value(bad_pixel_mask_value) + self.set_mg_hard_threshold(mg_hard_threshold) + self.set_hg_hard_threshold(hg_hard_threshold) self.set_g_gain_value(g_gain_value) self.flush_buffers(set(AgipdConstants)) @@ -231,6 +238,12 @@ class AgipdGpuRunner(base_kernel_runner.BaseGpuRunner): def set_bad_pixel_mask_value(self, mask_value): self.bad_pixel_mask_value = cupy.float32(mask_value) + def set_mg_hard_threshold(self, value): + self.mg_hard_threshold = cupy.float32(value) + + def set_hg_hard_threshold(self, value): + self.hg_hard_threshold = cupy.float32(value) + def flush_buffers(self, constants): if AgipdConstants.Offset in constants: self.offset_map_gpu.fill(0) @@ -267,6 +280,8 @@ class AgipdGpuRunner(base_kernel_runner.BaseGpuRunner): cupy.uint8(flags), self.default_gain, self.gain_thresholds_gpu, + self.mg_hard_threshold, + self.hg_hard_threshold, self.offset_map_gpu, self.rel_gain_pc_map_gpu, self.md_additional_offset_gpu, @@ -418,6 +433,16 @@ class AgipdCorrection(base_correction.BaseCorrection): # step name (used in schema), flag to enable for kernel, constants required ("thresholding", CorrectionFlags.THRESHOLD, {AgipdConstants.ThresholdsDark}), ("offset", CorrectionFlags.OFFSET, {AgipdConstants.Offset}), + ( # TODO: specify that /both/ constants are needed, not just one or the other + "forceMgIfBelow", + CorrectionFlags.FORCE_MG_IF_BELOW, + {AgipdConstants.ThresholdsDark, AgipdConstants.Offset}, + ), + ( + "forceHgIfBelow", + CorrectionFlags.FORCE_HG_IF_BELOW, + {AgipdConstants.ThresholdsDark, AgipdConstants.Offset}, + ), ("relGainPc", CorrectionFlags.REL_GAIN_PC, {AgipdConstants.SlopesPC}), ("gainXray", CorrectionFlags.GAIN_XRAY, {AgipdConstants.SlopesFF}), ( @@ -478,8 +503,42 @@ class AgipdCorrection(base_correction.BaseCorrection): expected, AgipdCorrection._managed_keys ) + # turn off the force MG / HG steps by default + for step in ("forceMgIfBelow", "forceHgIfBelow"): + for flag in ("enable", "preview"): + ( + OVERWRITE_ELEMENT(expected) + .key(f"corrections.{step}.{flag}") + .setNewDefaultValue(False) + .commit(), + ) # additional settings specific to AGIPD correction steps ( + FLOAT_ELEMENT(expected) + .key("corrections.forceMgIfBelow.hardThreshold") + .description( + "If enabled, any pixels assigned to low gain stage which would be " + "below this threshold after having medium gain offset subtracted will " + "be reassigned to medium gain." + ) + .assignmentOptional() + .defaultValue(1000) + .reconfigurable() + .commit(), + + FLOAT_ELEMENT(expected) + .key("corrections.forceHgIfBelow.hardThreshold") + .description( + "Like forceMgIfBelow, but potentially reassigning from medium gain to " + "high gain based on threshold and pixel value minus low gain offset. " + "Applied after forceMgIfBelow, pixels can theoretically be reassigned " + "twice, from LG to MG and to HG." + ) + .assignmentOptional() + .defaultValue(1000) + .reconfigurable() + .commit(), + BOOL_ELEMENT(expected) .key("corrections.relGainPc.overrideMdAdditionalOffset") .displayedName("Override md_additional_offset") @@ -523,11 +582,13 @@ class AgipdCorrection(base_correction.BaseCorrection): .reconfigurable() .commit(), ) - AgipdCorrection._managed_keys.add( - "corrections.relGainPc.overrideMdAdditionalOffset" - ) - AgipdCorrection._managed_keys.add("corrections.relGainPc.mdAdditionalOffset") - AgipdCorrection._managed_keys.add("corrections.gainXray.gGainValue") + AgipdCorrection._managed_keys |= { + "corrections.forceMgIfBelow.hardThreshold", + "corrections.forceHgIfBelow.hardThreshold", + "corrections.relGainPc.overrideMdAdditionalOffset", + "corrections.relGainPc.mdAdditionalOffset", + "corrections.gainXray.gGainValue", + } # mandatory: manager needs this in schema ( @@ -569,6 +630,12 @@ class AgipdCorrection(base_correction.BaseCorrection): "gain_mode": self.gain_mode, "bad_pixel_mask_value": self.bad_pixel_mask_value, "g_gain_value": self.unsafe_get("corrections.gainXray.gGainValue"), + "mg_hard_threshold": self.unsafe_get( + "corrections.forceMgIfBelow.hardThreshold" + ), + "hg_hard_threshold": self.unsafe_get( + "corrections.forceHgIfBelow.hardThreshold" + ), } @property @@ -717,6 +784,16 @@ class AgipdCorrection(base_correction.BaseCorrection): self.flush_constants() self._update_buffers() + if update.has("corrections.forceMgIfBelow.hardThreshold"): + self.kernel_runner.set_mg_hard_threshold( + self.get("corrections.forceMgIfBelow.hardThreshold") + ) + + if update.has("corrections.forceHgIfBelow.hardThreshold"): + self.kernel_runner.set_hg_hard_threshold( + self.get("corrections.forceHgIfBelow.hardThreshold") + ) + if update.has("corrections.gainXray.gGainValue"): self.kernel_runner.set_g_gain_value( self.get("corrections.gainXray.gGainValue") diff --git a/src/calng/kernels/agipd_gpu.cu b/src/calng/kernels/agipd_gpu.cu index 1e8cf672614fe108970530cc46fae419049a01ff..fed8a5c69a5417486eff07d38884097011993b5a 100644 --- a/src/calng/kernels/agipd_gpu.cu +++ b/src/calng/kernels/agipd_gpu.cu @@ -16,6 +16,8 @@ extern "C" { // default_gain can be 0, 1, or 2, and is relevant for fixed gain mode (no THRESHOLD) const unsigned char default_gain, const float* threshold_map, + const float mg_hard_threshold, + const float hg_hard_threshold, const float* offset_map, const float* rel_gain_pc_map, const float* md_additional_offset, @@ -99,16 +101,25 @@ extern "C" { gain = 2; } } + + const size_t gm_map_index_without_gain = map_cell * gm_map_stride_cell + + y * gm_map_stride_y + + x * gm_map_stride_x; + if ((corr_flags & FORCE_MG_IF_BELOW) && (gain == 2) && + (res - offset_map[gm_map_index_without_gain + 1 * gm_map_stride_gain] < mg_hard_threshold)) { + gain = 1; + } + if ((corr_flags & FORCE_HG_IF_BELOW) && (gain == 1) && + (res - offset_map[gm_map_index_without_gain] < hg_hard_threshold)) { + gain = 0; + } gain_map[output_index] = (float)gain; const size_t map_index = map_cell * map_stride_cell + y * map_stride_y + x * map_stride_x; - const size_t gm_map_index = gain * gm_map_stride_gain + - map_cell * gm_map_stride_cell + - y * gm_map_stride_y + - x * gm_map_stride_x; + const size_t gm_map_index = gm_map_index_without_gain + gain * gm_map_stride_gain; if ((corr_flags & BPMASK) && bad_pixel_map[gm_map_index]) { res = bad_pixel_mask_value;