/
bruker.py
2709 lines (2232 loc) · 84.3 KB
/
bruker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Functions for reading and writing Bruker binary (ser/fid) files, Bruker
JCAMP-DX parameter (acqus) files, and Bruker pulse program (pulseprogram)
files.
"""
import locale
import io
__developer_info__ = """
Bruker file format information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Bruker binary files (ser/fid) store data as an array of numbers whose
endianness is determined by the parameter BYTORDA (1 = big endian, 0 = little
endian), and whose data type is determined by the parameter DTYPA (0 = int32,
2 = float64). Typically the direct dimension is digitally filtered. The exact
method of removing this filter is unknown but an approximation is available.
Bruker JCAMP-DX files (acqus, etc) are text file which are described by the
`JCAMP-DX standard <http://www.jcamp-dx.org/>`_. Bruker parameters are
prefixed with a '$'.
Bruker pulseprogram files are text files described in various Bruker manuals.
Of special important are lines which describe external variable assignments
(surrounded by "'s), loops (begin with lo), phases (contain ip of dp) or
increments (contain id, dd, ipu or dpu). These lines are parsed when reading
the file with nmrglue.
"""
from functools import reduce
import operator
import os
from warnings import warn
import numpy as np
from . import fileiobase
from ..process import proc_base
# data creation
def create_data(data):
"""
Create a bruker data array (recast into a complex128 or int32)
"""
if np.iscomplexobj(data):
return np.array(data, dtype='complex128')
else:
return np.array(data, dtype='int32')
# universal dictionary functions
def guess_udic(dic, data, strip_fake=False):
"""
Guess parameters of universal dictionary from dic, data pair.
Parameters
----------
dic : dict
Dictionary of Bruker parameters.
data : ndarray
Array of NMR data.
strip_fake: bool
If data is proceed (i.e. read using `bruker.read_pdata`) and the Bruker
processing parameters STSI and/or STSR are set, the returned sweep
width and carrier frequencies is changed to values that are incorrect
but instead can are intended to trick the normal unit_conversion object
into producing the correct result.
Returns
-------
udic : dict
Universal dictionary of spectral parameters.
"""
# TODO if pprog, acqus, procs are in dic use them better.
# create an empty universal dictionary
udic = fileiobase.create_blank_udic(data.ndim)
# update default values
for b_dim in range(data.ndim):
udic[b_dim]["size"] = data.shape[b_dim]
# try to add additional parameter from acqus dictionary keys
try:
add_axis_to_udic(udic, dic, b_dim, strip_fake)
except:
warn("Failed to determine udic parameters for dim: %i" % (b_dim))
return udic
def add_axis_to_udic(udic, dic, udim, strip_fake):
"""
Add axis parameters to a udic.
Parameters
----------
udic : dict
Universal dictionary to update, modified in place.
dic : dict
Bruker dictionary used to determine axes parameters.
dim : int
Universal dictionary dimension to update.
strip_fake: bool
See `bruker.guess_udic`
"""
# This could still use some work
b_dim = udic['ndim'] - udim - 1 # last dim
acq_file = f"acqu{b_dim + 1}s"
pro_file = f"proc{b_dim + 1}s"
# Because they're inconsistent,..
if acq_file == "acqu1s":
acq_file = "acqus"
if pro_file == "proc1s":
pro_file = "procs"
if acq_file in dic:
if b_dim == 0:
sw = dic[acq_file]["SW_h"]
else:
sw = dic[acq_file]["SW"] * dic[acq_file]["SFO1"]
elif pro_file in dic:
sw = dic[pro_file]["SW_p"]
# procNs files store sw (in Hz) with the 'SW_p' key instead of 'SW_h'.
# this is a bug in TopSpin (TopSpin3.5pl7)
if acq_file in dic:
udic[udim]["label"] = dic[acq_file]["NUC1"]
elif pro_file in dic:
udic[udim]["label"] = dic[pro_file]["AXNUC"]
try:
obs = dic[pro_file]["SF"]
if acq_file in dic:
car = (dic[acq_file]["SFO1"] - obs) * 1e6
else:
# we should be able to use the 'OFFSET' parameter in procNs to
# calculate 'car'. But this is slightly off (~ 5E-3 Hz)
# most likely because the procs file does not store the OFFSET
# to a high precision. Hence the value in acquNs is given priority
car = dic[pro_file]["OFFSET"]*obs - sw/2
except KeyError:
warn('The chemical shift referencing was not corrected for "sr".')
obs = dic[acq_file]["SFO1"]
car = dic[acq_file]["O1"]
if strip_fake:
try:
# Temporary parameters
w = sw/float(dic[pro_file]["FTSIZE"])
d = (w * dic[pro_file]["STSR"]) + (w * dic[pro_file]["STSI"]/2.0)
# Fake car frequency
car -= (d-(sw/2.0))
# Fake sw frequency
sw = w * dic[pro_file]["STSI"]
except KeyError:
pass
udic[udim]["sw"] = sw
udic[udim]["car"] = car
udic[udim]["obs"] = obs
if acq_file in dic:
if acq_file == "acqus":
if dic['acqus']['AQ_mod'] == 0: # qf
udic[udim]['complex'] = False
else:
udic[udim]['complex'] = True
else:
aq_mod = dic[acq_file]["FnMODE"]
if aq_mod == 0:
udic[udim]["encoding"] = "undefined"
elif aq_mod == 1:
udic[udim]["encoding"] = "magnitude" # qf
elif aq_mod == 2:
udic[udim]["encoding"] = "magnitude" # qsec
elif aq_mod == 3:
udic[udim]["encoding"] = "tppi"
elif aq_mod == 4:
udic[udim]["encoding"] = "states"
elif aq_mod == 5:
udic[udim]["encoding"] = "states-tppi" # states-tppi
elif aq_mod == 6:
udic[udim]["encoding"] = "echo-antiecho" # echo-antiecho
else:
if pro_file == "procs":
# this seems to have the 'MC2' parameter always set to 0
# irrespective of what the actual data is
udic[udim]["complex"] = "undefined"
else:
# these are only used when params in acquNs are 'undefined'
# but in absence of acqus, this is the best that can be done
aq_mod = dic[pro_file]["MC2"]
if aq_mod == 0:
udic[udim]["encoding"] = "magnitude" # qf
elif aq_mod == 1:
udic[udim]["encoding"] = "magnitude" # qsec
elif aq_mod == 2:
udic[udim]["encoding"] = "tppi"
elif aq_mod == 3:
udic[udim]["encoding"] = "states"
elif aq_mod == 4:
udic[udim]["encoding"] = "states-tppi"
elif aq_mod == 5:
udic[udim]["encoding"] = "echo-antiecho"
return udic
def create_dic(udic):
"""
Create a Bruker parameter dictionary from a universal dictionary.
Parameters
----------
udic : dict
Universal dictionary of spectral parameters.
Returns
-------
dic : dict
Dictionary of Bruker parameters.
"""
ndim = udic['ndim']
# determine the size in bytes
if udic[ndim - 1]["complex"]:
bytes = 8
else:
bytes = 4
for k in range(ndim):
bytes *= udic[k]["size"]
dic = {"FILE_SIZE": bytes}
# create the pprog dictionary parameter
dic["pprog"] = {'incr': [[], [1]] * (ndim * 2 - 2),
'loop': [2] * (ndim * 2 - 2),
'ph_extra': [[]] * (ndim * 2 - 2),
'phase': [[]] * (ndim * 2 - 2),
'var': {}}
# create acqus dictionary parameters and fill in loop sizes
dic['acqus'] = create_acqus_dic(udic[ndim - 1], direct=True)
if ndim >= 2:
dic["acqu2s"] = create_acqus_dic(udic[ndim - 2])
dic["pprog"]["loop"][1] = udic[ndim - 2]["size"] // 2
if ndim >= 3:
dic["acqu3s"] = create_acqus_dic(udic[ndim - 3])
dic["pprog"]["loop"][3] = udic[ndim - 3]["size"] // 2
if ndim >= 4:
dic["acqu4s"] = create_acqus_dic(udic[ndim - 4])
dic["pprog"]["loop"][5] = udic[ndim - 4]["size"] // 2
return dic
def create_acqus_dic(adic, direct=False):
"""
Create a Bruker acqus dictionary from an Universal axis dictionary.
Set direct=True for direct dimension.
"""
if adic["complex"]:
AQ_mod = 3
if direct:
TD = int(np.ceil(adic["size"] / 256.) * 256) * 2
else:
TD = adic["size"]
else:
AQ_mod = 1
if direct:
TD = int(np.ceil(adic["size"] / 256.) * 256)
else:
TD = adic["size"]
s = '##NMRGLUE automatically created parameter file'
return {'_comments': [], '_coreheader': [s], 'AQ_mod': AQ_mod, 'TD': TD}
# Global read/write function and related utilities
def read(dir=".", bin_file=None, acqus_files=None, pprog_file=None, shape=None,
cplex=None, big=None, isfloat=None, read_pulseprogram=True,
read_acqus=True, procs_files=None, read_procs=True):
"""
Read Bruker files from a directory.
Parameters
----------
dir : str
Directory to read from.
bin_file : str, optional
Filename of binary file in directory. None uses standard files.
acqus_files : list, optional
List of filename(s) of acqus parameter files in directory. None uses
standard files.
pprog_file : str, optional
Filename of pulse program in directory. None uses standard files.
shape : tuple, optional
Shape of resulting data. None will guess the shape from the spectral
parameters.
cplex : bool, optional
True is direct dimension is complex, False otherwise. None will guess
quadrature from spectral parameters.
big : bool or None, optional
Endianness of binary file. True for big-endian, False for
little-endian, None to determine endianness from acqus file(s).
isfloat : bool or None, optional
Data type of binary file. True for float64, False for int32. None to
determine data type from acqus file(s).
read_pulseprogram : bool, optional
True to read pulse program, False prevents reading.
read_acqus : bool, optional
True to read acqus files(s), False prevents reading.
procs_files : list, optional
List of filename(s) of procs parameter files in directory. None uses
standard files.
read_procs : bool, optional
True to read procs files(s), False prevents reading.
Returns
-------
dic : dict
Dictionary of Bruker parameters.
data : ndarray
Array of NMR data.
See Also
--------
read_pdata : Read Bruker processed files.
read_lowmem : Low memory reading of Bruker files.
write : Write Bruker files.
"""
if os.path.isdir(dir) is not True:
raise OSError("directory %s does not exist" % (dir))
# Take a shot at reading the procs file
if read_procs:
dic = read_procs_file(dir, procs_files)
else:
# create an empty dictionary
dic = dict()
# determine parameter automatically
if bin_file is None:
if os.path.isfile(os.path.join(dir, "fid")):
bin_file = "fid"
elif os.path.isfile(os.path.join(dir, "ser")):
bin_file = "ser"
# Look two directory levels lower.
elif os.path.isdir(os.path.dirname(os.path.dirname(dir))):
# ! change the dir
dir = os.path.dirname(os.path.dirname(dir))
if os.path.isfile(os.path.join(dir, "fid")):
bin_file = "fid"
elif os.path.isfile(os.path.join(dir, "ser")):
bin_file = "ser"
else:
mesg = "No Bruker binary file could be found in %s"
raise OSError(mesg % (dir))
else:
mesg = "No Bruker binary file could be found in %s"
raise OSError(mesg % (dir))
if read_acqus:
# read the acqus_files and add to the dictionary
acqus_dic = read_acqus_file(dir, acqus_files)
dic = _merge_dict(dic, acqus_dic)
if pprog_file is None:
pprog_file = "pulseprogram"
# read the pulse program and add to the dictionary
if read_pulseprogram:
try:
dic["pprog"] = read_pprog(os.path.join(dir, pprog_file))
except:
warn('Error reading the pulse program')
# determine file size and add to the dictionary
dic["FILE_SIZE"] = os.stat(os.path.join(dir, bin_file)).st_size
# determine shape and complexity for direct dim if needed
if shape is None or cplex is None:
gshape, gcplex = guess_shape(dic)
if gcplex is True: # divide last dim by 2 if complex
t = list(gshape)
t[-1] = t[-1] // 2
gshape = tuple(t)
if shape is None:
shape = gshape
if cplex is None:
cplex = gcplex
# determine endianness (assume little-endian unless BYTORDA is 1)
if big is None:
big = False # default value
if "acqus" in dic and "BYTORDA" in dic["acqus"]:
if dic["acqus"]["BYTORDA"] == 1:
big = True
else:
big = False
# determine data type (assume int32 unless DTYPA is 2)
if isfloat is None:
isfloat = False # default value
if "acqus" in dic and "DTYPA" in dic["acqus"]:
if dic["acqus"]["DTYPA"] == 2:
isfloat = True
else:
isfloat = False
# read the binary file
f = os.path.join(dir, bin_file)
_, data = read_binary(f, shape=shape, cplex=cplex, big=big,
isfloat=isfloat)
try:
if dic['acqus']['FnTYPE'] == 2: # non-uniformly sampled data
try:
dic['nuslist'] = read_nuslist(dir)
except FileNotFoundError:
warn("NUS data detected, but nuslist was not found")
except KeyError:
# old datasets do not have the FnTYPE parameter in acqus files.
# also fails silently when acqus file is absent.
pass
return dic, data
def read_lowmem(dir=".", bin_file=None, acqus_files=None, pprog_file=None,
shape=None, cplex=None, big=None, isfloat=None,
read_pulseprogram=True, read_acqus=True, procs_files=None,
read_procs=True):
"""
Read Bruker files from a directory using minimal amounts of memory.
See :py:func:`read` for Parameters.
Returns
-------
dic : dict
Dictionary of Bruker parameters.
data : array_like
Low memory object which can access NMR data on demand.
See Also
--------
read : Read Bruker files.
write_lowmem : Write Bruker files using minimal amounts of memory.
"""
if os.path.isdir(dir) is not True:
raise OSError("directory %s does not exist" % (dir))
# Take a shot at reading the procs file
if read_procs:
dic = read_procs_file(dir, procs_files)
else:
# create an empty dictionary
dic = dict()
# determine parameter automatically
if bin_file is None:
if os.path.isfile(os.path.join(dir, "fid")):
bin_file = "fid"
elif os.path.isfile(os.path.join(dir, "ser")):
bin_file = "ser"
# Look two directory levels lower.
elif os.path.isdir(os.path.dirname(os.path.dirname(dir))):
# ! change the dir
dir = os.path.dirname(os.path.dirname(dir))
if os.path.isfile(os.path.join(dir, "fid")):
bin_file = "fid"
elif os.path.isfile(os.path.join(dir, "ser")):
bin_file = "ser"
else:
mesg = "No Bruker binary file could be found in %s"
raise OSError(mesg % (dir))
else:
mesg = "No Bruker binary file could be found in %s"
raise OSError(mesg % (dir))
if read_acqus:
# read the acqus_files and add to the dictionary
acqus_dic = read_acqus_file(dir, acqus_files)
dic = _merge_dict(dic, acqus_dic)
if pprog_file is None:
pprog_file = "pulseprogram"
# read the pulse program and add to the dictionary
if read_pulseprogram:
dic["pprog"] = read_pprog(os.path.join(dir, pprog_file))
# determine file size and add to the dictionary
dic["FILE_SIZE"] = os.stat(os.path.join(dir, bin_file)).st_size
# determine shape and complexity for direct dim if needed
if shape is None or cplex is None:
gshape, gcplex = guess_shape(dic)
if gcplex is True: # divide last dim by 2 if complex
t = list(gshape)
t[-1] = t[-1] // 2
gshape = tuple(t)
if shape is None:
shape = gshape
if cplex is None:
cplex = gcplex
# determine endianness (assume little-endian unless BYTORDA is 1)
if big is None:
big = False # default value
if "acqus" in dic and "BYTORDA" in dic["acqus"]:
if dic["acqus"]["BYTORDA"] == 1:
big = True
else:
big = False
# determine data type (assume int32 unless DTYPA is 2)
if isfloat is None:
isfloat = False # default value
if "acqus" in dic and "DTYPA" in dic["acqus"]:
if dic["acqus"]["DTYPA"] == 2:
isfloat = True
else:
isfloat = False
# read the binary file
f = os.path.join(dir, bin_file)
_, data = read_binary_lowmem(f, shape=shape, cplex=cplex, big=big,
isfloat=isfloat)
try:
if dic['acqus']['FnTYPE'] == 2: # non-uniformly sampled data
try:
dic['nuslist'] = read_nuslist(dir)
except FileNotFoundError:
warn("NUS data detected, but nuslist was not found")
except KeyError:
# old datasets do not have the FnTYPE parameter in acqus files.
# also fails silently when acqus file is absent.
pass
return dic, data
def read_acqus_file(dir='.', acqus_files=None):
"""
Read Bruker acquisition files from a directory.
Parameters
----------
dir : str
Directory to read from.
acqus_files : list, optional
List of filename(s) of acqus parameter files in directory. None uses
standard files. If filename(s) contains a full absolute path, dir is not used.
Returns
-------
dic : dict
Dictionary of Bruker parameters.
"""
if acqus_files is None:
acqus_files = []
for f in ["acqus", "acqu2s", "acqu3s", "acqu4s"]:
fp = os.path.join(dir, f)
if os.path.isfile(fp):
acqus_files.append(fp)
# create an empty dictionary
dic = dict()
# read the acqus_files and add to the dictionary
for f in acqus_files:
if not os.path.isfile(f):
f = os.path.join(dir, f)
acqu = os.path.basename(f)
dic[acqu] = read_jcamp(f)
return dic
def read_procs_file(dir='.', procs_files=None):
"""
Read Bruker processing files from a directory.
Parameters
----------
dir : str
Directory to read from.
procs_files : list, optional
List of filename(s) of procs parameter files in directory. None uses
standard files. If filename(s) contains a full absolute path, dir is not used.
Returns
-------
dic : dict
Dictionary of Bruker parameters.
"""
if procs_files is None:
# Reading standard procs files
procs_files = []
pdata_path = dir
for f in ["procs", "proc2s", "proc3s", "proc4s"]:
pf = os.path.join(pdata_path, f)
if os.path.isfile(pf):
procs_files.append(pf)
if not procs_files:
# procs not found in the given dir, try look adding pdata to the dir path
if os.path.isdir(os.path.join(dir, 'pdata')):
pdata_folders = [folder for folder in
os.walk(os.path.join(dir, 'pdata'))][0][1]
if '1' in pdata_folders:
pdata_path = os.path.join(dir, 'pdata', '1')
else:
pdata_path = os.path.join(dir, 'pdata', pdata_folders[0])
for f in ["procs", "proc2s", "proc3s", "proc4s"]:
pf = os.path.join(pdata_path, f)
if os.path.isfile(pf):
procs_files.append(pf)
else:
# proc paths were explicitly given
# just check if they exists
for i, f in enumerate(procs_files):
pdata_path, f = os.path.split(f)
if not pdata_path:
pdata_path = dir
pf = os.path.join(pdata_path, f)
if not os.path.isfile(pf):
mesg = "The file `%s` could not be found "
warn(mesg % pf)
else:
procs_files[i] = pf
# create an empty dictionary
dic = dict()
# read the acqus_files and add to the dictionary
for f in procs_files:
pdata_path = os.path.basename(f)
dic[pdata_path] = read_jcamp(f)
return dic
def write(dir, dic, data, bin_file=None, acqus_files=None, procs_files=None,
pprog_file=None, overwrite=False, big=None, isfloat=None,
write_prog=True, write_acqus=True, write_procs=False,
pdata_folder=False):
"""
Write Bruker files to disk.
Parameters
----------
dir : str
Directory to write files to.
dir : dict
Dictionary of Bruker parameters.
data : array_like
Array of NMR data
bin_file : str, optional
Filename of binary file in directory. None uses standard files.
acqus_files : list, optional
List of filename(s) of acqus parameter files in directory. None uses
standard files.
procs_file : list, optional
List of filename(s) of procs parameter files (to write out). None uses a
list of standard files
pprog_file : str, optional
Filename of pulse program in directory. None uses standard files.
overwrite : bool, optional
Set True to overwrite files, False will raise a Warning if files
exist.
big : bool or None, optional
Endianness of binary file. True for big-endian, False for
little-endian, None to determine endianness from Bruker dictionary.
isfloat : bool or None, optional
Data type of binary file. True for float64, False for int32. None to
determine data type from Bruker dictionary.
write_pprog : bool, optional
True to write the pulse program file, False prevents writing.
write_acqus : bool, optional
True to write the acqus files(s), False prevents writing.
write_procs : bool, optional
True to write the procs files(s), False prevents writing.
pdata_folder : int, optional
Makes a folder and a subfolder ('pdata/pdata_folder') inside the given
directory where pdata_folder is an integer. procN and procNs files are
stored inside pdata_folder. pdata_folder=False (or =0) does not make the
pdata folder and pdata_folder=True makes folder '1'.
See Also
--------
write_lowmem : Write Bruker files using minimal amounts of memory.
read : Read Bruker files.
"""
# determine parameters automatically
if bin_file is None:
if data.ndim == 1:
bin_file = "fid"
else:
bin_file = "ser"
if acqus_files is None:
acq = ["acqus", "acqu2s", "acqu3s", "acqu4s"]
acqus_files = [k for k in acq if (k in dic)]
acqu_files = [k[:-1] for k in acqus_files]
if procs_files is None:
proc = ["procs", "proc2s", "proc3s", "proc4s"]
procs_files = [k for k in proc if (k in dic)]
proc_files = [k[:-1] for k in procs_files]
if pprog_file is None:
pprog_file = "pulseprogram"
# write out the acqus files
if write_acqus:
for f in acqus_files:
write_jcamp(dic[f], os.path.join(dir, f), overwrite=overwrite)
for f in acqu_files:
write_jcamp(dic[f+'s'], os.path.join(dir, f), overwrite=overwrite)
# write out the procs files
if write_procs:
if pdata_folder is not False:
try:
procno = str(int(pdata_folder))
pdata_path = os.path.join(dir, 'pdata', procno)
except ValueError:
raise ValueError('pdata_folder should be an integer')
if not os.path.isdir(pdata_path):
os.makedirs(pdata_path)
else:
pdata_path = dir
for f in procs_files:
write_jcamp(dic[f], os.path.join(pdata_path, f))
for f in proc_files:
write_jcamp(dic[f+'s'], os.path.join(pdata_path, f))
# write out the pulse program
if write_prog:
write_pprog(os.path.join(dir, pprog_file), dic["pprog"],
overwrite=overwrite)
# determine endianness (assume little-endian unless BYTORDA is 1)
if big is None:
big = False # default value
if "acqus" in dic and "BYTORDA" in dic["acqus"]:
if dic["acqus"]["BYTORDA"] == 1:
big = True
else:
big = False
# determine data type (assume int32 unless DTYPA is 2)
if isfloat is None:
isfloat = False # default value
if "acqus" in dic and "DTYPA" in dic["acqus"]:
if dic["acqus"]["DTYPA"] == 2:
isfloat = True
else:
isfloat = False
# write out the binary data
bin_full = os.path.join(dir, bin_file)
write_binary(bin_full, dic, data, big=big, isfloat=isfloat,
overwrite=overwrite)
def write_lowmem(dir, dic, data, bin_file=None, acqus_files=None,
pprog_file=None, overwrite=False, big=None, isfloat=None,
write_prog=True, write_acqus=True):
"""
Write Bruker files using minimal amounts of memory (trace by trace).
See :py:func:`write` for Parameters.
See Also
--------
write : Write Bruker files.
read_lowmem : Read Bruker files using minimal amounts of memory.
"""
# determine parameters automatically
if bin_file is None:
if data.ndim == 1:
bin_file = "fid"
else:
bin_file = "ser"
if acqus_files is None:
acq = ["acqus", "acqu2s", "acqu3s", "acqu4s"]
acqus_files = [k for k in acq if (k in dic)]
if pprog_file is None:
pprog_file = "pulseprogram"
# write out the acqus files
if write_acqus:
for f in acqus_files:
write_jcamp(dic[f], os.path.join(dir, f), overwrite=overwrite)
# write out the pulse program
if write_prog:
write_pprog(os.path.join(dir, pprog_file), dic["pprog"],
overwrite=overwrite)
# determine endianness (assume little-endian unless BYTORDA is 1)
if big is None:
big = False # default value
if "acqus" in dic and "BYTORDA" in dic["acqus"]:
if dic["acqus"]["BYTORDA"] == 1:
big = True
else:
big = False
# determine data type (assume int32 unless DTYPA is 2)
if isfloat is None:
isfloat = False # default value
if "acqus" in dic and "DTYPA" in dic["acqus"]:
if dic["acqus"]["DTYPA"] == 2:
isfloat = True
else:
isfloat = False
# write out the binary data
bin_full = os.path.join(dir, bin_file)
write_binary_lowmem(bin_full, dic, data, big=big, isfloat=isfloat,
overwrite=overwrite)
def write_pdata(dir, dic, data, roll=False, shape=None, submatrix_shape=None,
scale_data=False, bin_file=None, procs_files=None,
write_procs=False, pdata_folder=False, overwrite=False,
big=None, isfloat=None, restrict_access=True):
"""
Write processed Bruker files to disk.
Parameters
----------
dir : str
Directory to write files to.
dic : dict
Dictionary of Bruker parameters.
data : array_like
Array of NMR data
roll : int
Number of points by which a circular shift needs to be applied to the data
True will apply a circular shift of 1 data point
shape : tuple, optional
Shape of data, if file is to be written with a shape
different than data.shape
submatrix_shape : tuple, optional
Shape of the submatrix used to store data (using Bruker specifications)
If this is not given, the submatrix shape will be guessed from dic
scale_data : Bool
Apply a reverse scaling using the scaling factor defined in procs file
By default, the array to be written will not be scaled using the value
in procs but will be e scaled so that the max intensity in that array
will have a value between 2**28 and 2**29. scale_data is to be used when
the array is itself a processed bruker file that was read into nmrglue
bin_file : str, optional
Filename of binary file in directory. None uses standard files.
procs_file : list, optional
List of filename(s) of procs parameter files (to write out). None uses a
list of standard files
write_procs : Bool
True to write out the procs files
pdata_folder : int, optional
Makes a folder and a subfolder ('pdata/pdata_folder') inside the given
directory where pdata_folder is an integer. All files (procs and data) are
stored inside pdata_folder. pdata_folder=False (or =0) does not make the
pdata folder and pdata_folder=True makes folder '1'.
overwrite : bool, optional
Set True to overwrite files, False will raise a Warning if files
exist.
big : bool or None, optional
Endianness of binary file. True for big-endian, False for
little-endian, None to determine endianness from Bruker dictionary.
isfloat : bool or None, optional
Data type of binary file. True for float64, False for int32. None to
determine data type from Bruker dictionary.
restrict_access : not implemented
"""
# see that data consists of only real elements
data = np.roll(data.real, int(roll))
# either apply a reverse scaling to the data or scale processed data
# so that the max value is between 2**28 and 2**29 and cast to integers
if scale_data:
data = scale_pdata(dic, data, reverse=True)
else:
data = array_to_int(data)
# see if the dimensionality is given
# else, set it to the dimensions of data
if shape is None:
shape = data.shape
# guess data dimensionality
ndim = len(shape)
# update PARMODE in dictionary
# This is required when writing back 1D slices from a 2D, 2D planes of 3D, etc
dic['procs']['PPARMOD'] = ndim - 1
# reorder the submatrix according
if submatrix_shape is None:
submatrix_shape = guess_shape_and_submatrix_shape(dic)[1]
data = reorder_submatrix(data, shape, submatrix_shape, reverse=True)
# see if pdata_folder needs to make and set write path
if pdata_folder is not False:
try:
procno = str(int(pdata_folder))
pdata_path = os.path.join(dir, 'pdata', procno)
except ValueError:
raise ValueError('pdata_folder should be an integer')
if not os.path.isdir(pdata_path):
os.makedirs(pdata_path)
else:
pdata_path = dir
# write out the procs files only for the desired dimensions
if write_procs:
if procs_files is None:
proc = ['procs'] + [f'proc{i}s' for i in range(2, ndim+1)]
procs_files = [f for f in proc if (f in dic)]
for f in procs_files:
write_jcamp(dic[f], os.path.join(pdata_path, f),
overwrite=overwrite)
write_jcamp(dic[f], os.path.join(pdata_path, f[:-1]),
overwrite=overwrite)
if bin_file is None:
bin_file = str(ndim) + 'r'*ndim
bin_full = os.path.join(pdata_path, bin_file)
write_binary(bin_full, dic, data, big=big, isfloat=isfloat,
overwrite=overwrite)
def guess_shape(dic):
"""
Determine data shape and complexity from Bruker dictionary.
Returns
-------
shape : tuple
Shape of data in Bruker binary file (R+I for all dimensions).
cplex : bool
True for complex data in last (direct) dimension, False otherwise.
"""
# determine complexity of last (direct) dimension
try:
aq_mod = dic["acqus"]["AQ_mod"]
except KeyError:
aq_mod = 0
if aq_mod in (0, 2):