Skip to content

Commit acde819

Browse files
authored
Merge pull request #1203 from wright-group/norm_for_each
norm_for_each
2 parents bc263cf + b56b3e7 commit acde819

5 files changed

Lines changed: 134 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
55

66
## [Unreleased]
77

8+
### Added
9+
- `Data.norm_for_each`: easier norm-by-axis syntax
10+
- `kit.from_list_of_objects`: convenience method for grabbing an channel/variable/axis/etc. using multiple specifiers
11+
- `Data.get_var`: get variable object by index, name, or variable itself
12+
- `Data.get_axis`: get axis object by index, name, or variable itself
13+
- `Data.get_channel`: get channel object by index, name, or variable itself
14+
815
### Fixed
916
- quick2D works with contours on
1017
- quick2D works with `pixelated=False` (i.e. contourf)
18+
- `data.from_LabRAM`: small tweaks to deal with newly emerged test failure in python 3.11 tests
1119

1220
### Changed
1321
- default colorbar is sampled at 4096 points (was 256)

WrightTools/data/_data.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,68 @@ def gradient(self, axis, *, channel=0):
738738
else:
739739
new[:] = np.gradient(channel[:], self[axis].points, axis=axis_index)
740740

741+
def get_var(self, hint: str | int | Variable) -> Variable:
742+
return wt_kit.from_list_of_objects(self.variables, self.variable_names, hint)
743+
744+
def get_channel(self, hint: str | int | Channel) -> Channel:
745+
return wt_kit.from_list_of_objects(self.channels, self.channel_names, hint)
746+
747+
def get_axis(self, hint: str | int | Axis) -> Axis:
748+
return wt_kit.from_list_of_objects(self.axes, self.axis_expressions, hint)
749+
750+
def norm_for_each(
751+
self,
752+
var: str | Variable | int,
753+
channel: str | Channel | int = 0,
754+
new_channel: dict = {},
755+
):
756+
"""normalize the data for each var slice
757+
var array must at least one trivial dimension (or else norm will return an array of ones)
758+
759+
Parameters
760+
----------
761+
var : str, int, or WrightTools.data.Variable
762+
variable to apply normalization at each unique point.
763+
channel : str, int or WrightTools.data.Channel (default 0)
764+
channel to apply normalization. Channel should have more non-trivial dimensions than variable
765+
new_channel : dict
766+
Default is empty, and channel is overwriten with norm values.
767+
If not empty, a new channel will be created.
768+
Fields (e.g. name) can be supplied by supplying a dictionary (consult `Data.create_channel`).
769+
770+
771+
Examples
772+
--------
773+
import WrightTools.datasets as ds
774+
import WrightTools as wt
775+
776+
d = wt.open(ds.wt5.v1p0p1_MoS2_TrEE_movie)
777+
d.norm_for_each("d2", "ai0") # equivalent to d.ai0[:] /= d.ai0[:].max(axis=(0,1))[None, None, :]
778+
779+
"""
780+
variable = self.get_var(var)
781+
channel = self.get_channel(channel)
782+
trivial = {i for i, si in enumerate(variable.shape) if si == 1}
783+
if not trivial:
784+
raise wt_exceptions.WrightToolsWarning(
785+
f"Variable {variable.natural_name} and Channel {channel.natural_name} have the same shape {variable.shape}. "
786+
+ "Produces a ones array channel."
787+
)
788+
# nontrivial = tuple({i for i in range(self.ndim)} - trivial)
789+
trivial = tuple(trivial)
790+
norm_vals = np.expand_dims(np.nanmax(channel[:], axis=trivial), trivial)
791+
new = (channel[:] - channel.null) / (norm_vals - channel.null)
792+
if new_channel:
793+
self.create_channel(
794+
new_channel.pop("name", f"{channel.natural_name}_{variable.natural_name}_norm"),
795+
values=new,
796+
**new_channel,
797+
)
798+
else:
799+
channel[:] = new
800+
channel.null = 0
801+
return
802+
741803
def moment(self, axis, channel=0, moment=1, *, resultant=None):
742804
"""Take the nth moment the dataset along one axis, adding lower rank channels.
743805

WrightTools/data/_labram.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# --- import --------------------------------------------------------------------------------------
22

33

4-
import os
54
import pathlib
65
import warnings
76
import time
@@ -46,7 +45,6 @@ def from_LabRAM(filepath, name=None, parent=None, verbose=True) -> Data:
4645
New data object(s).
4746
"""
4847
# parse filepath
49-
filestr = os.fspath(filepath)
5048
filepath = pathlib.Path(filepath)
5149

5250
if not ".txt" in filepath.suffixes:
@@ -55,7 +53,7 @@ def from_LabRAM(filepath, name=None, parent=None, verbose=True) -> Data:
5553
if not name:
5654
name = filepath.name.split(".")[0]
5755

58-
kwargs = {"name": name, "kind": "Horiba", "source": filestr}
56+
kwargs = {"name": name, "kind": "Horiba", "source": str(filepath)}
5957

6058
# create data
6159
if parent is None:
@@ -64,7 +62,7 @@ def from_LabRAM(filepath, name=None, parent=None, verbose=True) -> Data:
6462
data = parent.create_data(**kwargs)
6563

6664
ds = DataSource(None)
67-
f = ds.open(filestr, "rt", encoding="ISO-8859-1")
65+
f = ds.open(str(filepath), "rt", encoding="ISO-8859-1")
6866

6967
# header
7068
header = {}
@@ -110,7 +108,7 @@ def from_LabRAM(filepath, name=None, parent=None, verbose=True) -> Data:
110108
# dimensionality
111109
extra_dims = np.isnan(wm).sum()
112110

113-
if extra_dims == 0: # single spectrum; we extracted wm wrong, so go back in file
111+
if not extra_dims: # single spectrum; we extracted wm wrong, so go back in file
114112
f.seek(0)
115113
wm, arr = np.genfromtxt(f, delimiter="\t", unpack=True)
116114
f.close()
@@ -121,13 +119,13 @@ def from_LabRAM(filepath, name=None, parent=None, verbose=True) -> Data:
121119
arr = np.genfromtxt(f, delimiter="\t")
122120
f.close()
123121
wm = wm[extra_dims:]
122+
x = arr[:, 0]
124123

125124
if extra_dims == 1: # spectrum vs (x or survey)
126125
data.create_variable("wm", values=wm[:, None], units=spectral_units)
127126
data.create_channel(
128127
"signal", values=arr[:, 1:].T / acquisition_time, units=channel_units
129128
)
130-
x = arr[:, 0]
131129
if np.all(x == np.arange(x.size) + 1): # survey
132130
data.create_variable("index", values=x[None, :])
133131
data.transform("wm", "index")
@@ -136,9 +134,7 @@ def from_LabRAM(filepath, name=None, parent=None, verbose=True) -> Data:
136134
data.transform("wm", "x")
137135
elif extra_dims == 2: # spectrum vs x vs y
138136
# fold to 3D
139-
x = sorted(
140-
set(arr[:, 0]), reverse=arr[0, 0] > arr[-1, 0]
141-
) # 0th column is stepped always (?)
137+
x = sorted(set(x), reverse=bool(x[0] > x[-1])) # 0th column is stepped always (?)
142138
x = np.array(list(x))
143139
x = x.reshape(1, -1, 1)
144140
y = arr[:, 1].reshape(1, x.size, -1)

WrightTools/kit/_list.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# --- define --------------------------------------------------------------------------------------
88

99

10-
__all__ = ["flatten_list", "intersperse", "get_index", "pairwise"]
10+
__all__ = ["flatten_list", "intersperse", "get_index", "from_list_of_objects", "pairwise"]
1111

1212

1313
# --- functions -----------------------------------------------------------------------------------
@@ -98,6 +98,43 @@ def get_index(lis, argument):
9898
return lis.index(argument)
9999

100100

101+
def from_list_of_objects(values: list, attrs: list, hint):
102+
"""
103+
convenience wrapper of `get_index`.
104+
Retrieve an object from a list of objects using either:
105+
* its index,
106+
* matching to an attr, or
107+
* passing the value itself
108+
the method normalizes a variety of input types to get the object itself.
109+
110+
arguments
111+
---------
112+
values: list
113+
list of objects to be searched
114+
attrs: list
115+
list of attrs that correspond to objects in the list `values`
116+
hint: str, list or object
117+
if hint is a member of values, return the hint itself
118+
119+
returns
120+
-------
121+
match: desired_type
122+
the member of `values` that matches the given hint.
123+
Raises ValueError if a match is not found.
124+
125+
see also
126+
--------
127+
WrightTools.kit.get_index
128+
"""
129+
130+
if isinstance(hint, int) or isinstance(hint, str):
131+
return values[get_index(attrs, hint)]
132+
elif hint in values:
133+
return hint
134+
else:
135+
raise ValueError(f"could not find a member in values based on hint {hint}")
136+
137+
101138
def pairwise(iterable):
102139
"""s -> (s0,s1), (s1,s2), (s2, s3), ...
103140

tests/data/norm_for_each.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import numpy as np
2+
import WrightTools as wt
3+
from WrightTools import datasets
4+
5+
6+
def test_3D():
7+
data = wt.open(datasets.wt5.v1p0p1_MoS2_TrEE_movie)
8+
9+
data.norm_for_each("w1", 0)
10+
assert np.all(data.channels[0][:].max(axis=(0, 2)) == 1)
11+
12+
data.norm_for_each("d2", 0, new_channel={"name": "ai0_d2_norm"})
13+
assert np.all(data.channels[-1][:].max(axis=(0, 1)) == 1)
14+
15+
data.norm_for_each("d1", 0, new_channel={"name": "ai0_d1_norm"})
16+
data.channels[0].normalize()
17+
assert np.all(np.isclose(data.ai0_d1_norm[:], data.channels[0][:]))
18+
19+
20+
if __name__ == "__main__":
21+
test_3D()

0 commit comments

Comments
 (0)