8
8
from mantid .kernel import Logger , Property , PropertyManager
9
9
from mantid .simpleapi import (
10
10
AbsorptionCorrection ,
11
+ DefineGaugeVolume ,
11
12
DeleteWorkspace ,
12
13
Divide ,
13
14
Load ,
24
25
import numpy as np
25
26
import os
26
27
from functools import wraps
28
+ import xml .etree .ElementTree as ET
27
29
28
30
VAN_SAMPLE_DENSITY = 0.0721
29
31
_EXTENSIONS_NXS = ["_event.nxs" , ".nxs.h5" ]
32
+ GV_VALS = {
33
+ "X" : {"x" : "0.2" , "y" : "0.0" , "z" : "0.0" , "t" : "90.0" , "p" : "180.0" },
34
+ "Y" : {"x" : "0.0" , "y" : "0.2" , "z" : "0.0" , "t" : "90.0" , "p" : "270.0" },
35
+ "Z" : {"x" : "0.0" , "y" : "0.0" , "z" : "0.2" , "t" : "180.0" , "p" : "0.0" },
36
+ }
30
37
31
38
32
39
# ---------------------------- #
@@ -284,6 +291,11 @@ def calculate_absorption_correction(
284
291
sample_formula ,
285
292
mass_density ,
286
293
sample_geometry = {},
294
+ can_geometry = {},
295
+ can_material = {},
296
+ gauge_vol = "" ,
297
+ container_gauge_vol = "" ,
298
+ beam_height = Property .EMPTY_DBL ,
287
299
number_density = Property .EMPTY_DBL ,
288
300
container_shape = "PAC06" ,
289
301
num_wl_bins = 1000 ,
@@ -323,6 +335,11 @@ def calculate_absorption_correction(
323
335
:param sample_formula: Sample formula to specify the Material for absorption correction
324
336
:param mass_density: Mass density of the sample to specify the Material for absorption correction
325
337
:param sample_geometry: Dictionary to specify the sample geometry for absorption correction
338
+ :param can_geometry: Dictionary to specify the container geometry for absorption correction
339
+ :param can_material: Dictionary to specify the container material for absorption correction
340
+ :param gauge_vol: String in XML form to define the volume of the sample visible to the beam
341
+ :param container_gauge_vol: String in XML form to define the volume of the container visible to the beam
342
+ :param beam_height: Optional beam height to use for absorption correction
326
343
:param number_density: Optional number density of sample to be added to the Material for absorption correction
327
344
:param container_shape: Shape definition of container, such as PAC06.
328
345
:param num_wl_bins: Number of bins for calculating wavelength
@@ -344,12 +361,26 @@ def calculate_absorption_correction(
344
361
material ["SampleNumberDensity" ] = number_density
345
362
346
363
environment = {}
347
- if container_shape :
364
+ find_env = True
365
+ if container_shape or (can_geometry and can_material ):
348
366
environment ["Name" ] = "InAir"
349
- environment ["Container" ] = container_shape
367
+ find_env = False
368
+ if not (can_geometry and can_material ):
369
+ environment ["Container" ] = container_shape
350
370
351
371
donorWS = create_absorption_input (
352
- filename , props , num_wl_bins , material = material , geometry = sample_geometry , environment = environment , metaws = metaws
372
+ filename ,
373
+ props ,
374
+ num_wl_bins ,
375
+ material = material ,
376
+ geometry = sample_geometry ,
377
+ can_geometry = can_geometry ,
378
+ can_material = can_material ,
379
+ gauge_vol = gauge_vol ,
380
+ beam_height = beam_height ,
381
+ environment = environment ,
382
+ find_environment = find_env ,
383
+ metaws = metaws ,
353
384
)
354
385
355
386
# NOTE: Ideally we want to separate cache related task from calculation,
@@ -381,6 +412,8 @@ def calculate_absorption_correction(
381
412
donorWS ,
382
413
abs_method ,
383
414
element_size ,
415
+ container_gauge_vol = container_gauge_vol ,
416
+ beam_height = beam_height ,
384
417
prefix_name = absName ,
385
418
cache_dirs = cache_dirs ,
386
419
ms_method = ms_method ,
@@ -392,6 +425,8 @@ def calc_absorption_corr_using_wksp(
392
425
donor_wksp ,
393
426
abs_method ,
394
427
element_size = 1 ,
428
+ container_gauge_vol = "" ,
429
+ beam_height = Property .EMPTY_DBL ,
395
430
prefix_name = "" ,
396
431
cache_dirs = [],
397
432
ms_method = "" ,
@@ -401,7 +436,7 @@ def calc_absorption_corr_using_wksp(
401
436
if cache_dirs :
402
437
log .warning ("Empty cache dir found." )
403
438
# 1. calculate first order absorption correction
404
- abs_s , abs_c = calc_1st_absorption_corr_using_wksp (donor_wksp , abs_method , element_size , prefix_name )
439
+ abs_s , abs_c = calc_1st_absorption_corr_using_wksp (donor_wksp , abs_method , element_size , container_gauge_vol , beam_height , prefix_name )
405
440
# 2. calculate 2nd order absorption correction
406
441
if ms_method in ["" , None , "None" ]:
407
442
log .information ("Skip multiple scattering correction as instructed." )
@@ -452,6 +487,8 @@ def calc_1st_absorption_corr_using_wksp(
452
487
donor_wksp ,
453
488
abs_method ,
454
489
element_size = 1 ,
490
+ container_gauge_vol = "" ,
491
+ beam_height = Property .EMPTY_DBL ,
455
492
prefix_name = "" ,
456
493
):
457
494
"""
@@ -461,6 +498,8 @@ def calc_1st_absorption_corr_using_wksp(
461
498
:param donor_wksp: Input workspace to compute absorption correction on
462
499
:param abs_method: Type of absorption correction: None, SampleOnly, SampleAndContainer, FullPaalmanPings
463
500
:param element_size: Size of one side of the integration element cube in mm
501
+ :param container_gauge_vol: String in XML form to define the volume of container visible to the beam
502
+ :param beam_height: Beam height for defining the gauge volume for container visible to the beam
464
503
:param prefix_name: Optional prefix of the output workspaces, default is the donor_wksp name.
465
504
466
505
:return: Two workspaces (A_s, A_c), the first for the sample and the second for the container
@@ -473,6 +512,13 @@ def calc_1st_absorption_corr_using_wksp(
473
512
raise RuntimeError ("Specified donor workspace not found in the ADS" )
474
513
donor_wksp = mtd [donor_wksp ]
475
514
515
+ def is_valid_xml (xml_string ):
516
+ try :
517
+ ET .fromstring (xml_string )
518
+ return True
519
+ except ET .ParseError :
520
+ return False
521
+
476
522
absName = donor_wksp .name ()
477
523
if prefix_name != "" :
478
524
absName = prefix_name
@@ -482,6 +528,42 @@ def calc_1st_absorption_corr_using_wksp(
482
528
return absName + "_ass" , ""
483
529
elif abs_method == "SampleAndContainer" :
484
530
AbsorptionCorrection (donor_wksp , OutputWorkspace = absName + "_ass" , ScatterFrom = "Sample" , ElementSize = element_size )
531
+ if container_gauge_vol and is_valid_xml (container_gauge_vol ):
532
+ try :
533
+ DefineGaugeVolume (donor_wksp , container_gauge_vol )
534
+ except ValueError :
535
+ pass
536
+ elif container_gauge_vol and beam_height != Property .EMPTY_DBL :
537
+ ref_frame = donor_wksp .getInstrument ().getReferenceFrame ()
538
+ up_direction = ref_frame .pointingUpAxis ()
539
+ info_to_use = GV_VALS [up_direction ]
540
+
541
+ # Factor '100' here is for unit conversion from cm to m. An extra factor
542
+ # of '2' in 'beam_height / 200.0' is to make sure the center of the
543
+ # bottom base of the gauge volume is down from the origin by half of the
544
+ # gauge volume height so the center of the gauge volume is at the origin.
545
+ # This is our assumed location of the beam if only beam height is given.
546
+ gauge_vol = """<hollow-cylinder id="container_gauge">
547
+ <centre-of-bottom-base r="{0:4.2F}" t="{1:s}" p="{2:s}" />
548
+ <axis x="{3:s}" y="{4:s}" z="{5:s}" />
549
+ <inner-radius val="{6:7.5F}" />
550
+ <outer-radius val="{7:7.5F}" />
551
+ <height val="{8:4.2F}" />
552
+ </hollow-cylinder>
553
+ """
554
+ gauge_vol = gauge_vol .format (
555
+ beam_height / 200.0 ,
556
+ info_to_use ["t" ],
557
+ info_to_use ["p" ],
558
+ info_to_use ["x" ],
559
+ info_to_use ["y" ],
560
+ info_to_use ["z" ],
561
+ float (container_gauge_vol .split ()[0 ]) / 100.0 ,
562
+ float (container_gauge_vol .split ()[1 ]) / 100.0 ,
563
+ beam_height / 100.0 ,
564
+ )
565
+ DefineGaugeVolume (donor_wksp , gauge_vol )
566
+
485
567
AbsorptionCorrection (donor_wksp , OutputWorkspace = absName + "_acc" , ScatterFrom = "Container" , ElementSize = element_size )
486
568
return absName + "_ass" , absName + "_acc"
487
569
elif abs_method == "FullPaalmanPings" :
@@ -499,6 +581,10 @@ def create_absorption_input(
499
581
num_wl_bins = 1000 ,
500
582
material = {},
501
583
geometry = {},
584
+ can_geometry = {},
585
+ can_material = {},
586
+ gauge_vol = "" ,
587
+ beam_height = Property .EMPTY_DBL ,
502
588
environment = {},
503
589
find_environment = True ,
504
590
opt_wl_min = 0 ,
@@ -513,6 +599,10 @@ def create_absorption_input(
513
599
:param num_wl_bins: The number of wavelength bins used for absorption correction
514
600
:param material: Optional material to use in SetSample
515
601
:param geometry: Optional geometry to use in SetSample
602
+ :param can_geometry: Optional container geometry to use in SetSample
603
+ :param can_material: Optional container material to use in SetSample
604
+ :param gauge_vol: Optional gauge volume definition, i.e., sample portion visible to the beam.
605
+ :param beam_height: Optional beam height to define gauge volume
516
606
:param environment: Optional environment to use in SetSample
517
607
:param find_environment: Optional find_environment to control whether to figure out environment automatically.
518
608
:param opt_wl_min: Optional minimum wavelength. If specified, this is used instead of from the props
@@ -611,7 +701,49 @@ def confirmProps(props):
611
701
# Make sure one is set before calling SetSample
612
702
if material or geometry or environment :
613
703
mantid .simpleapi .SetSampleFromLogs (
614
- InputWorkspace = absName , Material = material , Geometry = geometry , Environment = environment , FindEnvironment = find_environment
704
+ InputWorkspace = absName ,
705
+ Material = material ,
706
+ Geometry = geometry ,
707
+ ContainerGeometry = can_geometry ,
708
+ ContainerMaterial = can_material ,
709
+ Environment = environment ,
710
+ FindEnvironment = find_environment ,
615
711
)
616
712
713
+ if beam_height != Property .EMPTY_DBL and not gauge_vol :
714
+ # If the gauge volume is not defined, use the beam height to define it,
715
+ # and we will be assuming a cylinder shape of the sample.
716
+ ref_frame = mtd [absName ].getInstrument ().getReferenceFrame ()
717
+ up_direction = ref_frame .pointingUpAxis ()
718
+ info_to_use = GV_VALS [up_direction ]
719
+ gauge_vol = """<cylinder id="shape">
720
+ <centre-of-bottom-base r="{0:4.2F}" t="{1:s}" p="{2:s}" />
721
+ <axis x="{3:s}" y="{4:s}" z="{5:s}" />
722
+ <radius val="{6:7.5F}" />
723
+ <height val="{7:4.2F}" />
724
+ </cylinder>"""
725
+ if isinstance (geometry ["Radius" ], float ):
726
+ sam_rad = geometry ["Radius" ]
727
+ else :
728
+ sam_rad = geometry ["Radius" ].value
729
+
730
+ # Factor '100' here is for unit conversion from cm to m. An extra factor
731
+ # of '2' in 'beam_height / 200.0' is to make sure the center of the
732
+ # bottom base of the gauge volume is down from the origin by half of the
733
+ # gauge volume height so the center of the gauge volume is at the origin.
734
+ # This is our assumed location of the beam if only beam height is given.
735
+ gauge_vol = gauge_vol .format (
736
+ beam_height / 200.0 ,
737
+ info_to_use ["t" ],
738
+ info_to_use ["p" ],
739
+ info_to_use ["x" ],
740
+ info_to_use ["y" ],
741
+ info_to_use ["z" ],
742
+ sam_rad / 100.0 ,
743
+ beam_height / 100.0 ,
744
+ )
745
+
746
+ if gauge_vol :
747
+ DefineGaugeVolume (absName , gauge_vol )
748
+
617
749
return absName
0 commit comments