11"""
22Data manipulations for 2D data sets.
3- Using the meta data information, various types of averaging are performed in Q-space
3+ Using the meta data information, various types of averaging are performed in Q-space.
44
55To test this module use:
66```
77cd test
8- PYTHONPATH=../src/ python2 -m sasmanipulations.test.utest_averaging DataInfoTests.test_sectorphi_quarter
8+ PYTHONPATH=../src/ python2 -m sasmanipulations.test.utest_averaging DataInfoTests.test_sectorphi_quarter
99```
1010"""
1111#####################################################################
4242 SlabY ,
4343 WedgePhi ,
4444 WedgeQ ,
45+ get_dq_data ,
4546)
4647from sasdata .dataloader .data_info import Data1D , Data2D
4748from sasdata .dataloader .data_info import reader2D_converter as _di_reader2D_converter
@@ -70,10 +71,10 @@ def deduce_qz(qx: float, qy: float, wavelength: float) -> float:
7071
7172def position_and_wavelength_to_q (dx : float , dy : float , detector_distance : float , wavelength : float ) -> float :
7273 """
73- :param dx: x-distance from beam center [mm]
74- :param dy: y-distance from beam center [mm]
75- :param detector_distance: sample to detector distance [mm]
76- :param wavelength: neutron wavelength [nm]
74+ :param dx: x-distance from beam center
75+ :param dy: y-distance from beam center
76+ :param detector_distance: sample to detector distance
77+ :param wavelength: neutron wavelength
7778 :return: q-value at the given position
7879 """
7980 # Distance from beam center in the plane of detector
@@ -233,55 +234,13 @@ def get_pixel_fraction(q_max: float, q_00: float, q_01: float, q_10: float, q_11
233234 return frac_max
234235
235236
236- def get_dq_data (data2d : Data2D ) -> np .array :
237- '''
238- Get the dq for resolution averaging
239- The pinholes and det. pix contribution present
240- in both direction of the 2D which must be subtracted when
241- converting to 1D: dq_overlap should be calculated ideally at
242- q = 0. Note This method works on only pinhole geometry.
243- Extrapolate dqx(r) and dqy(phi) at q = 0, and take an average.
244- '''
245- z_max = max (data2d .q_data )
246- z_min = min (data2d .q_data )
247- dqx_at_z_max = data2d .dqx_data [np .argmax (data2d .q_data )]
248- dqx_at_z_min = data2d .dqx_data [np .argmin (data2d .q_data )]
249- dqy_at_z_max = data2d .dqy_data [np .argmax (data2d .q_data )]
250- dqy_at_z_min = data2d .dqy_data [np .argmin (data2d .q_data )]
251- # Find qdx at q = 0
252- dq_overlap_x = (dqx_at_z_min * z_max - dqx_at_z_max * z_min ) / (z_max - z_min )
253- # when extrapolation goes wrong
254- if dq_overlap_x > min (data2d .dqx_data ):
255- dq_overlap_x = min (data2d .dqx_data )
256- dq_overlap_x *= dq_overlap_x
257- # Find qdx at q = 0
258- dq_overlap_y = (dqy_at_z_min * z_max - dqy_at_z_max * z_min ) / (z_max - z_min )
259- # when extrapolation goes wrong
260- if dq_overlap_y > min (data2d .dqy_data ):
261- dq_overlap_y = min (data2d .dqy_data )
262- # get dq at q=0.
263- dq_overlap_y *= dq_overlap_y
264-
265- dq_overlap = np .sqrt ((dq_overlap_x + dq_overlap_y ) / 2.0 )
266- # Final protection of dq
267- if dq_overlap < 0 :
268- dq_overlap = dqy_at_z_min
269- dqx_data = data2d .dqx_data [np .isfinite (data2d .data )]
270- dqy_data = data2d .dqy_data [np .isfinite (
271- data2d .data )] - dq_overlap
272- # def; dqx_data = dq_r dqy_data = dq_phi
273- # Convert dq 2D to 1D here
274- dq_data = np .sqrt (dqx_data ** 2 + dqy_data ** 2 )
275- return dq_data
276-
277237################################################################################
278238
279239
280240def reader2D_converter (data2d : Data2D | None = None ) -> Data2D :
281241 """
282- convert old 2d format opened by IhorReader or danse_reader
283- to new Data2D format
284- This is mainly used by the Readers
242+ Convert old 2d format opened by IhorReader or danse_reader to new Data2D format.
243+ This is mainly used by the Readers.
285244
286245 :param data2d: 2d array of Data2D object
287246 :return: 1d arrays of Data2D object
@@ -358,8 +317,10 @@ def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0,
358317 width = max (abs (x_max - x_min ), 1e-12 )
359318 nbins = int (math .ceil (width / abs (bin_width ))) if bin_width != 0 else 1
360319 self .nbins = nbins
361- super ().__init__ (qx_range = (x_min , x_max ), qy_range = (y_min , y_max ),
362- nbins = nbins , fold = fold )
320+ super ().__init__ (qx_range = (x_min , x_max ),
321+ qy_range = (y_min , y_max ),
322+ nbins = nbins ,
323+ fold = fold )
363324
364325
365326class SlabY (SlabY ):
@@ -382,22 +343,22 @@ def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0,
382343 height = max (abs (y_max - y_min ), 1e-12 )
383344 nbins = int (math .ceil (height / abs (bin_width ))) if bin_width != 0 else 1
384345 self .nbins = nbins
385- super ().__init__ (qx_range = (x_min , x_max ), qy_range = (y_min , y_max ),
386- nbins = nbins , fold = fold )
346+ super ().__init__ (qx_range = (x_min , x_max ),
347+ qy_range = (y_min , y_max ),
348+ nbins = nbins ,
349+ fold = fold )
387350
388351
389352################################################################################
390353
391354
392355class Boxsum (Boxsum ):
393356 def __init__ (self , x_min = 0.0 , x_max = 0.0 , y_min = 0.0 , y_max = 0.0 ):
394-
395357 super ().__init__ (qx_range = (x_min , x_max ), qy_range = (y_min , y_max ))
396358
397359
398360class Boxavg (Boxavg ):
399361 def __init__ (self , x_min = 0.0 , x_max = 0.0 , y_min = 0.0 , y_max = 0.0 ):
400-
401362 super ().__init__ (qx_range = (x_min , x_max ), qy_range = (y_min , y_max ))
402363
403364################################################################################
@@ -422,23 +383,22 @@ def __call__(self, data2D, ismask=False):
422383 """
423384 Perform circular averaging on the data
424385
425- :param data2D: Data2D object
386+ :param data2D: SasData object
426387 :return: Data1D object
427388 """
428389 # Get data W/ finite values
429- data = data2D .data [np .isfinite (data2D .data )]
430- q_data = data2D .q_data [np .isfinite (data2D .data )]
431- err_data = None
432- if data2D .err_data is not None :
433- err_data = data2D .err_data [np .isfinite (data2D .data )]
434- mask_data = data2D .mask [np .isfinite (data2D .data )]
390+ finite_mask = np .isfinite (data2D ._data_contents ["I" ].value )
391+ data = data2D ._data_contents ["I" ].value [finite_mask ]
392+ q_data = np .sqrt (data2D ._data_contents ["Qx" ].value ** 2 + data2D ._data_contents ["Qy" ].value ** 2 )[finite_mask ]
393+ err_data = np .sqrt (data2D ._data_contents ["I" ].variance .value )[finite_mask ]
394+ mask_data = (data2D .mask if data2D .mask is not None else np .ones_like (data2D ._data_contents ["I" ].value , dtype = bool ))[finite_mask ]
435395
436396 dq_data = None
437- if data2D .dqx_data is not None and data2D .dqy_data is not None :
397+ if data2D ._data_contents [ "Qx" ]. has_variance and data2D ._data_contents [ "Qy" ]. has_variance :
438398 dq_data = get_dq_data (data2D )
439399
440400 if len (q_data ) == 0 :
441- msg = "Circular averaging: invalid q_data: %g" % data2D . q_data
401+ msg = "Circular averaging: invalid q_data: %g" % q_data
442402 raise RuntimeError (msg )
443403
444404 # Build array of Q intervals
@@ -523,10 +483,7 @@ def __call__(self, data2D, ismask=False):
523483
524484
525485class Ring (Ring ):
526- """
527- Wrapper for new Ring.
528- """
529-
486+ """Wrapper for new Ring."""
530487
531488 @property
532489 def nbins_phi (self ):
@@ -537,12 +494,9 @@ def nbins_phi(self, value):
537494 self .nbins = value
538495
539496 def __init__ (self , r_min = 0.0 , r_max = 0.0 , center_x = 0.0 , center_y = 0.0 , nbins = 36 ):
540-
541- super ().__init__ (
542- r_range = (r_min , r_max ),
543- center = (center_x , center_y ),
544- nbins = nbins
545- )
497+ super ().__init__ (r_range = (r_min , r_max ),
498+ center = (center_x , center_y ),
499+ nbins = nbins )
546500
547501class _Sector :
548502 """
@@ -556,12 +510,10 @@ class _Sector:
556510 starting from the negative x-axis.
557511 """
558512
559- def __init__ (self , r_min , r_max , phi_min = 0 , phi_max = 2 * math .pi , nbins = 20 ,
560- base = None ):
561- '''
562- :param base: must be a valid base for an algorithm, i.e.,
563- a positive number
564- '''
513+ def __init__ (self , r_min , r_max , phi_min = 0 , phi_max = 2 * math .pi , nbins = 20 , base = None ):
514+ """
515+ :param base: must be a valid base for an algorithm, i.e., a positive number
516+ """
565517 self .r_min = r_min
566518 self .r_max = r_max
567519 self .phi_min = phi_min
@@ -576,26 +528,29 @@ def _agv(self, data2D, run='phi'):
576528 """
577529 Perform sector averaging.
578530
579- :param data2D: Data2D object
580- :param run: define the varying parameter ('phi' , or 'sector')
531+ :param data2D: SasData object
532+ :param run: define the varying parameter ('phi' or 'sector')
581533
582534 :return: Data1D object
583535 """
584- if data2D .__class__ .__name__ not in ["Data2D" , "plottable_2D" ]:
585- raise RuntimeError ("Ring averaging only take plottable_2D objects" )
536+ if not ("Qx" in data2D ._data_contents and
537+ "Qy" in data2D ._data_contents and
538+ "I" in data2D ._data_contents ):
539+ raise RuntimeError ("For ring averaging the SasData object must contain 'Qx', 'Qy', and 'I' data." )
586540
587541 # Get all the data & info
588- data = data2D .data [np .isfinite (data2D .data )]
589- q_data = data2D .q_data [np .isfinite (data2D .data )]
590- err_data = None
591- if data2D .err_data is not None :
592- err_data = data2D .err_data [np .isfinite (data2D .data )]
593- qx_data = data2D .qx_data [np .isfinite (data2D .data )]
594- qy_data = data2D .qy_data [np .isfinite (data2D .data )]
595- mask_data = data2D .mask [np .isfinite (data2D .data )]
542+ finite_mask = np .isfinite (data2D ._data_contents ["I" ].value )
543+ data = data2D ._data_contents ["I" ].value [finite_mask ]
544+ err_data = np .sqrt (data2D ._data_contents ["I" ].variance .value )[finite_mask ]
545+ qx_data = data2D ._data_contents ["Qx" ].value [finite_mask ]
546+ qy_data = data2D ._data_contents ["Qy" ].value [finite_mask ]
547+ q_data = np .sqrt (data2D ._data_contents ["Qx" ].value ** 2 +
548+ data2D ._data_contents ["Qy" ].value ** 2
549+ )[finite_mask ]
550+ mask_data = (data2D .mask if data2D .mask is not None else np .ones_like (data2D ._data_contents ["I" ].value , dtype = bool ))[finite_mask ]
596551
597552 dq_data = None
598- if data2D .dqx_data is not None and data2D .dqy_data is not None :
553+ if data2D ._data_contents [ "Qx" ]. has_variance and data2D ._data_contents [ "Qy" ]. has_variance :
599554 dq_data = get_dq_data (data2D )
600555
601556 # set space for 1d outputs
@@ -775,7 +730,7 @@ def __call__(self, data2D):
775730 """
776731 Perform sector average and return I(phi).
777732
778- :param data2D: Data2D object
733+ :param data2D: SasData object
779734 :return: Data1D object
780735 """
781736 return self ._agv (data2D , 'phi' )
@@ -798,51 +753,38 @@ def __call__(self, data2D):
798753 """
799754 Perform sector average and return I(Q).
800755
801- :param data2D: Data2D object
756+ :param data2D: SasData object
802757
803758 :return: Data1D object
804759 """
805760 return self ._agv (data2D , 'sector' )
806761
807762
808763class WedgePhi (WedgePhi ):
809- """
810- Wrapper for new WedgePhi (behaviour matches legacy WedgePhi expectations).
811- """
764+ """Wrapper for new WedgePhi (behaviour matches legacy WedgePhi expectations)."""
812765 def __init__ (self , r_min , r_max , phi_min = 0 , phi_max = TwoPi , center_x = 0.0 , center_y = 0.0 , nbins = 10 ):
813-
814- super ().__init__ (
815- r_range = (r_min , r_max ),
816- phi_range = (phi_min , phi_max ),
817- center = (center_x , center_y ),
818- nbins = nbins
819- )
766+ super ().__init__ (r_range = (r_min , r_max ),
767+ phi_range = (phi_min , phi_max ),
768+ center = (center_x , center_y ),
769+ nbins = nbins )
820770
821771class WedgeQ (WedgeQ ):
822- """
823- Wrapper for new WedgeQ (behaviour matches legacy WedgeQ expectations).
824- """
772+ """Wrapper for new WedgeQ (behaviour matches legacy WedgeQ expectations)."""
825773 def __init__ (self , r_min , r_max , phi_min = 0 , phi_max = TwoPi , center_x = 0.0 , center_y = 0.0 , nbins = 10 ):
826-
827- super ().__init__ (
828- r_range = (r_min , r_max ),
829- phi_range = (phi_min , phi_max ),
830- center = (center_x , center_y ),
831- nbins = nbins
832- )
774+ super ().__init__ (r_range = (r_min , r_max ),
775+ phi_range = (phi_min , phi_max ),
776+ center = (center_x , center_y ),
777+ nbins = nbins )
833778
834779################################################################################
835780
836781
837782class Ringcut (Ringcut ):
838783 def __init__ (self , r_min = 0.0 , r_max = 0.0 , center_x = 0.0 , center_y = 0.0 ):
839784 # center_x, center_y ignored for compatibility
840-
841- super ().__init__ (
842- r_range = (r_min , r_max ),
843- phi_range = (0.0 , TwoPi ),
844- center = (center_x , center_y )
845- )
785+ super ().__init__ (r_range = (r_min , r_max ),
786+ phi_range = (0.0 , TwoPi ),
787+ center = (center_x , center_y ))
846788
847789################################################################################
848790
0 commit comments