From 85c80cdf8ff3bde2f2a513bc4f5ca576f28956f9 Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Fri, 27 Jun 2025 12:25:32 +0200 Subject: [PATCH 1/2] HSI reshape fix, readers extension for nanoraman measurements --- pySNOM/datasets/testspectrum_multipoint.txt | 39 +++++++++++++++++ pySNOM/datasets/testspectrum_nanoraman.txt | 48 +++++++++++++++++++++ pySNOM/defaults.py | 1 + pySNOM/spectra.py | 20 ++++++--- pySNOM/tests/test_readers.py | 8 ++++ pySNOM/tests/test_spectra.py | 12 ++++++ 6 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 pySNOM/datasets/testspectrum_multipoint.txt create mode 100644 pySNOM/datasets/testspectrum_nanoraman.txt diff --git a/pySNOM/datasets/testspectrum_multipoint.txt b/pySNOM/datasets/testspectrum_multipoint.txt new file mode 100644 index 0000000..e4f2725 --- /dev/null +++ b/pySNOM/datasets/testspectrum_multipoint.txt @@ -0,0 +1,39 @@ +# www.neaspec.com +# Scan:   Fourier Scan +# Project:   nanoFTIR-QualityControl +# Description:   NeaSpec-Slow-Arrowhead-Clean-REV2-45avg +# Date:   05/19/2024 16:59:10 +# Scanner Center Position (X, Y): [µm] 46.77 49.79   +# Rotation: [°] 0     +# Scan Area (X, Y, Z): [µm] 1.000 0.000 0.000 +# Pixel Area (X, Y, Z): [px] 2 1 2 +# Interferometer Center/Distance: [µm] 470.000 490.000   +# Averaging:   45     +# Integration time: [ms] 20     +# Wavenumber Scaling:   1.003656     +# Laser Source:   +# Detector:   R +# Target Wavelength: [µm]     +# Demodulation Mode:   Fourier +# Tip Frequency: [Hz] 68,263.500     +# Tip Amplitude: [mV] 339.702     +# Tapping Amplitude: [nm] 76.233     +# Modulation Frequency: [Hz] 0.000     +# Modulation Amplitude: [mV] 0.000     +# Modulation Offset: [mV] 0.000     +# Setpoint: [%] 80.49     +# Regulator (P, I, D):   3.767854 6.228756 1.000000 +# Tip Potential: [mV] 0.000     +# M1A Scaling: [nm/V] 1.520     +# M1A Cantilever Factor:   2.066     +# Q-Factor:   221.1     +# Version:   2.1.11508.0 +Row Column Omega Wavenumber O0A O0P O1A O1P O2A O2P O3A O3P O4A O4P O5A O5P +0 0 0 0 37.119324 3.1415927 1.738142 -3.1415913 0.1600194 8.742275E-08 0.12289047 -2.6534444E-10 0.03433025 -3.1415925 0.044003725 -3.1415925 +0 0 1 2.560346956632653 35.434025 2.681263 4.94985 0.9037826 0.43971792 -2.300659 0.16032808 -0.58963114 0.053768463 1.7078916 0.08377697 1.4515767 +0 0 2 5.120693913265306 32.58373 2.3122003 8.926398 -0.2935503 0.8658856 2.7100954 0.26857227 -1.3153769 0.09340307 0.8722349 0.14734396 0.52470887 +0 0 3 7.681040869897961 33.03119 2.0073247 12.101863 -1.3972439 1.3369256 1.5798267 0.43721926 -2.1214292 0.15267329 0.18834925 0.21037804 -0.2551048 +0 1 0 0 37.119324 3.1415927 1.738142 -3.1415913 0.1600194 8.742275E-08 0.12289047 -2.6534444E-10 0.03433025 -3.1415925 0.044003725 -3.1415925 +0 1 1 2.560346956632653 35.434025 2.681263 4.94985 0.9037826 0.43971792 -2.300659 0.16032808 -0.58963114 0.053768463 1.7078916 0.08377697 1.4515767 +0 1 2 5.120693913265306 32.58373 2.3122003 8.926398 -0.2935503 0.8658856 2.7100954 0.26857227 -1.3153769 0.09340307 0.8722349 0.14734396 0.52470887 +0 1 3 7.681040869897961 33.03119 2.0073247 12.101863 -1.3972439 1.3369256 1.5798267 0.43721926 -2.1214292 0.15267329 0.18834925 0.21037804 -0.2551048 diff --git a/pySNOM/datasets/testspectrum_nanoraman.txt b/pySNOM/datasets/testspectrum_nanoraman.txt new file mode 100644 index 0000000..6bddc84 --- /dev/null +++ b/pySNOM/datasets/testspectrum_nanoraman.txt @@ -0,0 +1,48 @@ +# www.neaspec.com +# Scan:   AFM-Raman/PL Scan (Tapping Mode) +# Project:   KJK - Wiegner RCP - SM007 +# Description:   Area2 +# Date:   05/14/2025 17:07:02 +# Scanner Center Position (X, Y): [µm] 52.83 53.80   +# Rotation: [°] 0     +# Scan Area (X, Y, Z): [µm] 5.000 5.000 0.000 +# Pixel Area (X, Y, Z): [px] 2 1 1 +# Averaging:   1     +# Integration time: [ms] 10     +# Laser Source:   532 nm visible laser +# Detector:   VIS +# Target Wavelength: [µm]     +# Demodulation Mode:   Fourier +# Tip Frequency: [Hz] 56,332.262     +# Tip Amplitude: [mV] 10.000     +# Tapping Amplitude: [nm] 44.401     +# Modulation Frequency: [Hz] 0.000     +# Modulation Amplitude: [mV] 0.000     +# Modulation Offset: [mV] 0.000     +# Setpoint: [%] 80.28     +# Regulator (P, I, D):   6.394934 13.411389 1.000000 +# Tip Potential: [mV] 0.000     +# M1A Scaling: [nm/V] 1.800     +# M1A Cantilever Factor:   2.449     +# Q-Factor:   157.5     +# Version:   2.3.13115.0 +# CCD Pixels: [px] 1340     +# Spectral Range: [nm] 533.186 792.054   +# Exposure Time: [s] 0.100     +# Input slit:   Direct Input     +# Output slit:   Direct     +# Shutter:   Open     +# Grating Offset:   0     +# Focus Mirror: [nm] 0     +# Camera:   CCD     +# Temperature: [°C] -75.0     +# Grating: [l/mm] 300.000 750nm   +Row Column Wavelength Index Data Z +0 0 533186.4014 0 11.0000 0.0000 +0 0 533383.8501 1 -5.0000 0.0000 +0 0 533581.2988 2 -2.0000 0.0000 +0 0 533778.7476 3 7.0000 0.0000 +0 1 533186.4014 0 5.0000 0.0000 +0 1 533383.8501 1 -4.0000 0.0000 +0 1 533581.2988 2 3.0000 0.0000 +0 1 533778.7476 3 -1.0000 0.0000 diff --git a/pySNOM/defaults.py b/pySNOM/defaults.py index 5b9e63d..275184b 100644 --- a/pySNOM/defaults.py +++ b/pySNOM/defaults.py @@ -17,6 +17,7 @@ def __init__(self) -> None: self.spectral_mode_defs = { "Fourier Scan": "nanoFTIR", "Pointspectroscopy PTE+": "PTE", + "AFM-Raman/PL Scan (Tapping Mode)": "nanoRaman" } diff --git a/pySNOM/spectra.py b/pySNOM/spectra.py index ad8a829..7c9d65e 100644 --- a/pySNOM/spectra.py +++ b/pySNOM/spectra.py @@ -3,7 +3,7 @@ from pySNOM.images import type_from_channelname from pySNOM.defaults import Defaults -MeasurementModes = Enum("MeasurementModes", ["None", "nanoFTIR", "PsHet", "PTE"]) +MeasurementModes = Enum("MeasurementModes", ["None", "nanoFTIR", "PsHet", "PTE", "nanoRaman"]) DataTypes = Enum("DataTypes", ["Amplitude", "Phase", "Complex", "Topography"]) ChannelTypes = Enum("ChannelTypes", ["None", "Optical", "Mechanical"]) ScanTypes = Enum("ScanTypes", ["Point", "LineScan", "HyperScan"]) @@ -219,19 +219,29 @@ def reshape_spectrum_data(data, params): if params["Scan"] == "Fourier Scan": n = 2 - for channel in list(data.keys()): + allchannels = list(data.keys()) + if "Depth" in allchannels: + spectral_depth = len(np.unique(data["Depth"])) + elif "Index" in allchannels: + spectral_depth = len(np.unique(data["Index"])) + elif "Omega" in allchannels: + spectral_depth = len(np.unique(data["Omega"])) + else: + spectral_depth = params["PixelArea"][2] * n + + for channel in allchannels: # Point spectrum if params["PixelArea"][1] == 1 and params["PixelArea"][0] == 1: - data[channel] = np.reshape(data[channel], (params["PixelArea"][2] * n)) + data[channel] = np.reshape(data[channel], (spectral_depth)) # Linescan and HyperScan else: - data[data[channel]] = np.reshape( + data[channel] = np.reshape( data[channel], ( params["PixelArea"][0], params["PixelArea"][1], - params["PixelArea"][2] * n, + spectral_depth, ), ) diff --git a/pySNOM/tests/test_readers.py b/pySNOM/tests/test_readers.py index 070c93a..aa7d015 100644 --- a/pySNOM/tests/test_readers.py +++ b/pySNOM/tests/test_readers.py @@ -54,6 +54,14 @@ def test_general_reader_spectrum(self): np.testing.assert_almost_equal(data["O2A"][0], 0.1600194) np.testing.assert_string_equal(params["Scan"], "Fourier Scan") + def test_general_reader_nanoraman(self): + f = "datasets/testspectrum_nanoraman.txt" + file_reader = readers.NeaSpectralReader(os.path.join(pySNOM.__path__[0], f)) + data, params = file_reader.read() + + np.testing.assert_almost_equal(data["Data"][0], 11.0) + np.testing.assert_string_equal(params["Scan"], "AFM-Raman/PL Scan (Tapping Mode)") + def test_legacy_nea_reader(self): f = "datasets/neafile_test_ifg.nea" file_reader = readers.NeaFileLegacyReader(os.path.join(pySNOM.__path__[0], f)) diff --git a/pySNOM/tests/test_spectra.py b/pySNOM/tests/test_spectra.py index 84ec948..7376f6e 100644 --- a/pySNOM/tests/test_spectra.py +++ b/pySNOM/tests/test_spectra.py @@ -20,6 +20,18 @@ def test_pointspectrum_object(self): np.testing.assert_string_equal(s.scantype, "Point") np.testing.assert_equal(np.shape(s.data["O2A"])[0], 2048) + def test_multipointspectrum_object(self): + f = "datasets/testspectrum_multipoint.txt" + file_reader = readers.NeaSpectralReader(os.path.join(pySNOM.__path__[0], f)) + data, params = file_reader.read() + + s = spectra.NeaSpectrum(data, params) + + np.testing.assert_almost_equal(s.data["O2A"][1,0,0], 0.1600194) + np.testing.assert_string_equal(s.parameters["Scan"], "Fourier Scan") + np.testing.assert_string_equal(s.scantype, "LineScan") + np.testing.assert_equal(np.shape(s.data["O2A"])[2], 4) + def test_transfromations(self): f = "datasets/testspectrum_singlepoint.txt" file_reader = readers.NeaSpectralReader(os.path.join(pySNOM.__path__[0], f)) From 0bb72cf62390c08275d436dcd9229b90b476a318 Mon Sep 17 00:00:00 2001 From: Gergely Nemeth Date: Fri, 27 Jun 2025 12:28:43 +0200 Subject: [PATCH 2/2] Lint --- pySNOM/defaults.py | 2 +- pySNOM/spectra.py | 4 +++- pySNOM/tests/test_readers.py | 4 +++- pySNOM/tests/test_spectra.py | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pySNOM/defaults.py b/pySNOM/defaults.py index 275184b..54de1d3 100644 --- a/pySNOM/defaults.py +++ b/pySNOM/defaults.py @@ -17,7 +17,7 @@ def __init__(self) -> None: self.spectral_mode_defs = { "Fourier Scan": "nanoFTIR", "Pointspectroscopy PTE+": "PTE", - "AFM-Raman/PL Scan (Tapping Mode)": "nanoRaman" + "AFM-Raman/PL Scan (Tapping Mode)": "nanoRaman", } diff --git a/pySNOM/spectra.py b/pySNOM/spectra.py index 7c9d65e..e980e8c 100644 --- a/pySNOM/spectra.py +++ b/pySNOM/spectra.py @@ -3,7 +3,9 @@ from pySNOM.images import type_from_channelname from pySNOM.defaults import Defaults -MeasurementModes = Enum("MeasurementModes", ["None", "nanoFTIR", "PsHet", "PTE", "nanoRaman"]) +MeasurementModes = Enum( + "MeasurementModes", ["None", "nanoFTIR", "PsHet", "PTE", "nanoRaman"] +) DataTypes = Enum("DataTypes", ["Amplitude", "Phase", "Complex", "Topography"]) ChannelTypes = Enum("ChannelTypes", ["None", "Optical", "Mechanical"]) ScanTypes = Enum("ScanTypes", ["Point", "LineScan", "HyperScan"]) diff --git a/pySNOM/tests/test_readers.py b/pySNOM/tests/test_readers.py index aa7d015..20e1c9d 100644 --- a/pySNOM/tests/test_readers.py +++ b/pySNOM/tests/test_readers.py @@ -60,7 +60,9 @@ def test_general_reader_nanoraman(self): data, params = file_reader.read() np.testing.assert_almost_equal(data["Data"][0], 11.0) - np.testing.assert_string_equal(params["Scan"], "AFM-Raman/PL Scan (Tapping Mode)") + np.testing.assert_string_equal( + params["Scan"], "AFM-Raman/PL Scan (Tapping Mode)" + ) def test_legacy_nea_reader(self): f = "datasets/neafile_test_ifg.nea" diff --git a/pySNOM/tests/test_spectra.py b/pySNOM/tests/test_spectra.py index 7376f6e..873eac8 100644 --- a/pySNOM/tests/test_spectra.py +++ b/pySNOM/tests/test_spectra.py @@ -27,7 +27,7 @@ def test_multipointspectrum_object(self): s = spectra.NeaSpectrum(data, params) - np.testing.assert_almost_equal(s.data["O2A"][1,0,0], 0.1600194) + np.testing.assert_almost_equal(s.data["O2A"][1, 0, 0], 0.1600194) np.testing.assert_string_equal(s.parameters["Scan"], "Fourier Scan") np.testing.assert_string_equal(s.scantype, "LineScan") np.testing.assert_equal(np.shape(s.data["O2A"])[2], 4)