First fixes for OFR Analysis function, and starting to documentation

This commit is contained in:
tschan_d committed 2026-07-01 18:52:08 +02:00
1 parent 13e674e7b1
commit f3f3924a42
4 files changed
+149 -62

No files matched your search

-8
View File
@@ -9,14 +9,6 @@
"program": "${file}",
"console": "integratedTerminal",
"env": {"PYTHONPATH": "${workspaceFolder}/"}
},
{
"name": "Python Debugger: debug_scrapyard.py",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/defs/debug_scrapyard.py",
"console": "integratedTerminal",
"env": {"PYTHONPATH": "${workspaceFolder}/"}
}
]
}
+33 -2
View File
@@ -1,6 +1,37 @@
# Welcome!
# Not so fast!!!
Hello! This is the read-me file with some important information for the code project.
Hello! This is the read-me file with information for the code project. Please take some time to read it before going on, as it contains detailed description of code and methods used. So don't be hasty, and have a good look!
# Code Documentation
## Code Structure:
This code project is structured as follows:
```
Master_Thesis
+-- .git
+-- .vscode
+-- defs
+-- __init__.py
+-- defs.py
+-- vis
+-- __init__.py
+-- defs.py
+-- .env
+-- .gitignore
+-- .enviornment.yml
+-- main.py
+-- README.md
```
You are currently reading `README.md`, where the structure of this oroject is explained. You're in the right spot to start, then!
### Auxilliary directories `.git` and `.vscode`
1. `.git` contains all files related to `git`. These files are usually not interesting, unless you need to go back to old commits or want to know more details. Best leave this alone. For a detailed description of how `git` (and with it the `.git` directory) works, see [`git` description](##Git).
2. `.vscode` contains all files relevant for environment setup to run this project in [Visual Studio Code](https://code.visualstudio.com/). These files are:
1. `settings.json`: the setup file to set up VS Code in the desired configuration. Feel free to change these as you like. For example, the setting `"python.defaultInterpreterPath": "C:/Users/tschan_d/AppData/Local/miniforge3/envs/MA/python"` needs to be changed to point to the path of the Python interpreter you select.
2. `launch.json`: the launch configuration file for the project, in which it is specified how VSCode should behave when you launch a job: that is, when you want to run a file or debug it. Again, feel free to customise these settings.
Important here is that
3. `defs` contains source code for data analysis:
1. `__init__.py` is a file to structure the project like a package. This file is empty, but it is needed such that relative imports work properly.
4. `vis` contains source code for data visualisation.
# Coding help
## Conda
+106 -46
View File
@@ -12,27 +12,80 @@ import matplotlib.pyplot as plt
class OFRSample:
def __init__(self, number, sourcefile, headlines, density, lower_bound, upper_bound, scan_up, scan_down):
"""Object to represent a single sample from the OFR
self.number = number
self.sourcefile = sourcefile
self.headlines = headlines
Description: Bla
self.lower_bound = lower_bound
self.upper_bound = upper_bound
Attributes
----------
number: int
The sample number
sourcefile: str
The path to the data file
headlines: int
The number of lines in the data file until raw data is reached
lower_bound: float
The lower bound diameter (nm) of the SMPS
upper_bound: float
The upper bound diameter (nm) of the SMPS
scan_up: float
The time (s) to ramp up voltage in SMPS
scan_down: float
The time (s) to ramp voltage back down in SMPS
delta_time: float
The time interval (s) between two measured data points of the raw count density
SOA_density: float
The density (kg/m3) of the SOA produced in the OFR
Methods
-------
__init__(number, sourcefile, headlines, density, lower_bound, upper_bound, scan_up, scan_down, delta_time) -> OFRSample:
The constructor of the Object
"""
self.scan_up = scan_up
self.scan_down = scan_down
def __init__(self, number, sourcefile, headlines, density, lower_bound, upper_bound, scan_up, scan_down, delta_time = 0.1):
"""Constructor of a OFRSample object
Description:
# in g/cc
self.density = density
Parameters
----------
number: int
The sample number
Returns
-------
None: None
Does not have a return value
"""
self.diameters = None
self.counts = None
# Sample information
self.number = number # sample number, i.e. 33
self.sourcefile = sourcefile # where the data is stored, "C:/Users/tschan_d/..."
self.headlines = headlines # Number of lines before raw data is reached, usually 238
self.lower_bound = lower_bound # Lower Diameter (nm) where CPC works
self.upper_bound = upper_bound # Upper Diameter (nm) where CPC works
self.scan_up = scan_up # Time to scan up the voltage range in DMA
self.scan_down = scan_down # Time to ramp the voltage back down in DMA
self.delta_time = delta_time # Measuring interval to get raw count density; (s)
self.SOA_density = density # Expected density of SOA (kg/m3)
self.__get_data()
# Sample distributions
self.diameters = None # Diameters (nm)
self.count_density = None # Count Density (#/(m3*m*s))
self.surface_density = None # Surface Density (m2/(m3*m*s))
self.volume_density = None # Volume Density (m3/(m3*m*s))
self.mass_density = None # Mass Density (kg/(m3*m*s))
def __get_data(self):
self.total_count = None # Total Count (#/(m3*s))
self.total_surface = None # Total Surface (m2/(m3*s))
self.total_volume = None # Total Volume (m3/(m3*s))
self.total_mass = None # Total Mass (kg/(m3*s))
self.get_data()
def get_data(self):
columns = ['Diameter #' + str(self.number), 'Counts #' + str(self.number)]
sample_data = pd.read_csv(filepath_or_buffer=self.sourcefile, header=self.headlines, usecols=columns, index_col=False,
@@ -40,31 +93,41 @@ class OFRSample:
filter_array = np.where((sample_data[:, 0] >= self.lower_bound) & (sample_data[:, 0] <= self.upper_bound), True, False)
# Particle Diameter: (nm)
self.diameters = sample_data[filter_array, 0]
# Particle Diameter: Raw data in (nm) -> Output data in (m): 1 nm = 1E-9 m
self.diameters = sample_data[filter_array, 0] * 1E-9
# Particle raw counts: (# / cc)
self.counts = sample_data[filter_array, 1]
# Particle Count Density: Raw data in (#/(cm3*nm*0.1s)) -> Output data in (#/(m3*m*s)): 1/(cm3*nm*s) = 1E16/(m3*m*s)
self.count_density = sample_data[filter_array, 1] * 1E16
def volume_integrator(self, input_prefix = 1E-9, output_prefix = 1E-2) -> np.float64:
self.surface_density = np.multiply(math.pi * self.diameters ** 2, self.count_density)
self.volume_density = np.multiply((math.pi / 6) * self.diameters ** 3, self.count_density)
self.mass_density = np.multiply(self.SOA_density, self.volume_density)
adjustment_factor = input_prefix / output_prefix
self.total_count = self.__integrator(integrand = self.count_density)
self.total_surface = self.__integrator(integrand = self.surface_density)
self.total_volume = self.__integrator(integrand = self.volume_density)
self.total_mass = self.total_volume * self.SOA_density
#
local_volumes = (math.pi / 6) * (self.diameters * adjustment_factor) ** 3
local_volume = np.trapezoid(y=self.counts, x=local_volumes)
return local_volume
def __integrator(self, integrand: np.float64) -> np.float64:
return np.trapezoid(y = integrand, x = self.diameters)
class OFRAnalyser:
def __init__(self, sourcefile, headlines = 238, density=1.2, lower_bound = 0, upper_bound = 1896, scan_up=160, scan_down=20) -> None:
def __init__(self, sourcefile, headlines = 238, density=1.2, lower_bound = 0, upper_bound = 1896, scan_up=160, scan_down=20,
flow_rate=5) -> None:
# where to take data from:
self.sourcefile = sourcefile
# how many lines to skip until the raw counts data section is reached:
self.headlines = headlines
# assumed SOA density
self.density = density
self.lower_bound = lower_bound
self.upper_bound = upper_bound
self.scan_up = scan_up
self.scan_down = scan_down
self.flow_rate = flow_rate
with open(self.sourcefile) as file:
file_reader = csv.reader(file)
@@ -77,6 +140,9 @@ class OFRAnalyser:
self.max_samples = int((len(head_row) - 1) / 2)
# total mass in mg
self.total_counts = None
self.total_surface = None
self.total_volume = None
self.total_mass = None
def __sample_number_list_check(self, sample_number_list) -> None:
@@ -86,35 +152,29 @@ class OFRAnalyser:
if last_sample > self.max_samples:
raise IndexError("The last sample(s) are out of range of the number of recorded samples")
def GetTotalMass(self, samples = [1], plot = False) -> None:
def AggregateSamples(self, samples = [1]) -> None:
self.__sample_number_list_check(samples)
counts = []
surfaces = []
volumes = []
masses = []
for sample in samples:
local_sample = OFRSample(number=sample, sourcefile=self.sourcefile, headlines=self.headlines, density=self.density,
for samplenumber in samples:
sample = OFRSample(number=samplenumber, sourcefile=self.sourcefile, headlines=self.headlines, density=self.density,
lower_bound = self.lower_bound, upper_bound = self.upper_bound, scan_up = self.scan_up,
scan_down = self.scan_down)
# For python notebooks and debugging reasons
if plot == True:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(local_sample.diameters, local_sample.counts)
ax.set_xscale('log')
plt.show()
local_volume = local_sample.volume_integrator()
masses.append(local_volume * local_sample.density)
counts.append(sample.total_count)
surfaces.append(sample.total_surface)
volumes.append(sample.total_volume)
masses.append(sample.total_mass)
total_mass = sum(masses)
self.total_mass = total_mass
def PlotSampleDistribution(self, sample) -> None:
pass
def PlotAllDistributions(self):
pass
self.total_count = sum(counts)
self.total_surface = sum(surfaces)
self.total_volume = sum(volumes)
self.total_mass = sum(masses)
### ----------------------------------------------------------------------------------------------------------------------------------- ###
###
+10 -6
View File
@@ -1,12 +1,16 @@
#%%
from defs.defs import OFRAnalyser
from vis.defs import HelloWorld
import numpy as np
#%%
analyser = OFRAnalyser(sourcefile="Z:/People/David_Tschan/data/OFR_SMPS/2026/processed/06_June/20260612/20260612_SET02_EXP01_full.csv",
lower_bound=22.87, upper_bound=1000, density=102, scan_up=160, scan_down=20)
lower_bound=22.87, upper_bound=1000, density=1200, scan_up=160, scan_down=20)
samplelist = np.linspace(start=1, stop=analyser.max_samples, num=analyser.max_samples, dtype=int).tolist()
#samplelist = np.linspace(start=1, stop=analyser.max_samples, num=analyser.max_samples, dtype=int).tolist()
analyser.GetTotalMass(samples=[33], plot=True)
print(analyser.total_mass)
#%%
analyser.AggregateSamples(samples=[33])
#%%
print(analyser.total_surface)
# %%