Skip to content

Commit 068ee67

Browse files
committed
Refactors sasmanipulations to use new data objects for output
1 parent ebb93a5 commit 068ee67

5 files changed

Lines changed: 148 additions & 123 deletions

File tree

sasdata/data_util/averaging.py

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
from sasdata.data_util.binning import DirectionalAverage
1010
from sasdata.data_util.interval import IntervalType
1111
from sasdata.data_util.roi import CartesianROI, PolarROI
12-
from sasdata.dataloader.data_info import Data1D
12+
from sasdata.dataset_types import one_dim
1313
from sasdata.quantities.constants import Pi, TwoPi
14+
from sasdata.quantities.quantity import Quantity
1415

1516

1617
def get_dq_data(data2d: SasData) -> npt.NDArray[np.floating]:
@@ -140,7 +141,7 @@ class SlabX(CartesianROI):
140141
Average I(Q_x, Q_y) along the y direction (within a ROI), giving I(Q_x).
141142
142143
This class is initialised by specifying the boundaries of the ROI and is
143-
called by supplying a SasData object. It returns a Data1D object.
144+
called by supplying a SasData object. It returns a SasData object.
144145
The averaging process can also be thought of as projecting 2D -> 1D.
145146
146147
There also exists the option to "fold" the ROI, where Q data on opposite
@@ -170,12 +171,12 @@ def __init__(
170171
self.fold: bool = fold
171172
self.base: float | None = base
172173

173-
def __call__(self, data2d: SasData = None) -> Data1D:
174+
def __call__(self, data2d: SasData = None) -> SasData:
174175
"""
175176
Compute the 1D average of 2D data, projecting along the Q_x axis.
176177
177178
:param data2d: The SasData object for which the average is computed.
178-
:return: Data1D object for plotting.
179+
:return: SasData object for plotting.
179180
"""
180181
self.validate_and_assign_data(data2d)
181182

@@ -201,15 +202,19 @@ def __call__(self, data2d: SasData = None) -> Data1D:
201202

202203
qx_data, intensity, error = directional_average(data=self.data, err_data=self.err_data)
203204

204-
return Data1D(x=qx_data, y=intensity, dy=error)
205+
data_contents = {
206+
"Q": Quantity(qx_data, data2d._data_contents["Qx"].units, None),
207+
"I": Quantity(intensity, data2d._data_contents["I"].units, error),
208+
}
209+
return SasData(f"{data2d.name}: Slab X Average", data_contents, one_dim, data2d.metadata)
205210

206211

207212
class SlabY(CartesianROI):
208213
"""
209214
Average I(Q_x, Q_y) along the x direction (within a ROI), giving I(Q_y).
210215
211216
This class is initialised by specifying the boundaries of the ROI and is
212-
called by supplying a SasData object. It returns a Data1D object.
217+
called by supplying a SasData object. It returns a SasData object.
213218
The averaging process can also be thought of as projecting 2D -> 1D.
214219
215220
There also exists the option to "fold" the ROI, where Q data on opposite
@@ -240,12 +245,12 @@ def __init__(
240245
self.fold: bool = fold
241246
self.base: float | None = base
242247

243-
def __call__(self, data2d: SasData = None) -> Data1D:
248+
def __call__(self, data2d: SasData = None) -> SasData:
244249
"""
245250
Compute the 1D average of 2D data, projecting along the Q_y axis.
246251
247252
:param data2d: The SasData object for which the average is computed.
248-
:return: Data1D object for plotting.
253+
:return: SasData object for plotting.
249254
"""
250255
self.validate_and_assign_data(data2d)
251256

@@ -270,7 +275,11 @@ def __call__(self, data2d: SasData = None) -> Data1D:
270275
)
271276
qy_data, intensity, error = directional_average(data=self.data, err_data=self.err_data)
272277

273-
return Data1D(x=qy_data, y=intensity, dy=error)
278+
data_contents = {
279+
"Q": Quantity(qy_data, data2d._data_contents["Qy"].units, None),
280+
"I": Quantity(intensity, data2d._data_contents["I"].units, error),
281+
}
282+
return SasData(f"{data2d.name}: Slab Y Average", data_contents, one_dim, data2d.metadata)
274283

275284

276285
class CircularAverage(PolarROI):
@@ -280,7 +289,7 @@ class CircularAverage(PolarROI):
280289
This class is initialised by specifying lower and upper limits on the
281290
magnitude of Q values to consider during the averaging, though currently
282291
SasView always calls this class using the full range of data. When called,
283-
this class is supplied with a SasData object. It returns a Data1D object
292+
this class is supplied with a SasData object. It returns a SasData object
284293
where intensity is given as a function of Q only.
285294
"""
286295

@@ -302,15 +311,15 @@ def __init__(
302311
self.nbins: int = nbins
303312
self.base: float | None = base
304313

305-
def __call__(self, data2D: SasData, ismask: bool = False) -> Data1D:
314+
def __call__(self, data2D: SasData, ismask: bool = False) -> SasData:
306315
"""
307316
Perform circular averaging on the data. Uses DirectionalAverage for
308317
bin construction and weights, and computes dx (d_q) using get_dq_data
309318
averaged with those weights so behavior matches the legacy implementation.
310319
311320
:param data2D: SasData object
312321
:param ismask: If True, respect data2D.mask (skip masked points). If False, ignore mask.
313-
:return: Data1D object with x (bin centers), y (intensity), dy and dx (if available)
322+
:return: SasData object with x (bin centers), y (intensity), dy and dx (if available)
314323
"""
315324
# Work on unmasked finite arrays first (matches legacy filtering)
316325
finite_mask = np.isfinite(data2D._data_contents["I"].value)
@@ -369,11 +378,16 @@ def __call__(self, data2D: SasData, ismask: bool = False) -> Data1D:
369378
counts = np.sum(weights, axis=1)
370379
with np.errstate(divide="ignore", invalid="ignore"):
371380
dx_full = dq_weighted / counts
372-
dx = dx_full[populated]
381+
dQ = dx_full[populated]
373382
else:
374-
dx = None
383+
dQ = None
384+
385+
data_contents = {
386+
"Q": Quantity(x, data2D._data_contents["Qx"].units, dQ),
387+
"I": Quantity(intensity, data2D._data_contents["I"].units, error),
388+
}
389+
return SasData(f"{data2D.name}: Circular Average", data_contents, one_dim, data2D.metadata)
375390

376-
return Data1D(x=x, y=intensity, dy=error, dx=dx)
377391

378392

379393
class Ring(PolarROI):
@@ -382,9 +396,8 @@ class Ring(PolarROI):
382396
383397
This class is initialised by specifying lower and upper limits on the
384398
magnitude of Q values to consider during the averaging. When called,
385-
this class is supplied with a SasData object. It returns a Data1D object.
386-
This Data1D object gives intensity as a function of the angle from the
387-
positive x-axis, φ, only.
399+
this class is supplied with a SasData object. It returns a SasData object
400+
which gives intensity as a function of the angle from the positive x-axis, φ, only.
388401
"""
389402

390403
def __init__(
@@ -408,14 +421,14 @@ def __init__(
408421
self.nbins: int = nbins
409422
self.base: float | None = base
410423

411-
def __call__(self, data2D: SasData) -> Data1D:
424+
def __call__(self, data2D: SasData) -> SasData:
412425
"""
413426
Apply the ring to the data set.
414427
Returns the angular distribution for a given q range
415428
416429
:param data2D: SasData object
417430
418-
:return: Data1D object
431+
:return: SasData object
419432
"""
420433
if not isinstance(data2D, SasData):
421434
msg = "Data supplied for ring averaging must be of type SasData."
@@ -489,7 +502,11 @@ def __call__(self, data2D: SasData) -> Data1D:
489502
msg = "Average Error: No points inside ROI to average..."
490503
raise ValueError(msg)
491504

492-
return Data1D(x=phi_values[idx], y=phi_bins[idx], dy=phi_err[idx])
505+
data_contents = {
506+
"Q": Quantity(phi_values[idx], data2D._data_contents["Qx"].units, None),
507+
"I": Quantity(phi_bins[idx], data2D._data_contents["I"].units, phi_err[idx]),
508+
}
509+
return SasData(f"{data2D.name}: Ring Average", data_contents, one_dim, data2D.metadata)
493510

494511

495512
class SectorQ(PolarROI):
@@ -511,7 +528,7 @@ class SectorQ(PolarROI):
511528
ROI data labelled using negative Q values.
512529
513530
When called, this class is supplied with a SasData object. It returns a
514-
Data1D object where intensity is given as a function of Q only.
531+
SasData object where intensity is given as a function of Q only.
515532
"""
516533

517534
def __init__(
@@ -539,12 +556,12 @@ def __init__(
539556
self.fold: bool = fold
540557
self.base: float | None = base
541558

542-
def __call__(self, data2d: SasData = None) -> Data1D:
559+
def __call__(self, data2d: SasData = None) -> SasData:
543560
"""
544561
Compute the 1D average of 2D data, projecting along the Q_y axis.
545562
546563
:param data2d: The SasData object for which the average is computed.
547-
:return: Data1D object for plotting.
564+
:return: SasData object for plotting.
548565
"""
549566
self.validate_and_assign_data(data2d)
550567

@@ -621,15 +638,22 @@ def __call__(self, data2d: SasData = None) -> Data1D:
621638

622639
finite = np.isfinite(average_intensity)
623640

624-
data1d = Data1D(x=combined_q[finite], y=average_intensity[finite], dy=combined_err[finite])
641+
data_contents = {
642+
"Q": Quantity(combined_q[finite], data2d._data_contents["Qx"].units, None),
643+
"I": Quantity(average_intensity[finite], data2d._data_contents["I"].units, combined_err[finite]),
644+
}
625645
else:
626646
# The secondary ROI is labelled with negative Q values.
627647
combined_q = np.append(np.flip(-1 * secondary_q), primary_q)
628648
combined_intensity = np.append(np.flip(secondary_I), primary_I)
629649
combined_error = np.append(np.flip(secondary_err), primary_err)
630-
data1d = Data1D(x=combined_q, y=combined_intensity, dy=combined_error)
631650

632-
return data1d
651+
data_contents = {
652+
"Q": Quantity(combined_q, data2d._data_contents["Qx"].units, None),
653+
"I": Quantity(combined_intensity, data2d._data_contents["I"].units, combined_error),
654+
}
655+
656+
return SasData(f"{data2d.name}:SectorQ Average", data_contents, one_dim, data2d.metadata)
633657

634658

635659
class WedgeQ(PolarROI):
@@ -643,7 +667,7 @@ class WedgeQ(PolarROI):
643667
This class is initialised by specifying lower and upper limits on both the
644668
magnitude of Q and the angle φ.
645669
When called, this class is supplied with a SasData object. It returns a
646-
Data1D object where intensity is given as a function of Q only.
670+
sasData object where intensity is given as a function of Q only.
647671
"""
648672

649673
def __init__(
@@ -666,12 +690,12 @@ def __init__(
666690
self.nbins: int = nbins
667691
self.base: float | None = base
668692

669-
def __call__(self, data2d: SasData = None) -> Data1D:
693+
def __call__(self, data2d: SasData = None) -> SasData:
670694
"""
671695
Compute the 1D average of 2D data, projecting along the Q_y axis.
672696
673697
:param data2d: The SasData object for which the average is computed.
674-
:return: Data1D object for plotting.
698+
:return: SasData object for plotting.
675699
"""
676700
self.validate_and_assign_data(data2d)
677701

@@ -711,7 +735,11 @@ def __call__(self, data2d: SasData = None) -> Data1D:
711735

712736
q_data, intensity, error = directional_average(data=self.data, err_data=self.err_data)
713737

714-
return Data1D(x=q_data, y=intensity, dy=error)
738+
data_contents = {
739+
"Q": Quantity(q_data, data2d._data_contents["Qx"].units, None),
740+
"I": Quantity(intensity, data2d._data_contents["I"].units, error),
741+
}
742+
return SasData(f"{data2d.name}: Wedge Q Average", data_contents, one_dim, data2d.metadata)
715743

716744

717745
class WedgePhi(PolarROI):
@@ -724,7 +752,7 @@ class WedgePhi(PolarROI):
724752
This class is initialised by specifying lower and upper limits on both the
725753
magnitude of Q and the angle φ, measured anticlockwise from the positive
726754
x-axis. When called, this class is supplied with a SasData object. It returns
727-
a Data1D object where intensity is given as a function of Q only.
755+
a SasData object where intensity is given as a function of Q only.
728756
"""
729757

730758
def __init__(
@@ -748,12 +776,12 @@ def __init__(
748776
self.nbins: int = nbins
749777
self.base: float | None = base
750778

751-
def __call__(self, data2d: SasData = None) -> Data1D:
779+
def __call__(self, data2d: SasData = None) -> SasData:
752780
"""
753781
Compute the 1D average of 2D data, projecting along the Q_y axis.
754782
755783
:param data2d: The SasData object for which the average is computed.
756-
:return: Data1D object for plotting.
784+
:return: SasData object for plotting.
757785
"""
758786
self.validate_and_assign_data(data2d)
759787

@@ -815,21 +843,11 @@ def __call__(self, data2d: SasData = None) -> Data1D:
815843
phi_centers = full_phi[populated] + directional_average.bin_widths[populated] / 2.0
816844

817845
# intensity and error returned by DirectionalAverage are already filtered to the populated/finite bins
818-
return Data1D(x=phi_centers, y=intensity, dy=error)
819-
820-
"""
821-
# Convert angular data back to the original phi range
822-
phi_data += phi_offset
823-
# In the old manipulations.py, we also had this shift to plot the data
824-
# at the centre of the bins. I'm not sure why it's only angular binning
825-
# which gets this treatment.
826-
# TODO: Update this once non-linear binning options are implemented
827-
weights = directional_average.compute_weights()
828-
populated = np.sum(weights, axis=1) > 0
829-
phi_data += directional_average.bin_widths[populated] / 2
830-
831-
return Data1D(x=phi_data, y=intensity, dy=error)
832-
"""
846+
data_contents = {
847+
"Q": Quantity(phi_centers, data2d._data_contents["Qx"].units, None),
848+
"I": Quantity(intensity, data2d._data_contents["I"].units, error),
849+
}
850+
return SasData(f"{data2d.name}: Wedge Phi Average", data_contents, one_dim, data2d.metadata)
833851

834852

835853
class SectorPhi(WedgePhi):

0 commit comments

Comments
 (0)