@@ -229,7 +229,7 @@ def __init__(
229229 self .intermediate_variables = self .PlanarPtychographyIntermediateVariables ()
230230
231231 self .diffraction_pattern_blur_sigma = diffraction_pattern_blur_sigma
232-
232+
233233 self .check_inputs ()
234234
235235 def check_inputs (self ):
@@ -760,12 +760,18 @@ def scale_gradients(self, patterns):
760760
761761
762762class NoiseModel (torch .nn .Module ):
763- def __init__ (self , eps = 1e-6 , valid_pixel_mask : Optional [Tensor ] = None ) -> None :
763+ def __init__ (
764+ self ,
765+ eps : float = 1e-6 ,
766+ valid_pixel_mask : Optional [Tensor ] = None ,
767+ exclude_measured_pixels_below : Optional [float ] = None ,
768+ ) -> None :
764769 super ().__init__ ()
765770 self .eps = eps
766771 self .noise_statistics = None
767772 self .valid_pixel_mask = valid_pixel_mask
768-
773+ self .exclude_measured_pixels_below = exclude_measured_pixels_below
774+
769775 def nll (self , y_pred : Tensor , y_true : Tensor ) -> Tensor :
770776 """
771777 Calculate the negative log-likelihood.
@@ -775,6 +781,25 @@ def nll(self, y_pred: Tensor, y_true: Tensor) -> Tensor:
775781 def backward (self , * args , ** kwargs ):
776782 raise NotImplementedError
777783
784+ @staticmethod
785+ def get_constrained_pixel_mask (
786+ valid_pixel_mask : Optional [Tensor ],
787+ exclude_measured_pixels_below : Optional [float ],
788+ y_true : Tensor ,
789+ ) -> Tensor :
790+ constrained_pixel_mask = torch .ones_like (y_true , dtype = torch .bool )
791+ if valid_pixel_mask is not None :
792+ constrained_pixel_mask = valid_pixel_mask .to (y_true .device )
793+ constrained_pixel_mask = constrained_pixel_mask .unsqueeze (0 ).expand (
794+ y_true .shape [0 ], - 1 , - 1
795+ )
796+ if exclude_measured_pixels_below is not None :
797+ constrained_pixel_mask = torch .logical_and (
798+ constrained_pixel_mask ,
799+ y_true > exclude_measured_pixels_below ,
800+ )
801+ return constrained_pixel_mask
802+
778803 @timer ()
779804 def conform_to_exit_wave_size (
780805 self ,
@@ -822,17 +847,24 @@ def backward_to_psi_far(self, y_pred, y_true, psi_far):
822847 $g = \f rac{\partial L}{\partial \psi_{far}}$.
823848
824849 When `self.valid_pixel_mask` is not None, pixels of the gradient `g` where the
825- mask is False are set to 0. When `g` is used to update the far-field wavefield
826- `psi_far`, the invalid pixels are kept unchanged.
850+ mask is False are set to 0. When `self.exclude_measured_pixels_below` is not
851+ None, gradients at pixels with measured intensities less than or equal to that
852+ threshold are also set to 0.
827853 """
828854 # Shape of g: (batch_size, h, w)
829855 # Shape of psi_far: (batch_size, n_probe_modes, h, w)
830856 y_pred , y_true , valid_pixel_mask = self .conform_to_exit_wave_size (
831857 y_pred , y_true , self .valid_pixel_mask , psi_far .shape [- 2 :]
832858 )
859+ constrained_pixel_mask = self .get_constrained_pixel_mask (
860+ valid_pixel_mask ,
861+ self .exclude_measured_pixels_below ,
862+ y_true ,
863+ )
833864 g = 1 - torch .sqrt (y_true ) / (torch .sqrt (y_pred ) + self .eps ) # Eq. 12b
834- if valid_pixel_mask is not None :
835- g [:, torch .logical_not (valid_pixel_mask )] = 0
865+
866+ g [torch .logical_not (constrained_pixel_mask )] = 0
867+
836868 w = 1 / (2 * self .sigma ) ** 2
837869 g = 2 * w * g [:, None , :, :] * psi_far
838870 return g
@@ -863,8 +895,12 @@ def backward_to_psi_far(self, y_pred: Tensor, y_true: Tensor, psi_far: Tensor):
863895 y_pred , y_true , valid_pixel_mask = self .conform_to_exit_wave_size (
864896 y_pred , y_true , self .valid_pixel_mask , psi_far .shape [- 2 :]
865897 )
898+ constrained_pixel_mask = self .get_constrained_pixel_mask (
899+ valid_pixel_mask ,
900+ self .exclude_measured_pixels_below ,
901+ y_true ,
902+ )
866903 g = 1 - y_true / (y_pred + self .eps ) # Eq. 12b
867- if valid_pixel_mask is not None :
868- g [:, torch .logical_not (valid_pixel_mask )] = 0
904+ g [torch .logical_not (constrained_pixel_mask )] = 0
869905 g = g [:, None , :, :] * psi_far
870906 return g
0 commit comments