Skip to content

Commit b80d55f

Browse files
committed
Add noise screening for CT25K
1 parent c01e254 commit b80d55f

10 files changed

Lines changed: 111 additions & 23 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
[![Run tests](https://github.com/actris-cloudnet/ceilopyter/actions/workflows/test.yml/badge.svg)](https://github.com/actris-cloudnet/ceilopyter/actions/workflows/test.yml)
44
[![PyPI version](https://badge.fury.io/py/ceilopyter.svg)](https://badge.fury.io/py/ceilopyter)
55

6-
Python package for reading ceilometer data.
6+
Python package for reading ceilometer data and doing some post-processing like
7+
screening background noise. This package is used in
8+
[CloudnetPy](https://github.com/actris-cloudnet/cloudnetpy) for generating the
9+
[Cloudnet lidar product](https://cloudnet.fmi.fi/product/lidar).
710

811
## Supported ceilometers
912

ceilopyter/ceilo.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
1-
from .ceilo_raw import CeiloRaw, concatenate_raw
1+
import numpy as np
2+
import numpy.typing as npt
3+
4+
from .ceilo_raw import CeiloRaw
25

36

47
class Ceilo:
5-
def __init__(self, raw: list[CeiloRaw], calibration_factor: float):
6-
concat = concatenate_raw(raw)
7-
self.time = concat.time
8-
self.range = concat.range
9-
self.beta_raw = concat.beta * calibration_factor
8+
"""Raw ceilometer data.
9+
10+
Attributes:
11+
time: Time
12+
range: Range (m)
13+
beta_raw: Non-screened range-corrected backscatter coefficient (sr-1 m-1)
14+
beta: Screened range-corrected backscatter coefficient (sr-1 m-1)
15+
wavelength: Wavelength (nm)
16+
zenith_angle: Zenith angle (deg)
17+
"""
18+
19+
def __init__(
20+
self,
21+
raw: CeiloRaw,
22+
beta_raw: npt.NDArray[np.floating],
23+
beta: npt.NDArray[np.floating] | None,
24+
calibration_factor: float,
25+
):
26+
self.time = raw.time
27+
self.range = raw.range
28+
self.beta = beta
29+
self.beta_raw = beta_raw
1030
self.calibration_factor = calibration_factor
11-
self.wavelength = concat.wavelength
12-
self.zenith_angle = concat.zenith_angle
31+
self.wavelength = raw.wavelength
32+
self.zenith_angle = raw.zenith_angle

ceilopyter/instruments/chm15k.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from numpy import ma
99

1010
from ..ceilo import Ceilo
11-
from ..ceilo_raw import CeiloRaw
11+
from ..ceilo_raw import CeiloRaw, concatenate_raw
1212

1313

1414
def read_chm15k(
@@ -23,7 +23,9 @@ def read_chm15k(
2323
raw = []
2424
for file in files:
2525
raw.append(_read_file(file))
26-
return Ceilo(raw, calibration_factor)
26+
concat = concatenate_raw(raw)
27+
beta_raw = concat.beta * calibration_factor
28+
return Ceilo(concat, beta_raw, None, calibration_factor)
2729

2830

2931
def _read_file(file: str | PathLike) -> CeiloRaw:

ceilopyter/instruments/cl31.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from os import PathLike
33

44
from ..ceilo import Ceilo
5+
from ..ceilo_raw import concatenate_raw
56
from ..common import read_msgs
67
from ..readers.read_cl import read_cl_file
78

@@ -13,5 +14,7 @@ def read_cl31(
1314
if calibration_factor is None:
1415
calibration_factor = 1.0
1516
logging.warning("Using default calibration factor: %s", calibration_factor)
16-
raw = read_msgs(files, read_cl_file, 910.0)
17-
return Ceilo(raw, calibration_factor)
17+
raw = read_msgs(files, read_cl_file, wavelength=910.0)
18+
concat = concatenate_raw(raw)
19+
beta_raw = concat.beta * calibration_factor
20+
return Ceilo(concat, beta_raw, None, calibration_factor)

ceilopyter/instruments/cl51.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from os import PathLike
33

44
from ..ceilo import Ceilo
5+
from ..ceilo_raw import concatenate_raw
56
from ..common import read_msgs
67
from ..readers.read_cl import read_cl_file
78

@@ -13,5 +14,7 @@ def read_cl51(
1314
if calibration_factor is None:
1415
calibration_factor = 1.0
1516
logging.warning("Using default calibration factor: %s", calibration_factor)
16-
raw = read_msgs(files, read_cl_file, 910.0)
17-
return Ceilo(raw, calibration_factor)
17+
raw = read_msgs(files, read_cl_file, wavelength=910.0)
18+
concat = concatenate_raw(raw)
19+
beta_raw = concat.beta * calibration_factor
20+
return Ceilo(concat, beta_raw, None, calibration_factor)

ceilopyter/instruments/cl61.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from cftime import num2pydate
77

88
from ..ceilo import Ceilo
9-
from ..ceilo_raw import CeiloRaw
9+
from ..ceilo_raw import CeiloRaw, concatenate_raw
1010

1111

1212
def read_cl61(
@@ -21,7 +21,9 @@ def read_cl61(
2121
raw = list(executor.map(_read_file, files))
2222
else:
2323
raw = [_read_file(files)]
24-
return Ceilo(raw, calibration_factor)
24+
concat = concatenate_raw(raw)
25+
beta_raw = concat.beta * calibration_factor
26+
return Ceilo(concat, beta_raw, None, calibration_factor)
2527

2628

2729
def _read_file(file: str | PathLike) -> CeiloRaw:

ceilopyter/instruments/cs135.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import logging
22
from os import PathLike
33

4+
from ceilopyter.ceilo_raw import concatenate_raw
5+
46
from ..ceilo import Ceilo
57
from ..common import read_msgs
68
from ..readers.read_cs import read_cs_file
@@ -13,5 +15,7 @@ def read_cs135(
1315
if calibration_factor is None:
1416
calibration_factor = 1.0
1517
logging.warning("Using default calibration factor: %s", calibration_factor)
16-
raw = read_msgs(files, read_cs_file, 905.0)
17-
return Ceilo(raw, calibration_factor)
18+
raw = read_msgs(files, read_cs_file, wavelength=905.0)
19+
concat = concatenate_raw(raw)
20+
beta_raw = concat.beta * calibration_factor
21+
return Ceilo(concat, beta_raw, None, calibration_factor)

ceilopyter/instruments/ct25k.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,44 @@
11
import logging
22
from os import PathLike
33

4+
import numpy as np
5+
import numpy.typing as npt
6+
from numpy import ma
7+
48
from ..ceilo import Ceilo
9+
from ..ceilo_raw import concatenate_raw
510
from ..common import read_msgs
11+
from ..noise import remove_noise
612
from ..readers.read_ct import read_ct_file
713

814

915
def read_ct25k(
1016
files: str | PathLike | list[str | PathLike],
1117
calibration_factor: float | None = None,
18+
noise_h2: bool = True,
1219
) -> Ceilo:
1320
if calibration_factor is None:
1421
calibration_factor = 1.0
1522
logging.warning("Using default calibration factor: %s", calibration_factor)
16-
raw = read_msgs(files, read_ct_file, 905.0)
17-
return Ceilo(raw, calibration_factor)
23+
24+
raw = read_msgs(files, read_ct_file, wavelength=905.0)
25+
concat = concatenate_raw(raw)
26+
27+
r2 = (concat.range * 1e-3) ** 2
28+
beta_calib = concat.beta * calibration_factor
29+
beta_uncorr = beta_calib / r2 if noise_h2 else _fix_beta(r2, beta_calib)
30+
31+
is_noise = remove_noise(beta_uncorr, noise_floor=6e-8)
32+
beta_raw = beta_uncorr * r2
33+
beta = ma.masked_where(is_noise, beta_raw)
34+
35+
return Ceilo(concat, beta_raw, beta, calibration_factor)
36+
37+
38+
def _fix_beta(
39+
r2: npt.NDArray[np.floating], beta_raw: npt.NDArray[np.floating]
40+
) -> npt.NDArray[np.floating]:
41+
range_broad = np.broadcast_to(r2, beta_raw.shape)
42+
is_strong = beta_raw > 1e-7
43+
beta_raw[is_strong] /= range_broad[is_strong]
44+
return beta_raw

ceilopyter/instruments/ld40.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import numpy as np
66

77
from ..ceilo import Ceilo
8-
from ..ceilo_raw import CeiloRaw
8+
from ..ceilo_raw import CeiloRaw, concatenate_raw
99
from ..common import InvalidMessageError
1010

1111

@@ -21,7 +21,9 @@ def read_ld40(
2121
raw = []
2222
for file in files:
2323
raw.append(_read_file(file))
24-
return Ceilo(raw, calibration_factor)
24+
concat = concatenate_raw(raw)
25+
beta_raw = concat.beta * calibration_factor
26+
return Ceilo(concat, beta_raw, None, calibration_factor)
2527

2628

2729
def _read_file(filename: str | PathLike) -> CeiloRaw:

ceilopyter/noise.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import numpy as np
2+
import numpy.typing as npt
3+
from numpy import ma
4+
5+
6+
def remove_noise(
7+
beta_uncorr: npt.NDArray[np.floating], noise_floor: float, snr_limit: float = 5
8+
) -> npt.NDArray[np.bool]:
9+
zero_ranges = np.all(beta_uncorr == 0, axis=0)
10+
n_zeros = np.argmax(~zero_ranges[::-1])
11+
12+
fraction = 0.1
13+
n_top_gates = round(beta_uncorr.shape[1] * fraction)
14+
beta_top = beta_uncorr[:, -n_top_gates - n_zeros : -n_zeros]
15+
noise = ma.std(beta_top, axis=1)
16+
17+
noise = np.maximum(noise, noise_floor)
18+
snr = beta_uncorr / noise[:, np.newaxis]
19+
20+
is_noise = snr < snr_limit
21+
22+
return is_noise

0 commit comments

Comments
 (0)