From 7288ab21372be8555e959a560e6bdebd1e0b8dec Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Tue, 17 Sep 2024 16:59:50 +0200 Subject: [PATCH 01/12] New class structure for readers and image transformations --- pySNOM/images.py | 134 +++++++++++----------- pySNOM/readers.py | 188 ++++++++++++++++++++++++++++--- pySNOM/tests/test_NeaImage.py | 33 ------ pySNOM/tests/test_NeaSpectrum.py | 2 +- pySNOM/tests/test_functions.py | 31 ----- pySNOM/tests/test_process.py | 4 + pySNOM/tests/test_readers.py | 38 +++++++ 7 files changed, 281 insertions(+), 149 deletions(-) delete mode 100644 pySNOM/tests/test_NeaImage.py delete mode 100644 pySNOM/tests/test_functions.py create mode 100644 pySNOM/tests/test_readers.py diff --git a/pySNOM/images.py b/pySNOM/images.py index 72deb8b..cb7d247 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -47,7 +47,7 @@ def setMeasurementModeFromParameters(self): m = self.parameters["Scan"] self.setMeasurementMode(defaults.image_mode_defs[m]) - def getImageFromChannel(self, channelname: str): + def getImageChannel(self, channelname: str): singleimage = Image() singleimage.filename = self.filename @@ -79,7 +79,6 @@ def __init__(self, channeldata = None) -> None: self.yres = None # Pixel size in Y # Overwrite the data definition of parent class self.data = None # Actual data, this case it is only a single channel - self.processor = Process() if channeldata: self.setImageParameters(channeldata) @@ -88,7 +87,6 @@ def __init__(self, channeldata = None) -> None: def setData(self, data): # Set the data self.data = data - self.processor.setData(self.data) def getData(self): # Set the data @@ -121,16 +119,16 @@ def transform(self, data): class LineLevel(Transformation): - def __init__(self, mtype='median', datatype=DataTypes.Phase): - self.mtype = mtype + def __init__(self, method='median', datatype=DataTypes.Phase): + self.method = method self.datatype = datatype def transform(self, data): - if self.mtype == 'median': + if self.method == 'median': norm = np.median(data, axis=1, keepdims=True) - elif self.mtype == 'average': + elif self.method == 'average': norm = np.mean(data, axis=1, keepdims=True) - elif self.mtype == 'difference': + elif self.method == 'difference': if self.datatype == DataTypes.Amplitude: norm = np.median(data[1:] / data[:-1], axis=1, keepdims=True) else: @@ -142,29 +140,67 @@ def transform(self, data): else: return data - norm +class RotatePhase(Transformation): -class Process: - def __init__(self, data = None): - self.data = data - if self.data is not None: - self.output = np.zeros(np.shape(self.data)) + def __init__(self, degree=90.0): + self.degree = degree + + def transform(self, amplitudedata, phasedata): + # Construct complex dataset + complexdata = amplitudedata * np.exp(phasedata*complex(1j)) + # Rotate and extract phase + return np.angle(complexdata*np.exp(np.deg2rad(self.degree)*complex(1j))) + +class SelfReference(Transformation): + + def __init__(self, datatype=DataTypes.Phase): + self.datatype = datatype + + def transform(self, data, referencedata): + if self.datatype == DataTypes.Amplitude: + return np.divide(data, referencedata) + elif self.datatype == DataTypes.Phase: + return data-referencedata else: - self.output = None + # TODO: We should replace this with a datatype error raise + print("Self-referencing makes only sense for amplitude or phase data") - def setData(self, data): - self.data = data - self.output = np.zeros(np.shape(self.data)) +class SimpleNormalize(Transformation): - def getOutput(self): - return self.output - - def getData(self): - return self.data + def __init__(self, method='median', value=1, datatype=DataTypes.Phase): + self.method = method + self.value = value + self.datatype = datatype + + def transform(self, data): + match self.method: + case 'median': + if self.datatype == DataTypes.Amplitude: + return data / np.median(data) + else: + return data - np.median(data) + case 'mean': + if self.datatype == DataTypes.Amplitude: + return data / np.mean(data) + else: + return data - np.mean(data) + case 'manual': + if self.datatype == DataTypes.Amplitude: + return data / self.value + else: + return data - self.value + +class BackgroundPolyFit(Transformation, xorder = int(1), yorder = int(1), datatype = DataTypes.Phase): - def bg_polyfit(self, xorder = int(1), yorder = int(1), datatype = DataTypes.Phase): - Z = copy.deepcopy(self.data) - x = list(range(0, self.data.shape[0])) - y = list(range(0, self.data.shape[1])) + def __init__(self, xorder=int(1), yorder=int(1), datatype=DataTypes.Phase): + self.xorder = xorder + self.yorder = yorder + self.datatype = datatype + + def transform(self, data): + Z = copy.deepcopy(data) + x = list(range(0, Z.shape[0])) + y = list(range(0, Z.shape[1])) X, Y = np.meshgrid(x, y) x, y = X.ravel(), Y.ravel() @@ -177,50 +213,14 @@ def get_basis(x, y, max_order_x=1, max_order_y=1): basis.append(x**j * y**i) return basis - basis = get_basis(x, y, xorder, yorder) + basis = get_basis(x, y, self.xorder, self.yorder) A = np.vstack(basis).T b = Z.ravel() c, r, rank, s = np.linalg.lstsq(A, b, rcond=None) - background = np.sum(c[:, None, None] * np.array(get_basis(X, Y, xorder, yorder)).reshape(len(basis), *X.shape), - axis=0) - - if datatype == DataTypes["Amplitude"]: - self.output = Z/background - else: - self.output = Z-background - - return self.output, background - - def rotate_phase(self, amplitudedata = None, degree = 90.0): - # Construct complex dataset - complexdata = amplitudedata * np.exp(self.data*complex(1j)) - # Rotate and extract phase - self.output = np.angle(complexdata*np.exp(np.deg2rad(degree)*complex(1j))) - - return self.output + background = np.sum(c[:, None, None] * np.array(get_basis(X, Y, self.xorder, self.yorder)).reshape(len(basis), *X.shape),axis=0) - def self_reference(self, refdata = None, datatype = DataTypes.Phase): - if datatype == DataTypes.Amplitude: - self.output = np.divide(self.data, refdata) - elif datatype == DataTypes.Phase: - self.output = self.data-refdata + if self.datatype == DataTypes["Amplitude"]: + return Z/background, background else: - print("Self-referencing makes only sense for amplitude or phase data") - - return self.output - - def normalize_simple(self, method='median', value=1, datatype = DataTypes.Phase): - self.output = np.zeros(np.shape(self.data)) - match method: - case 'median': - if datatype == DataTypes.Amplitude: - self.output = self.data / np.median(self.data) - else: - self.output = self.data - np.median(self.data) - case 'manual': - if datatype == DataTypes.Amplitude: - self.output = self.data / value - else: - self.output = self.data - value - return self.output \ No newline at end of file + return Z-background, background \ No newline at end of file diff --git a/pySNOM/readers.py b/pySNOM/readers.py index 893ade4..39655ca 100644 --- a/pySNOM/readers.py +++ b/pySNOM/readers.py @@ -3,38 +3,44 @@ import numpy as np class Reader: - def __init__(self, fullfilepath): + def __init__(self, fullfilepath=None): self.filename = fullfilepath - def setFilename(self, filename): - self.filename = filename +class GwyReader(Reader): - def read_gwyfile(self): + def __init__(self, fullfilepath=None, channelname=None): + super().__init__(fullfilepath) + self.channelname = channelname + + def read(self): # Returns a dictionary of all the channels gwyobj = gwyfile.load(self.filename) allchannels = gwyfile.util.get_datafields(gwyobj) - return allchannels + if self.channelname == None: + return allchannels + else: + # Read channels from gwyfile and return only a specific one + channel = allchannels[self.channelname] + return channel - def read_gwychannel(self, channelname: str): - # Read channels from gwyfile and return only a specific one - gwyobj = gwyfile.load(self.filename) - channels = gwyfile.util.get_datafields(gwyobj) - channel = channels[channelname] +class GsfReader(Reader): + def __init__(self, fullfilepath=None): + super().__init__(fullfilepath) - return channel - - def read_gsffile(self): + def read(self): data, metadata = gsffile.read_gsf(self.filename) - channel = gwyfile.objects.GwyDataField(data, xreal=metadata["XReal"], yreal=metadata["YReal"], xoff=metadata["XOffset"], yoff=metadata["YOffset"], si_unit_xy=None, si_unit_z=None, typecodes=None) - return channel - def read_nea_infofile(self): +class NeaInfoReader(Reader): + def __init__(self, fullfilepath=None): + super().__init__(fullfilepath) + + def read(self): # reader tested for neascan version 2.1.10719.0 fid = open(self.filename, errors='replace') infodict = {} @@ -84,4 +90,152 @@ def read_nea_infofile(self): except: infodict[fieldname] = val.strip() fid.close() - return infodict \ No newline at end of file + return infodict + +class NeaSpectrumReader(Reader): + def __init__(self, fullfilepath=None): + super().__init__(fullfilepath) + + def read(self): + # reader tested for neascan version 2.1.10719.0 + fid = open(self.filename,errors='replace') + data = {} + params = {} + + linestring = fid.readline() + Nlines = 1 + + while 'Row' not in linestring: + Nlines += 1 + linestring = fid.readline() + if Nlines > 1: + ct = linestring.split('\t') + fieldname = ct[0][2:-1] + fieldname = fieldname.replace(' ', '') + + if 'Scanner Center Position' in linestring: + fieldname = fieldname[:-5] + params[fieldname] = [float(ct[2]), float(ct[3])] + + elif 'Scan Area' in linestring: + fieldname = fieldname[:-7] + params[fieldname] = [float(ct[2]), float(ct[3]), float(ct[4])] + + elif 'Pixel Area' in linestring: + fieldname = fieldname[:-7] + params[fieldname] = [int(ct[2]), int(ct[3]), int(ct[4])] + + elif 'Interferometer Center/Distance' in linestring: + fieldname = fieldname.replace('/', '') + params[fieldname] = [float(ct[2]), float(ct[3])] + + elif 'Regulator' in linestring: + fieldname = fieldname[:-7] + params[fieldname] = [float(ct[2]), float(ct[3]), float(ct[4])] + + elif 'Q-Factor' in linestring: + fieldname = fieldname.replace('-', '') + params[fieldname] = float(ct[2]) + + else: + fieldname = ct[0][2:-1] + fieldname = fieldname.replace(' ', '') + val = ct[2] + val = val.replace(',','') + try: + params[fieldname] = float(val) + except: + params[fieldname] = val.strip() + + channels = linestring.split('\t') + fid.close() + + if "PTE+" in params['Scan']: + C_data = np.genfromtxt(self.filename, skip_header=Nlines, encoding='utf-8') + else: + C_data = np.genfromtxt(self.filename, skip_header=Nlines) + + for i in range(len(channels)-2): + if params['PixelArea'][1] == 1 and params['PixelArea'][0] == 1: + if "PTE+" in params['Scan']: + data[channels[i]] = np.reshape(C_data[:,i], (params['PixelArea'][2])) + else: + data[channels[i]] = np.reshape(C_data[:,i], (params['PixelArea'][2]*2)) + else: + if "PTE+" in params['Scan']: + data[channels[i]] = np.reshape(C_data[:,i], (params['PixelArea'][0], params['PixelArea'][1], params['PixelArea'][2])) + else: + data[channels[i]] = np.reshape(C_data[:,i], (params['PixelArea'][0], params['PixelArea'][1], params['PixelArea'][2]*2)) + return data, params + +class NeaInterferogramReader(Reader): + def __init__(self, fullfilepath=None): + super().__init__(fullfilepath) + + def read(self): + # reader tested for neascan version 2.1.10719.0 + fid = open(self.filename,errors='replace') + data = {} + params = {} + + linestring = fid.readline() + Nlines = 1 + + while 'Row' not in linestring: + Nlines += 1 + linestring = fid.readline() + if Nlines > 1: + ct = linestring.split('\t') + fieldname = ct[0][2:-1] + fieldname = fieldname.replace(' ', '') + + if 'Scanner Center Position' in linestring: + fieldname = fieldname[:-5] + params[fieldname] = [float(ct[2]), float(ct[3])] + + elif 'Scan Area' in linestring: + fieldname = fieldname[:-7] + params[fieldname] = [float(ct[2]), float(ct[3]), float(ct[4])] + + elif 'Pixel Area' in linestring: + fieldname = fieldname[:-7] + params[fieldname] = [int(ct[2]), int(ct[3]), int(ct[4])] + + elif 'Averaging' in linestring: + # fieldname = fieldname[:-7] + params[fieldname] = int(ct[2]) + + elif 'Interferometer Center/Distance' in linestring: + fieldname = fieldname.replace('/', '') + params[fieldname] = [float(ct[2]), float(ct[3])] + + elif 'Regulator' in linestring: + fieldname = fieldname[:-7] + params[fieldname] = [float(ct[2]), float(ct[3]), float(ct[4])] + + elif 'Q-Factor' in linestring: + fieldname = fieldname.replace('-', '') + params[fieldname] = float(ct[2]) + + else: + fieldname = ct[0][2:-1] + fieldname = fieldname.replace(' ', '') + val = ct[2] + val = val.replace(',','') + try: + params[fieldname] = float(val) + except: + params[fieldname] = val.strip() + + channels = linestring.split('\t') + fid.close() + + C_data = np.genfromtxt(self.filename, skip_header=Nlines) + + for i in range(len(channels)-2): + if params['PixelArea'][1] == 1 and params['PixelArea'][0] == 1: + data[channels[i]] = C_data[:,i] + else: + data[channels[i]] = np.reshape(C_data[:,i], (int(params['PixelArea'][0]), int(params['PixelArea'][1]), int(params['PixelArea'][2]*params['Averaging']))) + + return data, params \ No newline at end of file diff --git a/pySNOM/tests/test_NeaImage.py b/pySNOM/tests/test_NeaImage.py deleted file mode 100644 index 4f63245..0000000 --- a/pySNOM/tests/test_NeaImage.py +++ /dev/null @@ -1,33 +0,0 @@ -import unittest -import os - -import numpy as np - -import pySNOM -from pySNOM import Reader, Image - -class test_NeaImage(unittest.TestCase): - def test_readfile(self): - f = 'datasets/testPsHetData.gwy' - file_reader = Reader(os.path.join(pySNOM.__path__[0], f)) - channeldata = file_reader.read_gwychannel('O3A raw') - - im = Image(channeldata=channeldata) - - np.testing.assert_almost_equal(im.data[7][7], 15.213262557983398) - np.testing.assert_almost_equal(im.data[8][8], 15.736936569213867) - np.testing.assert_almost_equal(im.data[9][9], 13.609171867370605) - - def test_readgsf(self): - f = 'datasets/testPsHet O3A raw.gsf' - file_reader = Reader(os.path.join(pySNOM.__path__[0], f)) - channeldata = file_reader.read_gsffile() - im = Image(channeldata=channeldata) - im.setChannel('O3A raw') - - np.testing.assert_almost_equal(im.data[7][7], 15.213262557983398) - np.testing.assert_almost_equal(im.data[8][8], 15.736936569213867) - np.testing.assert_almost_equal(im.data[9][9], 13.609171867370605) - -if __name__ == '__main__': - unittest.main() diff --git a/pySNOM/tests/test_NeaSpectrum.py b/pySNOM/tests/test_NeaSpectrum.py index 10de34f..cedeb70 100644 --- a/pySNOM/tests/test_NeaSpectrum.py +++ b/pySNOM/tests/test_NeaSpectrum.py @@ -16,4 +16,4 @@ def test_readfile(self): if __name__ == '__main__': - unittest.main() + unittest.main() \ No newline at end of file diff --git a/pySNOM/tests/test_functions.py b/pySNOM/tests/test_functions.py deleted file mode 100644 index b10c9ac..0000000 --- a/pySNOM/tests/test_functions.py +++ /dev/null @@ -1,31 +0,0 @@ -import unittest -import os - -import numpy as np - -import pySNOM -from pySNOM import Image, Reader - - -class test_public_functions(unittest.TestCase): - def test_normalize_simple(self): - f = 'datasets/image.gwy' - file_reader = Reader(os.path.join(pySNOM.__path__[0], f)) - channeldata = file_reader.read_gwychannel('M1A raw') - - im = Image(channeldata=channeldata) - im.setChannel('M1A raw') - - res = im.processor.normalize_simple(datatype=pySNOM.images.DataTypes["Amplitude"]) - - np.testing.assert_almost_equal(res[4][8], 0.9997333773568211) - np.testing.assert_almost_equal(res[7][5], 1.0286144038809726) - - def self_reference(self): - # TODO for testing these we need to have a better test file with actual optical data - - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/pySNOM/tests/test_process.py b/pySNOM/tests/test_process.py index 57c5698..139f136 100644 --- a/pySNOM/tests/test_process.py +++ b/pySNOM/tests/test_process.py @@ -43,3 +43,7 @@ def test_difference(self): out = l.transform(d) np.testing.assert_almost_equal(out, [[0. , 0.2 , 0.6 ], [2.2222222, 2.7777778, 3.8888889]]) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/pySNOM/tests/test_readers.py b/pySNOM/tests/test_readers.py new file mode 100644 index 0000000..d9c3682 --- /dev/null +++ b/pySNOM/tests/test_readers.py @@ -0,0 +1,38 @@ +import unittest +import os + +import numpy as np + +import pySNOM +from pySNOM import readers, Image + +class TestReaders(unittest.TestCase): + def test_gwyread(self): + f = 'datasets/testPsHetData.gwy' + file_reader = readers.GwyReader(fullfilepath=os.path.join(pySNOM.__path__[0], f),channelname='O3A raw') + data = file_reader.read() + + np.testing.assert_almost_equal(data[7][7], 15.213262557983398) + np.testing.assert_almost_equal(data[8][8], 15.736936569213867) + np.testing.assert_almost_equal(data[9][9], 13.609171867370605) + + def test_readgsf(self): + f = 'datasets/testPsHet O3A raw.gsf' + file_reader = readers.GsfReader(os.path.join(pySNOM.__path__[0], f)) + data = file_reader.read() + + np.testing.assert_almost_equal(data[7][7], 15.213262557983398) + np.testing.assert_almost_equal(data[8][8], 15.736936569213867) + np.testing.assert_almost_equal(data[9][9], 13.609171867370605) + + def test_inforead(self): + f = 'datasets/testinfofile.txt' + file_reader = readers.NeaInfoReader(os.path.join(pySNOM.__path__[0], f)) + data = file_reader.read() + + np.testing.assert_approx_equal(data["ScanArea"][0],5.000) + np.testing.assert_almost_equal(data["TargetWavelength"], 6.006) + np.testing.assert_string_equal(data["DemodulationMode"],"PsHet") + +if __name__ == '__main__': + unittest.main() From c041fd17f30f712330d28bcf4179a20a74e7db3f Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Tue, 17 Sep 2024 17:46:00 +0200 Subject: [PATCH 02/12] Some minor correction + measurement class restructuring started --- pySNOM/__init__.py | 4 ++-- pySNOM/images.py | 55 +++++++++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/pySNOM/__init__.py b/pySNOM/__init__.py index 0ff9371..186c546 100644 --- a/pySNOM/__init__.py +++ b/pySNOM/__init__.py @@ -4,7 +4,7 @@ Scanning Near-field Optical Microscopy (SNOM) analysis tools """ from .defaults import defaults -from .images import Image, Process +from .images import Image, Transformation from .readers import Reader from .spectra import NeaSpectrum, NeaInterferogram # import images @@ -14,4 +14,4 @@ __version__ = '0.0.2' __author__ = 'Gergely NĂ©meth, Ferenc Borondics' __credits__ = 'Wigner Research Centre for Physics, Synchrotron SOLEIL' -__all__ = ["defaults","Reader","Image","Process","NeaSpectrum","NeaInterferogram"] +__all__ = ["defaults","Reader","Image","Transformation","NeaSpectrum","NeaInterferogram"] diff --git a/pySNOM/images.py b/pySNOM/images.py index cb7d247..0954aca 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -8,35 +8,50 @@ ChannelTypes = Enum('ChannelTypes', ['Optical','Mechanical']) # Full measurement data containing all the measurement channels -class Data: - def __init__(self, filename = None, data = None, parameters = None, mode = "None"): +class Measurement: + def __init__(self, filename = None, data = None, info = None, mode = "None"): self.filename = filename # Full path with name # Measurement mode (PTE, PSHet, AFM, NanoFTIR) - Enum MeasurementModes self.mode = MeasurementModes[mode] # Full data from the gwy files with all the channels - self.data = data # All channels - Dictionary + self._data = data # All channels - Dictionary # Other parameters from info txt - Dictionary - if parameters is not None: - self.setParameters(parameters) + if info is not None: + self.setParameters(info) else: - self.parameters = parameters - - # Method to set the actual data read from the gwyddion or gsf files - def setData(self, data: dict): - self.data = data - - # Method to set measurement mode directly - def setMeasurementMode(self, measmode: str): - self.mode = MeasurementModes[measmode] - + self._info = info + + @property + def mode(self): + return self._mode + @mode.setter + def mode(self, value: str): + try: + self._mode = MeasurementModes[value] + except ValueError: + self._mode = MeasurementModes["AFM"] + raise ValueError(value + 'is not a measurement mode!') + + @property + def data(self): + return self._data + @data.setter + def data(self, data: dict): + self._data = data + + # @property + # def info(self): + # re + +# METHODS -------------------------------------------------------------------------------------- # Method to retrieve e specific channel from all the measurement channels - def getChannelData(self, channelname: str): + def get_channel(self, channelname: str): channel = self.data[channelname] return channel # Method to set the additional informations about the measurement from the neaspec info txt def setParameters(self, infodict: dict): - self.parameters = infodict + self.info = infodict self.setMeasurementModeFromParameters() # Method to set the @@ -45,7 +60,7 @@ def setMeasurementModeFromParameters(self): print('Load the info file first') else: m = self.parameters["Scan"] - self.setMeasurementMode(defaults.image_mode_defs[m]) + self.mode = defaults.image_mode_defs[m] def getImageChannel(self, channelname: str): singleimage = Image() @@ -63,7 +78,7 @@ def getImageChannel(self, channelname: str): # Single image from a single data channel -class Image(Data): +class Image(Measurement): def __init__(self, channeldata = None) -> None: super().__init__() # Describing channel and datatype @@ -190,7 +205,7 @@ def transform(self, data): else: return data - self.value -class BackgroundPolyFit(Transformation, xorder = int(1), yorder = int(1), datatype = DataTypes.Phase): +class BackgroundPolyFit(Transformation): def __init__(self, xorder=int(1), yorder=int(1), datatype=DataTypes.Phase): self.xorder = xorder From 775bcb5319a212a7a2ddcc0373e87388c5d75b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9meth=20Gergely?= Date: Tue, 17 Sep 2024 18:26:39 +0200 Subject: [PATCH 03/12] Small correction due to changed argumentname --- pySNOM/tests/{test_process.py => test_transform.py} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename pySNOM/tests/{test_process.py => test_transform.py} (81%) diff --git a/pySNOM/tests/test_process.py b/pySNOM/tests/test_transform.py similarity index 81% rename from pySNOM/tests/test_process.py rename to pySNOM/tests/test_transform.py index 139f136..ccf6258 100644 --- a/pySNOM/tests/test_process.py +++ b/pySNOM/tests/test_transform.py @@ -9,12 +9,12 @@ class TestLineLevel(unittest.TestCase): def test_median(self): d = np.arange(12).reshape(3, -1)[:, [0, 1, 3]] - l = LineLevel(mtype="median", datatype=DataTypes.Phase) + l = LineLevel(method="median", datatype=DataTypes.Phase) out = l.transform(d) np.testing.assert_almost_equal(out, [[-1., 0., 2.], [-1., 0., 2.], [-1., 0., 2.]]) - l = LineLevel(mtype="median", datatype=DataTypes.Amplitude) + l = LineLevel(method="median", datatype=DataTypes.Amplitude) out = l.transform(d) np.testing.assert_almost_equal(out, [[0. , 1. , 3. ], [0.8 , 1. , 1.4 ], @@ -22,12 +22,12 @@ def test_median(self): def test_mean(self): d = np.arange(12).reshape(3, -1)[:, [0, 1, 3]] - l = LineLevel(mtype="average", datatype=DataTypes.Phase) + l = LineLevel(method="average", datatype=DataTypes.Phase) out = l.transform(d) np.testing.assert_almost_equal(out, [[-1.3333333, -0.3333333, 1.6666667], [-1.3333333, -0.3333333, 1.6666667], [-1.3333333, -0.3333333, 1.6666667]]) - l = LineLevel(mtype="average", datatype=DataTypes.Amplitude) + l = LineLevel(method="average", datatype=DataTypes.Amplitude) out = l.transform(d) np.testing.assert_almost_equal(out, [[0. , 0.75 , 2.25 ], [0.75 , 0.9375 , 1.3125 ], @@ -35,11 +35,11 @@ def test_mean(self): def test_difference(self): d = np.arange(12).reshape(3, -1)[:, [0, 1, 3]] - l = LineLevel(mtype="difference", datatype=DataTypes.Phase) + l = LineLevel(method="difference", datatype=DataTypes.Phase) out = l.transform(d) np.testing.assert_almost_equal(out, [[-4., -3., -1.], [ 0., 1., 3.]]) - l = LineLevel(mtype="difference", datatype=DataTypes.Amplitude) + l = LineLevel(method="difference", datatype=DataTypes.Amplitude) out = l.transform(d) np.testing.assert_almost_equal(out, [[0. , 0.2 , 0.6 ], [2.2222222, 2.7777778, 3.8888889]]) From b7a0e9d6d1d197f62b271d717a2415c382a88543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9meth=20Gergely?= Date: Tue, 17 Sep 2024 18:53:16 +0200 Subject: [PATCH 04/12] Reader tests corrected for the new classes --- pySNOM/examples/imageprocess.ipynb | 61 +++++++++++------------------- pySNOM/tests/test_readers.py | 16 ++++---- 2 files changed, 30 insertions(+), 47 deletions(-) diff --git a/pySNOM/examples/imageprocess.ipynb b/pySNOM/examples/imageprocess.ipynb index a2bbd96..616d04e 100644 --- a/pySNOM/examples/imageprocess.ipynb +++ b/pySNOM/examples/imageprocess.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -24,59 +24,42 @@ ], "source": [ "import numpy as np\n", - "from pySNOM.readers import Reader\n", - "from pySNOM.images import Image, Data, DataTypes\n", + "import pySNOM\n", + "from pySNOM import readers\n", + "import os\n", "\n", - "filename = r'C:\\Users\\NEMETHG\\OneDrive\\Python\\pySNOM\\pySNOM\\datasets\\testPsHetData.gwy'\n", + "f = 'datasets/testPsHetData.gwy'\n", + "file_reader = readers.GwyReader(fullfilepath=os.path.join(pySNOM.__path__[0], f),channelname='O3A raw')\n", + "gwy_image = file_reader.read()\n", "\n", - "file_reader = Reader(filename)\n", - "# From raw data black\n", - "# Reads all the files\n", - "rawgwydata = file_reader.read_gwyfile()\n", - "measurement = Data(filename = filename, data = rawgwydata, mode = 'PsHet')\n", - "O3Aimage = measurement.getImageFromChannel('O3A raw')\n", - "\n", - "# From a single channel\n", - "channel_name = 'O3A raw'\n", - "O3Achanneldata = file_reader.read_gwychannel(channel_name)\n", - "O3Aimage = Image(O3Achanneldata)\n", - "O3Aimage.setChannel(channel_name)\n", - "print(f'Pixel 7-7: {O3Aimage.data[7][7]}')\n", - "print(f'Pixel 8-8: {O3Aimage.data[8][8]}')\n", - "print(f'Pixel 8-9: {O3Aimage.data[9][9]}')\n", - "\n", - "# Read the info file\n", - "info_filename = r'C:\\Users\\NEMETHG\\OneDrive\\Python\\pySNOM\\pySNOM\\datasets\\testinfofile.txt'\n", - "file_reader.setFilename(info_filename)\n", - "infodict = file_reader.read_nea_infofile()\n", - "# Set parameters from the info dictionary\n", - "O3Aimage.setParameters(infodict=infodict)" + "print(f'Pixel 7-7: {gwy_image.data[7][7]}')\n", + "print(f'Pixel 8-8: {gwy_image.data[8][8]}')\n", + "print(f'Pixel 8-9: {gwy_image.data[9][9]}')" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " Measurement mode: MeasurementModes.PsHet\n", - " Channel: O2A raw\n", - " Data type: DataTypes.Amplitude\n", - " Demod order: 2\n", - "X resolution: 200\n", - "Data size: (200, 200)\n", - "PLT-EV-niceplace_spectrum_1665_cm-1\n" + "Pixel 7-7: 15.213262557983398\n", + "Pixel 8-8: 15.736936569213867\n", + "Pixel 8-9: 13.609171867370605\n" ] } ], "source": [ - "print(f' Measurement mode: {O3Aimage.mode}\\n Channel: {O3Aimage.channel}\\n Data type: {O3Aimage.datatype}\\n Demod order: {O3Aimage.order}')\n", - "print(f'X resolution: {O3Aimage.xres}')\n", - "print(f'Data size: {np.shape(O3Aimage.data)}')\n", - "print(O3Aimage.parameters['Description'])" + "f = 'datasets/testPsHet O3A raw.gsf'\n", + "file_reader = readers.GsfReader(os.path.join(pySNOM.__path__[0], f))\n", + "gwy_image = file_reader.read()\n", + "\n", + "print(f'Pixel 7-7: {gwy_image.data[7][7]}')\n", + "print(f'Pixel 8-8: {gwy_image.data[8][8]}')\n", + "print(f'Pixel 8-9: {gwy_image.data[9][9]}')" ] }, { @@ -190,7 +173,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.0" + "version": "3.11.6" } }, "nbformat": 4, diff --git a/pySNOM/tests/test_readers.py b/pySNOM/tests/test_readers.py index d9c3682..f631a51 100644 --- a/pySNOM/tests/test_readers.py +++ b/pySNOM/tests/test_readers.py @@ -10,20 +10,20 @@ class TestReaders(unittest.TestCase): def test_gwyread(self): f = 'datasets/testPsHetData.gwy' file_reader = readers.GwyReader(fullfilepath=os.path.join(pySNOM.__path__[0], f),channelname='O3A raw') - data = file_reader.read() + gwy_image = file_reader.read() - np.testing.assert_almost_equal(data[7][7], 15.213262557983398) - np.testing.assert_almost_equal(data[8][8], 15.736936569213867) - np.testing.assert_almost_equal(data[9][9], 13.609171867370605) + np.testing.assert_almost_equal(gwy_image.data[7][7], 15.213262557983398) + np.testing.assert_almost_equal(gwy_image.data[8][8], 15.736936569213867) + np.testing.assert_almost_equal(gwy_image.data[9][9], 13.609171867370605) def test_readgsf(self): f = 'datasets/testPsHet O3A raw.gsf' file_reader = readers.GsfReader(os.path.join(pySNOM.__path__[0], f)) - data = file_reader.read() + gwy_image = file_reader.read() - np.testing.assert_almost_equal(data[7][7], 15.213262557983398) - np.testing.assert_almost_equal(data[8][8], 15.736936569213867) - np.testing.assert_almost_equal(data[9][9], 13.609171867370605) + np.testing.assert_almost_equal(gwy_image.data[7][7], 15.213262557983398) + np.testing.assert_almost_equal(gwy_image.data[8][8], 15.736936569213867) + np.testing.assert_almost_equal(gwy_image.data[9][9], 13.609171867370605) def test_inforead(self): f = 'datasets/testinfofile.txt' From 87e504a39899806c368ae68080bf14fc295009ce Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Wed, 18 Sep 2024 11:47:03 +0200 Subject: [PATCH 05/12] Property decorators for Image and Measurement class --- pySNOM/images.py | 148 +++++++++++++++++++++++------------------------ 1 file changed, 71 insertions(+), 77 deletions(-) diff --git a/pySNOM/images.py b/pySNOM/images.py index 0954aca..7f119bf 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -1,7 +1,8 @@ import numpy as np import copy from enum import Enum -import pySNOM.defaults as defaults +from pySNOM.defaults import Defaults +from gwyfile.objects import GwyDataField MeasurementModes = Enum('MeasurementModes', ['None','AFM', 'PsHet', 'WLI', 'PTE', 'TappingAFMIR', 'ContactAFM']) DataTypes = Enum('DataTypes', ['Amplitude', 'Phase', 'Topography']) @@ -10,19 +11,14 @@ # Full measurement data containing all the measurement channels class Measurement: def __init__(self, filename = None, data = None, info = None, mode = "None"): - self.filename = filename # Full path with name - # Measurement mode (PTE, PSHet, AFM, NanoFTIR) - Enum MeasurementModes - self.mode = MeasurementModes[mode] - # Full data from the gwy files with all the channels + self.filename = filename # Full path with name + self.mode = mode # Measurement mode (PTE, PSHet, AFM, NanoFTIR) - Enum MeasurementModes self._data = data # All channels - Dictionary - # Other parameters from info txt - Dictionary - if info is not None: - self.setParameters(info) - else: - self._info = info + self.info = info @property def mode(self): + """Property - measurement mode (Enum)""" return self._mode @mode.setter def mode(self, value: str): @@ -34,98 +30,96 @@ def mode(self, value: str): @property def data(self): + """Property - data (dict with GwyDataFields)""" return self._data @data.setter def data(self, data: dict): self._data = data - # @property - # def info(self): - # re - -# METHODS -------------------------------------------------------------------------------------- - # Method to retrieve e specific channel from all the measurement channels - def get_channel(self, channelname: str): + @property + def info(self): + """Property - info (dictionary)""" + return self._info + + @info.setter + def info(self, info): + self._info = info + if not info == None: + m = self._info["Scan"] + self.mode = Defaults.image_mode_defs[m] + + # METHODS -------------------------------------------------------------------------------------- + def extract_channel(self, channelname: str): + """Returns a single data channel as GwyDataField""" channel = self.data[channelname] return channel - - # Method to set the additional informations about the measurement from the neaspec info txt - def setParameters(self, infodict: dict): - self.info = infodict - self.setMeasurementModeFromParameters() - - # Method to set the - def setMeasurementModeFromParameters(self): - if self.parameters == None: - print('Load the info file first') - else: - m = self.parameters["Scan"] - self.mode = defaults.image_mode_defs[m] - - def getImageChannel(self, channelname: str): - singleimage = Image() - singleimage.filename = self.filename - singleimage.mode = self.mode - singleimage.parameters = self.parameters - singleimage.channel = channelname + def image_from_channel(self, channelname: str): + """Returns a single Image object with the requred channeldata""" + channeldata = self.extract_channel(channelname) + singleimage = Image(filename = self.filename, data = channeldata, mode = self.mode, channel = channelname, info = self.info) - channeldata = self.getChannelData(channelname) - singleimage.setImageParameters(channeldata) - singleimage.setChannel(channelname=channelname) + # channeldata = self.extract_channel(channelname) + # singleimage.setImageParameters(channeldata) + # singleimage.setChannel(channelname=channelname) return singleimage # Single image from a single data channel class Image(Measurement): - def __init__(self, channeldata = None) -> None: + def __init__(self, filename = None, data = None, mode = "AFM", channelname = 'Z raw', order = 0, datatype = DataTypes['Topography']): super().__init__() # Describing channel and datatype - self.channel = None # Full channel name - self.order = None # Order, nth - self.datatype = None # Amplitude, Phase, Topography - Enum DataTypes - # Important measurement parameters from gwyddion fields - self.xreal = None # Physical image width - self.yreal = None # Physical image height - self.xoff = None # Center position X - self.yoff = None # Center position Y - self.xres = None # Pixel size in X - self.yres = None # Pixel size in Y - # Overwrite the data definition of parent class - self.data = None # Actual data, this case it is only a single channel - - if channeldata: - self.setImageParameters(channeldata) - - # Method to set the actual data (numpy array) - overwrite parent class method - def setData(self, data): - # Set the data - self.data = data - - def getData(self): + self.channel = channelname # Full channel name + self.order = order # Order, nth + self.datatype = datatype # Amplitude, Phase, Topography - Enum DataTypes + self.data = data # Actual data, this case it is only a single channel + + @property + def data(self): + """Property - data (numpy array)""" # Set the data - return self.data + return self._data - def setChannel(self, channelname): - self.channel = channelname - self.order = int(self.channel[1]) + @data.setter + def data(self, value): + # Set the data and the corresponding image attributes + if value is GwyDataField: + self._data = value.data + self._xres, self._yres = np.shape(value.data) + self._xoff = value.xoff + self._yoff = value.yoff + self._xreal = value.xreal + self._yreal = value.yreal + elif value is np.ndarray: + self._data = value + self._xres, self._yres = np.shape(value.data) + self._xoff = 0 + self._yoff = 0 + self._xreal = 1 + self._yreal = 1 + else: + self._data = None - if self.channel[2] == 'P': + @property + def channel(self): + """Property - channel (string)""" + return self._channel + + @channel.setter + def channel(self,value): + + self._channel = value + self.order = int(value[1]) + + if value[2] == 'P': self.datatype = DataTypes["Phase"] - elif 'Z' in self.channel: + elif 'Z' in value: self.datatype = DataTypes["Topography"] else: self.datatype = DataTypes["Amplitude"] - def setImageParameters(self, singlechannel): - # Set the basic attributes from gwyddion field - for key in singlechannel: - if key in dir(self): - setattr(self, key, singlechannel[key]) - self.setData(data=singlechannel.data) - - class Transformation: def transform(self, data): From 6a983c7213da010f61886f37ee25a6285fa6d45c Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Wed, 18 Sep 2024 13:32:26 +0200 Subject: [PATCH 06/12] Small corrections --- pySNOM/images.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pySNOM/images.py b/pySNOM/images.py index 7f119bf..20267c4 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -59,20 +59,16 @@ def image_from_channel(self, channelname: str): channeldata = self.extract_channel(channelname) singleimage = Image(filename = self.filename, data = channeldata, mode = self.mode, channel = channelname, info = self.info) - # channeldata = self.extract_channel(channelname) - # singleimage.setImageParameters(channeldata) - # singleimage.setChannel(channelname=channelname) - return singleimage # Single image from a single data channel class Image(Measurement): - def __init__(self, filename = None, data = None, mode = "AFM", channelname = 'Z raw', order = 0, datatype = DataTypes['Topography']): + def __init__(self, filename = None, data = None, mode = "AFM", channelname = 'Z raw', order = int(0), datatype = DataTypes['Topography']): super().__init__() # Describing channel and datatype self.channel = channelname # Full channel name - self.order = order # Order, nth + self.order = int(order) # Order, nth self.datatype = datatype # Amplitude, Phase, Topography - Enum DataTypes self.data = data # Actual data, this case it is only a single channel From 8ed7e58cc833bc6f8db6d9aa707d8d18ad59911b Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Thu, 19 Sep 2024 13:26:14 +0200 Subject: [PATCH 07/12] Small suggested changes --- pySNOM/images.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/pySNOM/images.py b/pySNOM/images.py index 20267c4..b768d89 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -10,7 +10,7 @@ # Full measurement data containing all the measurement channels class Measurement: - def __init__(self, filename = None, data = None, info = None, mode = "None"): + def __init__(self, data, filename=None, info=None, mode="None"): self.filename = filename # Full path with name self.mode = mode # Measurement mode (PTE, PSHet, AFM, NanoFTIR) - Enum MeasurementModes self._data = data # All channels - Dictionary @@ -64,7 +64,7 @@ def image_from_channel(self, channelname: str): # Single image from a single data channel class Image(Measurement): - def __init__(self, filename = None, data = None, mode = "AFM", channelname = 'Z raw', order = int(0), datatype = DataTypes['Topography']): + def __init__(self, filename=None, data=None, mode="AFM", channelname='Z raw', order=0, datatype=DataTypes['Topography']): super().__init__() # Describing channel and datatype self.channel = channelname # Full channel name @@ -131,7 +131,7 @@ def __init__(self, method='median', datatype=DataTypes.Phase): def transform(self, data): if self.method == 'median': norm = np.median(data, axis=1, keepdims=True) - elif self.method == 'average': + elif self.method == 'mean': norm = np.mean(data, axis=1, keepdims=True) elif self.method == 'difference': if self.datatype == DataTypes.Amplitude: @@ -150,25 +150,24 @@ class RotatePhase(Transformation): def __init__(self, degree=90.0): self.degree = degree - def transform(self, amplitudedata, phasedata): + def transform(self, data): # Construct complex dataset - complexdata = amplitudedata * np.exp(phasedata*complex(1j)) + complexdata = np.exp(data*complex(1j)) # Rotate and extract phase return np.angle(complexdata*np.exp(np.deg2rad(self.degree)*complex(1j))) class SelfReference(Transformation): - def __init__(self, datatype=DataTypes.Phase): + def __init__(self, referencedata=1, datatype=DataTypes.Phase): self.datatype = datatype - - def transform(self, data, referencedata): + self.referencedata = referencedata + def transform(self, data): if self.datatype == DataTypes.Amplitude: - return np.divide(data, referencedata) + return np.divide(data, self.referencedata) elif self.datatype == DataTypes.Phase: - return data-referencedata + return data-self.referencedata else: - # TODO: We should replace this with a datatype error raise - print("Self-referencing makes only sense for amplitude or phase data") + raise RuntimeError("Self-referencing makes only sense for amplitude or phase data") class SimpleNormalize(Transformation): @@ -181,23 +180,23 @@ def transform(self, data): match self.method: case 'median': if self.datatype == DataTypes.Amplitude: - return data / np.median(data) + return np.divide(data, np.median(data)) else: return data - np.median(data) case 'mean': if self.datatype == DataTypes.Amplitude: - return data / np.mean(data) + return np.divide(data, np.mean(data)) else: return data - np.mean(data) case 'manual': if self.datatype == DataTypes.Amplitude: - return data / self.value + return np.divide(data, self.value) else: return data - self.value class BackgroundPolyFit(Transformation): - def __init__(self, xorder=int(1), yorder=int(1), datatype=DataTypes.Phase): + def __init__(self, xorder=1, yorder=1, datatype=DataTypes.Phase): self.xorder = xorder self.yorder = yorder self.datatype = datatype @@ -226,6 +225,6 @@ def get_basis(x, y, max_order_x=1, max_order_y=1): background = np.sum(c[:, None, None] * np.array(get_basis(X, Y, self.xorder, self.yorder)).reshape(len(basis), *X.shape),axis=0) if self.datatype == DataTypes["Amplitude"]: - return Z/background, background + return np.divide(Z, background), background else: return Z-background, background \ No newline at end of file From f4ae331cc42e0006d0b35c4208a639c05e708692 Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Thu, 19 Sep 2024 13:56:34 +0200 Subject: [PATCH 08/12] Correction of handling wrong method argument --- pySNOM/images.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pySNOM/images.py b/pySNOM/images.py index b768d89..195b701 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -139,6 +139,11 @@ def transform(self, data): else: norm = np.median(data[1:] - data[:-1], axis=1, keepdims=True) data = data[:-1] # difference does not make sense for the last row + else: + if self.datatype == DataTypes.Amplitude: + norm = 1 + else: + norm = 0 if self.datatype == DataTypes.Amplitude: return data / norm From 6cf29da5daad964c06d707523dc6d3115987e3b5 Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Thu, 19 Sep 2024 14:02:02 +0200 Subject: [PATCH 09/12] Cirrecting test calls for transform --- pySNOM/tests/test_transform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pySNOM/tests/test_transform.py b/pySNOM/tests/test_transform.py index ccf6258..1f476c9 100644 --- a/pySNOM/tests/test_transform.py +++ b/pySNOM/tests/test_transform.py @@ -22,12 +22,12 @@ def test_median(self): def test_mean(self): d = np.arange(12).reshape(3, -1)[:, [0, 1, 3]] - l = LineLevel(method="average", datatype=DataTypes.Phase) + l = LineLevel(method="mean", datatype=DataTypes.Phase) out = l.transform(d) np.testing.assert_almost_equal(out, [[-1.3333333, -0.3333333, 1.6666667], [-1.3333333, -0.3333333, 1.6666667], [-1.3333333, -0.3333333, 1.6666667]]) - l = LineLevel(method="average", datatype=DataTypes.Amplitude) + l = LineLevel(method="mean", datatype=DataTypes.Amplitude) out = l.transform(d) np.testing.assert_almost_equal(out, [[0. , 0.75 , 2.25 ], [0.75 , 0.9375 , 1.3125 ], From a46fa781a58dc5654d891ee57b9496a02e1e5248 Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Thu, 19 Sep 2024 14:46:00 +0200 Subject: [PATCH 10/12] New NeaImage class, removed data setter --- pySNOM/images.py | 53 +++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/pySNOM/images.py b/pySNOM/images.py index 195b701..153266d 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -32,9 +32,6 @@ def mode(self, value: str): def data(self): """Property - data (dict with GwyDataFields)""" return self._data - @data.setter - def data(self, data: dict): - self._data = data @property def info(self): @@ -70,27 +67,11 @@ def __init__(self, filename=None, data=None, mode="AFM", channelname='Z raw', or self.channel = channelname # Full channel name self.order = int(order) # Order, nth self.datatype = datatype # Amplitude, Phase, Topography - Enum DataTypes - self.data = data # Actual data, this case it is only a single channel + self._data = data # Actual data, this case it is only a single channel - @property - def data(self): - """Property - data (numpy array)""" - # Set the data - return self._data - - @data.setter - def data(self, value): - # Set the data and the corresponding image attributes - if value is GwyDataField: - self._data = value.data - self._xres, self._yres = np.shape(value.data) - self._xoff = value.xoff - self._yoff = value.yoff - self._xreal = value.xreal - self._yreal = value.yreal - elif value is np.ndarray: - self._data = value - self._xres, self._yres = np.shape(value.data) + if data is np.ndarray: + self._data = data + self._xres, self._yres = np.shape(data) self._xoff = 0 self._yoff = 0 self._xreal = 1 @@ -98,6 +79,12 @@ def data(self, value): else: self._data = None + @property + def data(self): + """Property - data (numpy array)""" + # Set the data + return self._data + @property def channel(self): """Property - channel (string)""" @@ -116,6 +103,26 @@ def channel(self,value): else: self.datatype = DataTypes["Amplitude"] +class NeaImage(Image): + def __init__(self, filename=None, data=None, mode="AFM", channelname='Z raw', order=0, datatype=DataTypes['Topography']): + super().__init__(filename, data, mode, channelname, order, datatype) + + if data is GwyDataField: + self._data = data.data + self._xres, self._yres = np.shape(data.data) + self._xoff = data.xoff + self._yoff = data.yoff + self._xreal = data.xreal + self._yreal = data.yreal + else: + self._data = None + + @property + def data(self): + """Property - data (numpy array)""" + # Set the data + return self._data + class Transformation: def transform(self, data): From 3bb68169d5693a3425775f2e7ac8e0be1fe15ec8 Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Thu, 19 Sep 2024 14:50:02 +0200 Subject: [PATCH 11/12] Renaming NeaImage to GwyImage --- pySNOM/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pySNOM/images.py b/pySNOM/images.py index 153266d..c29bab5 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -103,7 +103,7 @@ def channel(self,value): else: self.datatype = DataTypes["Amplitude"] -class NeaImage(Image): +class GwyImage(Image): def __init__(self, filename=None, data=None, mode="AFM", channelname='Z raw', order=0, datatype=DataTypes['Topography']): super().__init__(filename, data, mode, channelname, order, datatype) From bf03937d1f0eea4c53b1b0454e6e1e03edf04c2e Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Thu, 19 Sep 2024 15:42:56 +0200 Subject: [PATCH 12/12] Unnecessary if removed --- pySNOM/images.py | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/pySNOM/images.py b/pySNOM/images.py index c29bab5..d9333e8 100644 --- a/pySNOM/images.py +++ b/pySNOM/images.py @@ -61,23 +61,19 @@ def image_from_channel(self, channelname: str): # Single image from a single data channel class Image(Measurement): - def __init__(self, filename=None, data=None, mode="AFM", channelname='Z raw', order=0, datatype=DataTypes['Topography']): + def __init__(self, data=None, filename=None, mode="AFM", channelname='Z raw', order=0, datatype=DataTypes['Topography']): super().__init__() # Describing channel and datatype self.channel = channelname # Full channel name self.order = int(order) # Order, nth self.datatype = datatype # Amplitude, Phase, Topography - Enum DataTypes - self._data = data # Actual data, this case it is only a single channel - - if data is np.ndarray: - self._data = data - self._xres, self._yres = np.shape(data) - self._xoff = 0 - self._yoff = 0 - self._xreal = 1 - self._yreal = 1 - else: - self._data = None + + self._data = data + self._xres, self._yres = np.shape(data) + self._xoff = 0 + self._yoff = 0 + self._xreal = 1 + self._yreal = 1 @property def data(self): @@ -104,18 +100,15 @@ def channel(self,value): self.datatype = DataTypes["Amplitude"] class GwyImage(Image): - def __init__(self, filename=None, data=None, mode="AFM", channelname='Z raw', order=0, datatype=DataTypes['Topography']): + def __init__(self, data=None, filename=None, mode="AFM", channelname='Z raw', order=0, datatype=DataTypes['Topography']): super().__init__(filename, data, mode, channelname, order, datatype) - if data is GwyDataField: - self._data = data.data - self._xres, self._yres = np.shape(data.data) - self._xoff = data.xoff - self._yoff = data.yoff - self._xreal = data.xreal - self._yreal = data.yreal - else: - self._data = None + self._data = data.data + self._xres, self._yres = np.shape(data.data) + self._xoff = data.xoff + self._yoff = data.yoff + self._xreal = data.xreal + self._yreal = data.yreal @property def data(self):