def test_linear_ramp(self): s0, s1 = hs.signals.Signal2D(np.meshgrid(range(100), range(110))) s0.change_dtype("float64") s1.change_dtype("float64") s0_plane = pst._get_linear_plane_from_signal2d(s0) s1_plane = pst._get_linear_plane_from_signal2d(s1) np.testing.assert_almost_equal(s0_plane.data, s0.data) np.testing.assert_almost_equal(s1_plane.data, s1.data)
def make_linear_plane(self, mask=None): """Fit linear planes to the beam shifts, which replaces the original data. In many scanning transmission electron microscopes, the center position of the diffraction pattern will change as a function of the scan position. This is most apparent when scanning over large regions (100+ nanometers). Thus, when working with datasets, it is typically necessary to correct for this. However, other effects can also affect the apparent center point, like diffraction contrast. So while it is possible to correct for the beam shift by finding the center position in each diffraction pattern, this can lead to features such as interfaces or grain boundaries affecting the centering of the diffraction pattern. As the shift caused by the scan system is a slow and monotonic, it can often be approximated by fitting linear planes to the x- and y- beam shifts. In addition, for regions within the scan where the center point of the direct beam is hard to ascertain precisely, for example in very thick or heavily diffracting regions, a mask can be used to ignore fitting the plane to these regions. This method does this, and replaces the original beam shift data with these fitted planes. The beam shift signal can then be directly used in the Diffraction2D.center_direct_beam method. Note that for very large regions, this linear plane will probably not approximate the beam shift very well. In those cases a higher order plane will likely be necessary. Alternatively, a vacuum scan with exactly the same scanning parameters should be used. Parameters ---------- mask : HyperSpy signal, optional Must be the same shape as the navigation dimensions of the beam shift signal. The True values will be masked. Examples -------- >>> s = pxm.signals.BeamShift(np.random.randint(0, 99, (100, 120, 2))) >>> s_mask = hs.signals.Signal2D(np.zeros((100, 120), dtype=bool)) >>> s_mask.data[20:-20, 20:-20] = True >>> s.make_linear_plane(mask=s_mask) """ if self._lazy: raise ValueError( "make_linear_plane is not implemented for lazy signals, " "run compute() first") s_shift_x = self.isig[0].T s_shift_y = self.isig[1].T if mask is not None: mask = mask.__array__() plane_image_x = pst._get_linear_plane_from_signal2d(s_shift_x, mask=mask) plane_image_y = pst._get_linear_plane_from_signal2d(s_shift_y, mask=mask) plane_image = np.stack((plane_image_x, plane_image_y), -1) self.data = plane_image self.events.data_changed.trigger(None)
def test_mask(self): data = np.ones((110, 200), dtype=np.float32) mask = np.zeros_like(data, dtype=bool) data[50, 51] = 10000 mask[50, 51] = True s = hs.signals.Signal2D(data) plane_no_mask = pst._get_linear_plane_from_signal2d(s) plane_mask = pst._get_linear_plane_from_signal2d(s, mask=mask) assert plane_no_mask != approx(1.0) assert plane_mask == approx(1.0)
def correct_ramp(self, corner_size=0.05, only_offset=False, out=None): """Subtract a plane from the signal by fitting a plane to the corners. Useful for removing the effects of the center of the diffraction pattern shifting as a function of scan position. The plane is calculated by fitting a plane to the corner values of the signal. This will only work well when the property one wants to measure is zero in these corners. Parameters ---------- corner_size : number, optional The size of the corners, as a percentage of the image's axis. If corner_size is 0.05 (5%), and the image is 500 x 1000, the size of the corners will be (500*0.05) x (1000*0.05) = 25 x 50. Default 0.05 only_offset : bool, optional If True, will subtract a "flat" plane, i.e. it will subtract the mean value of the corners. Default False out : optional, DPCSignal2D signal Returns ------- corrected_signal : Signal2D Examples -------- >>> s = pxm.dummy_data.get_square_dpc_signal(add_ramp=True) >>> s_corr = s.correct_ramp() >>> s_corr.plot() Only correct offset >>> s_corr = s.correct_ramp(only_offset=True) >>> s_corr.plot() """ if out is None: output = self.deepcopy() else: output = out corner_slice_list = pst._get_corner_slices(self.inav[0], corner_size=corner_size) mask = np.ones_like(self.inav[0].data, dtype=np.bool) for corner_slice in corner_slice_list: mask[corner_slice] = False for i, s in enumerate(self): if only_offset: plane = s.data[np.invert(mask)].mean() else: plane = pst._get_linear_plane_from_signal2d(s, mask=mask) output.data[i, :, :] -= plane if out is None: return output
def test_offest_and_scale(self): s, _ = hs.signals.Signal2D(np.meshgrid(range(100), range(110))) s.axes_manager[0].offset = -40 s.axes_manager[1].offset = 50 s.axes_manager[0].scale = -0.22 s.axes_manager[1].scale = 5.2 s_orig = s.deepcopy() mask = np.zeros_like(s.data, dtype=bool) s.data[50, 51] = 10000 mask[50, 51] = True plane_mask = pst._get_linear_plane_from_signal2d(s, mask=mask) np.testing.assert_almost_equal(plane_mask, s_orig.data)
def test_crop_signal(self): s, _ = hs.signals.Signal2D(np.meshgrid(range(100), range(110))) s.change_dtype("float32") s.axes_manager[0].offset = -40 s.axes_manager[1].offset = 50 s.axes_manager[0].scale = -0.22 s.axes_manager[1].scale = 5.2 s_crop = s.isig[10:-20, 21:-11] s_crop_orig = s_crop.deepcopy() s_crop.data[50, 51] = 10000 mask = np.zeros_like(s_crop.data, dtype=bool) mask[50, 51] = True plane = pst._get_linear_plane_from_signal2d(s_crop, mask=mask) np.testing.assert_almost_equal(plane, s_crop_orig.data, decimal=6)
def test_wrong_input_dimensions(self): s = hs.signals.Signal2D(np.ones((2, 10, 10))) with pytest.raises(ValueError): pst._get_linear_plane_from_signal2d(s) s = hs.signals.Signal2D(np.ones((2, 2, 10, 10))) with pytest.raises(ValueError): pst._get_linear_plane_from_signal2d(s) s = hs.signals.Signal1D(np.ones(10)) with pytest.raises(ValueError): pst._get_linear_plane_from_signal2d(s)
def test_wrong_mask_dimensions(self): s = hs.signals.Signal2D(np.ones((10, 10))) mask = np.zeros((11, 9), dtype=bool) with pytest.raises(ValueError): pst._get_linear_plane_from_signal2d(s, mask=mask)
def test_negative_values(self): data = np.ones((110, 100)) * -10 s = hs.signals.Signal2D(data) s_plane = pst._get_linear_plane_from_signal2d(s) np.testing.assert_almost_equal(s_plane.data, s.data)
def test_ones_values(self): s = hs.signals.Signal2D(np.ones((100, 200), dtype=np.float32)) s_plane = pst._get_linear_plane_from_signal2d(s) np.testing.assert_almost_equal(s_plane.data, s.data)