pcntoolkit 0.32.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pcntoolkit/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from . import trendsurf
2
+ from . import normative
3
+ from . import normative_parallel
4
+ from . import normative_NP
pcntoolkit/configs.py ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Mon Dec 7 12:51:07 2020
5
+
6
+ @author: seykia
7
+ """
8
+
9
+ PICKLE_PROTOCOL = 4
@@ -0,0 +1 @@
1
+ from . import fileio
@@ -0,0 +1,608 @@
1
+ from __future__ import print_function
2
+
3
+ import os
4
+ import sys
5
+ import numpy as np
6
+ import nibabel as nib
7
+ import tempfile
8
+ import pandas as pd
9
+ import re
10
+
11
+ try: # run as a package if installed
12
+ from pcntoolkit import configs
13
+ except ImportError:
14
+ pass
15
+
16
+ path = os.path.abspath(os.path.dirname(__file__))
17
+ path = os.path.dirname(path) # parent directory
18
+ if path not in sys.path:
19
+ sys.path.append(path)
20
+ del path
21
+ import configs
22
+
23
+ CIFTI_MAPPINGS = ('dconn', 'dtseries', 'pconn', 'ptseries', 'dscalar',
24
+ 'dlabel', 'pscalar', 'pdconn', 'dpconn',
25
+ 'pconnseries', 'pconnscalar')
26
+
27
+ CIFTI_VOL_ATLAS = 'Atlas_ROIs.2.nii.gz'
28
+
29
+ PICKLE_PROTOCOL = configs.PICKLE_PROTOCOL
30
+
31
+ # ------------------------
32
+ # general utility routines
33
+ # ------------------------
34
+
35
+
36
+ def predictive_interval(s2_forward,
37
+ cov_forward,
38
+ multiplicator):
39
+ """
40
+ Calculates a predictive interval for the forward model
41
+ """
42
+ # calculates a predictive interval
43
+
44
+ PI = np.zeros(len(cov_forward))
45
+ for i, xdot in enumerate(cov_forward):
46
+ s = np.sqrt(s2_forward[i])
47
+ PI[i] = multiplicator*s
48
+ return PI
49
+
50
+
51
+ def create_mask(data_array, mask, verbose=False):
52
+ """
53
+ Create a mask from a data array or a nifti file
54
+
55
+ Basic usage::
56
+
57
+ create_mask(data_array, mask, verbose)
58
+
59
+ :param data_array: numpy array containing the data to write out
60
+ :param mask: nifti image containing a mask for the image
61
+ :param verbose: verbose output
62
+ """
63
+
64
+ # create a (volumetric) mask either from an input nifti or the nifti itself
65
+
66
+ if mask is not None:
67
+ if verbose:
68
+ print('Loading ROI mask ...')
69
+ maskvol = load_nifti(mask, vol=True)
70
+ maskvol = maskvol != 0
71
+ else:
72
+ if len(data_array.shape) < 4:
73
+ dim = data_array.shape[0:3] + (1,)
74
+ else:
75
+ dim = data_array.shape[0:3] + (data_array.shape[3],)
76
+
77
+ if verbose:
78
+ print('Generating mask automatically ...')
79
+ if dim[3] == 1:
80
+ maskvol = data_array[:, :, :] != 0
81
+ else:
82
+ maskvol = data_array[:, :, :, 0] != 0
83
+
84
+ return maskvol
85
+
86
+
87
+ def vol2vec(dat, mask, verbose=False):
88
+ """
89
+ Vectorise a 3d image
90
+
91
+ Basic usage::
92
+
93
+ vol2vec(dat, mask, verbose)
94
+
95
+ :param dat: numpy array containing the data to write out
96
+ :param mask: nifti image containing a mask for the image
97
+ :param verbose: verbose output
98
+ """
99
+ # vectorise a 3d image
100
+
101
+ if len(dat.shape) < 4:
102
+ dim = dat.shape[0:3] + (1,)
103
+ else:
104
+ dim = dat.shape[0:3] + (dat.shape[3],)
105
+
106
+ # mask = create_mask(dat, mask=mask, verbose=verbose)
107
+ if mask is None:
108
+ mask = create_mask(dat, mask=mask, verbose=verbose)
109
+
110
+ # mask the image
111
+ maskid = np.where(mask.ravel())[0]
112
+ dat = np.reshape(dat, (np.prod(dim[0:3]), dim[3]))
113
+ dat = dat[maskid, :]
114
+
115
+ # convert to 1-d array if the file only contains one volume
116
+ if dim[3] == 1:
117
+ dat = dat.ravel()
118
+
119
+ return dat
120
+
121
+
122
+ def file_type(filename):
123
+ """
124
+ Determine the file type of a file
125
+
126
+ Basic usage::
127
+
128
+ file_type(filename)
129
+
130
+ :param filename: name of the file to check
131
+ """
132
+ # routine to determine filetype
133
+
134
+ if filename.endswith(('.dtseries.nii', '.dscalar.nii', '.dlabel.nii')):
135
+ ftype = 'cifti'
136
+ elif filename.endswith(('.nii.gz', '.nii', '.img', '.hdr')):
137
+ ftype = 'nifti'
138
+ elif filename.endswith(('.txt', '.csv', '.tsv', '.asc')):
139
+ ftype = 'text'
140
+ elif filename.endswith(('.pkl')):
141
+ ftype = 'binary'
142
+ else:
143
+ raise ValueError("I don't know what to do with " + filename)
144
+
145
+ return ftype
146
+
147
+
148
+ def file_extension(filename):
149
+ """
150
+ Determine the file extension of a file (e.g. .nii.gz)
151
+
152
+ Basic usage::
153
+
154
+ file_extension(filename)
155
+
156
+ :param filename: name of the file to check
157
+ """
158
+
159
+ # routine to get the full file extension (e.g. .nii.gz, not just .gz)
160
+
161
+ parts = filename.split(os.extsep)
162
+
163
+ if parts[-1] == 'gz':
164
+ if parts[-2] == 'nii' or parts[-2] == 'img' or parts[-2] == 'hdr':
165
+ ext = parts[-2] + '.' + parts[-1]
166
+ else:
167
+ ext = parts[-1]
168
+ elif parts[-1] == 'nii':
169
+ if parts[-2] in CIFTI_MAPPINGS:
170
+ ext = parts[-2] + '.' + parts[-1]
171
+ else:
172
+ ext = parts[-1]
173
+ else:
174
+ ext = parts[-1]
175
+
176
+ ext = '.' + ext
177
+ return ext
178
+
179
+
180
+ def file_stem(filename):
181
+ """
182
+ Determine the file stem of a file (e.g. /path/to/file.nii.gz -> file)
183
+
184
+ Basic usage::
185
+
186
+ file_stem(filename)
187
+
188
+ :param filename: name of the file to check
189
+ """
190
+ idx = filename.find(file_extension(filename))
191
+ stm = filename[0:idx]
192
+
193
+ return stm
194
+
195
+ # --------------
196
+ # nifti routines
197
+ # --------------
198
+
199
+
200
+ def load_nifti(datafile, mask=None, vol=False, verbose=False):
201
+ """
202
+ Load a nifti file into a numpy array
203
+
204
+ Basic usage::
205
+
206
+ load_nifti(datafile, mask, vol, verbose)
207
+
208
+ :param datafile: name of the file to load
209
+ :param mask: nifti image containing a mask for the image
210
+ :param vol: whether to load the image as a volume
211
+ :param verbose: verbose output
212
+ """
213
+
214
+ if verbose:
215
+ print('Loading nifti: ' + datafile + ' ...')
216
+ img = nib.load(datafile)
217
+ dat = img.get_data()
218
+
219
+ if mask is not None:
220
+ mask = load_nifti(mask, vol=True)
221
+
222
+ if not vol:
223
+ dat = vol2vec(dat, mask)
224
+
225
+ return dat
226
+
227
+
228
+ def save_nifti(data, filename, examplenii, mask, dtype=None):
229
+ '''
230
+ Write output to nifti
231
+
232
+ Basic usage::
233
+
234
+ save_nifti(data, filename mask, dtype)
235
+
236
+ :param data: numpy array containing the data to write out
237
+ :param filename: where to store it
238
+ :param examplenii: nifti to copy the geometry and data type from
239
+ :mask: nifti image containing a mask for the image
240
+ :param dtype: data type for the output image (if different from the image)
241
+ '''
242
+
243
+ # load mask
244
+ if isinstance(mask, str):
245
+ mask = load_nifti(mask, vol=True)
246
+ mask = mask != 0
247
+
248
+ # load example image
249
+ ex_img = nib.load(examplenii)
250
+ ex_img.shape
251
+ dim = ex_img.shape[0:3]
252
+ if len(data.shape) < 2:
253
+ nvol = 1
254
+ data = data[:, np.newaxis]
255
+ else:
256
+ nvol = int(data.shape[1])
257
+
258
+ # write data
259
+ array_data = np.zeros((np.prod(dim), nvol))
260
+ array_data[mask.flatten(), :] = data
261
+ array_data = np.reshape(array_data, dim+(nvol,))
262
+ hdr = ex_img.header
263
+ if dtype is not None:
264
+ hdr.set_data_dtype(dtype)
265
+ array_data = array_data.astype(dtype)
266
+ array_img = nib.Nifti1Image(array_data, ex_img.affine, hdr)
267
+
268
+ nib.save(array_img, filename)
269
+
270
+ # --------------
271
+ # cifti routines
272
+ # --------------
273
+
274
+
275
+ def load_cifti(filename, vol=False, mask=None, rmtmp=True):
276
+ """
277
+ Load a cifti file into a numpy array
278
+
279
+ Basic usage::
280
+
281
+ load_cifti(filename, vol, mask, rmtmp)
282
+
283
+ :param filename: name of the file to load
284
+ :param vol: whether to load the image as a volume
285
+ :param mask: nifti image containing a mask for the image
286
+ :param rmtmp: whether to remove temporary files
287
+ """
288
+ # parse the name
289
+ dnam, fnam = os.path.split(filename)
290
+ fpref = file_stem(fnam)
291
+ outstem = os.path.join(tempfile.gettempdir(),
292
+ str(os.getpid()) + "-" + fpref)
293
+
294
+ # extract surface data from the cifti file
295
+ print("Extracting cifti surface data to ", outstem, '-*.func.gii', sep="")
296
+ giinamel = outstem + '-left.func.gii'
297
+ giinamer = outstem + '-right.func.gii'
298
+ os.system('wb_command -cifti-separate ' + filename +
299
+ ' COLUMN -metric CORTEX_LEFT ' + giinamel)
300
+ os.system('wb_command -cifti-separate ' + filename +
301
+ ' COLUMN -metric CORTEX_RIGHT ' + giinamer)
302
+
303
+ # load the surface data
304
+ giil = nib.load(giinamel)
305
+ giir = nib.load(giinamer)
306
+ Nimg = len(giil.darrays)
307
+ Nvert = len(giil.darrays[0].data)
308
+ if Nimg == 1:
309
+ out = np.concatenate((giil.darrays[0].data, giir.darrays[0].data),
310
+ axis=0)
311
+ else:
312
+ Gl = np.zeros((Nvert, Nimg))
313
+ Gr = np.zeros((Nvert, Nimg))
314
+ for i in range(0, Nimg):
315
+ Gl[:, i] = giil.darrays[i].data
316
+ Gr[:, i] = giir.darrays[i].data
317
+ out = np.concatenate((Gl, Gr), axis=0)
318
+ if rmtmp:
319
+ # clean up temporary files
320
+ os.remove(giinamel)
321
+ os.remove(giinamer)
322
+
323
+ if vol:
324
+ niiname = outstem + '-vol.nii'
325
+ print("Extracting cifti volume data to ", niiname, sep="")
326
+ os.system('wb_command -cifti-separate ' + filename +
327
+ ' COLUMN -volume-all ' + niiname)
328
+ vol = load_nifti(niiname, vol=True)
329
+ volmask = create_mask(vol)
330
+ out = np.concatenate((out, vol2vec(vol, volmask)), axis=0)
331
+ if rmtmp:
332
+ os.remove(niiname)
333
+
334
+ return out
335
+
336
+
337
+ def save_cifti(data, filename, example, mask=None, vol=True, volatlas=None):
338
+ """
339
+ Save a cifti file from a numpy array
340
+
341
+ Basic usage::
342
+
343
+ save_cifti(data, filename, example, mask, vol, volatlas)
344
+
345
+ :param data: numpy array containing the data to write out
346
+ :param filename: where to store it
347
+ :param example: example file to copy the geometry from
348
+ :param mask: nifti image containing a mask for the image
349
+ :param vol: whether to load the image as a volume
350
+ :param volatlas: atlas to use for the volume
351
+ """
352
+
353
+ # do some sanity checks
354
+ if data.dtype == 'float32' or \
355
+ data.dtype == 'float' or \
356
+ data.dtype == 'float64':
357
+ data = data.astype('float32') # force 32 bit output
358
+ dtype = 'NIFTI_TYPE_FLOAT32'
359
+ else:
360
+ raise ValueError('Only float data types currently handled')
361
+
362
+ if len(data.shape) == 1:
363
+ Nimg = 1
364
+ data = data[:, np.newaxis]
365
+ else:
366
+ Nimg = data.shape[1]
367
+
368
+ # get the base filename
369
+ dnam, fnam = os.path.split(filename)
370
+ fstem = file_stem(fnam)
371
+
372
+ # Split the template
373
+ estem = os.path.join(tempfile.gettempdir(), str(os.getpid()) + "-" + fstem)
374
+ giiexnamel = estem + '-left.func.gii'
375
+ giiexnamer = estem + '-right.func.gii'
376
+ os.system('wb_command -cifti-separate ' + example +
377
+ ' COLUMN -metric CORTEX_LEFT ' + giiexnamel)
378
+ os.system('wb_command -cifti-separate ' + example +
379
+ ' COLUMN -metric CORTEX_RIGHT ' + giiexnamer)
380
+
381
+ # write left hemisphere
382
+ giiexl = nib.load(giiexnamel)
383
+ Nvertl = len(giiexl.darrays[0].data)
384
+ garraysl = []
385
+ for i in range(0, Nimg):
386
+ garraysl.append(
387
+ nib.gifti.gifti.GiftiDataArray(data=data[0:Nvertl, i],
388
+ datatype=dtype))
389
+ giil = nib.gifti.gifti.GiftiImage(darrays=garraysl)
390
+ fnamel = fstem + '-left.func.gii'
391
+ nib.save(giil, fnamel)
392
+
393
+ # write right hemisphere
394
+ giiexr = nib.load(giiexnamer)
395
+ Nvertr = len(giiexr.darrays[0].data)
396
+ garraysr = []
397
+ for i in range(0, Nimg):
398
+ garraysr.append(
399
+ nib.gifti.gifti.GiftiDataArray(data=data[Nvertl:Nvertl+Nvertr, i],
400
+ datatype=dtype))
401
+ giir = nib.gifti.gifti.GiftiImage(darrays=garraysr)
402
+ fnamer = fstem + '-right.func.gii'
403
+ nib.save(giir, fnamer)
404
+
405
+ tmpfiles = [fnamer, fnamel, giiexnamel, giiexnamer]
406
+
407
+ # process volumetric data
408
+ if vol:
409
+ niiexname = estem + '-vol.nii'
410
+ os.system('wb_command -cifti-separate ' + example +
411
+ ' COLUMN -volume-all ' + niiexname)
412
+ niivol = load_nifti(niiexname, vol=True)
413
+ if mask is None:
414
+ mask = create_mask(niivol)
415
+
416
+ if volatlas is None:
417
+ volatlas = CIFTI_VOL_ATLAS
418
+ fnamev = fstem + '-vol.nii'
419
+
420
+ save_nifti(data[Nvertr+Nvertl:, :], fnamev, niiexname, mask)
421
+ tmpfiles.extend([fnamev, niiexname])
422
+
423
+ # write cifti
424
+ fname = fstem + '.dtseries.nii'
425
+ os.system('wb_command -cifti-create-dense-timeseries ' + fname +
426
+ ' -volume ' + fnamev + ' ' + volatlas +
427
+ ' -left-metric ' + fnamel + ' -right-metric ' + fnamer)
428
+
429
+ # clean up
430
+ for f in tmpfiles:
431
+ os.remove(f)
432
+
433
+ # --------------
434
+ # ascii routines
435
+ # --------------
436
+
437
+
438
+ def load_pd(filename):
439
+ """
440
+ Load a csv file into a pandas dataframe
441
+
442
+ Basic usage::
443
+
444
+ load_pd(filename)
445
+
446
+ :param filename: name of the file to load
447
+ """
448
+
449
+ # based on pandas
450
+ x = pd.read_csv(filename,
451
+ sep=' ',
452
+ header=None)
453
+ return x
454
+
455
+
456
+ def save_pd(data, filename):
457
+ """
458
+ Save a pandas dataframe to a csv file
459
+
460
+ Basic usage::
461
+
462
+ save_pd(data, filename)
463
+
464
+ :param data: pandas dataframe containing the data to write out
465
+ :param filename: where to store it
466
+ """
467
+ # based on pandas
468
+ data.to_csv(filename,
469
+ index=None,
470
+ header=None,
471
+ sep=' ',
472
+ na_rep='NaN')
473
+
474
+
475
+ def load_ascii(filename):
476
+ """
477
+ Load an ascii file into a numpy array
478
+
479
+ Basic usage::
480
+
481
+ load_ascii(filename)
482
+
483
+ :param filename: name of the file to load
484
+ """
485
+
486
+ # based on pandas
487
+ x = np.loadtxt(filename)
488
+ return x
489
+
490
+
491
+ def save_ascii(data, filename):
492
+ """
493
+ Save a numpy array to an ascii file
494
+
495
+ Basic usage::
496
+
497
+ save_ascii(data, filename)
498
+
499
+ :param data: numpy array containing the data to write out
500
+ :param filename: where to store it
501
+ """
502
+ # based on pandas
503
+ np.savetxt(filename, data)
504
+
505
+ # ----------------
506
+ # generic routines
507
+ # ----------------
508
+
509
+
510
+ def save(data, filename, example=None, mask=None, text=False, dtype=None):
511
+ """
512
+ Save a numpy array to a file
513
+
514
+ Basic usage::
515
+
516
+ save(data, filename, example, mask, text, dtype)
517
+
518
+ :param data: numpy array containing the data to write out
519
+ :param filename: where to store it
520
+ :param example: example file to copy the geometry from
521
+ :param mask: nifti image containing a mask for the image
522
+ :param text: whether to write out a text file
523
+ :param dtype: data type for the output image (if different from the image)
524
+ """
525
+
526
+ if file_type(filename) == 'cifti':
527
+ save_cifti(data.T, filename, example, vol=True)
528
+ elif file_type(filename) == 'nifti':
529
+ save_nifti(data.T, filename, example, mask, dtype=dtype)
530
+ elif text or file_type(filename) == 'text':
531
+ save_ascii(data, filename)
532
+ elif file_type(filename) == 'binary':
533
+ data = pd.DataFrame(data)
534
+ data.to_pickle(filename, protocol=PICKLE_PROTOCOL)
535
+
536
+
537
+ def load(filename, mask=None, text=False, vol=True):
538
+ """
539
+ Load a numpy array from a file
540
+
541
+ Basic usage::
542
+
543
+ load(filename, mask, text, vol)
544
+
545
+ :param filename: name of the file to load
546
+ :param mask: nifti image containing a mask for the image
547
+ :param text: whether to write out a text file
548
+ :param vol: whether to load the image as a volume
549
+ """
550
+
551
+ if file_type(filename) == 'cifti':
552
+ x = load_cifti(filename, vol=vol)
553
+ elif file_type(filename) == 'nifti':
554
+ x = load_nifti(filename, mask, vol=vol)
555
+ elif text or file_type(filename) == 'text':
556
+ x = load_ascii(filename)
557
+ elif file_type(filename) == 'binary':
558
+ x = pd.read_pickle(filename)
559
+ x = x.to_numpy()
560
+ return x
561
+
562
+ # -------------------
563
+ # sorting routines for batched in normative parallel
564
+ # -------------------
565
+
566
+
567
+ def tryint(s):
568
+ """
569
+ Try to convert a string to an integer
570
+
571
+ Basic usage::
572
+
573
+ tryint(s)
574
+
575
+ :param s: string to convert
576
+ """
577
+
578
+ try:
579
+ return int(s)
580
+ except ValueError:
581
+ return s
582
+
583
+
584
+ def alphanum_key(s):
585
+ """
586
+ Turn a string into a list of numbers
587
+
588
+ Basic usage::
589
+
590
+ alphanum_key(s)
591
+
592
+ :param s: string to convert
593
+ """
594
+ return [tryint(c) for c in re.split('([0-9]+)', s)]
595
+
596
+
597
+ def sort_nicely(l):
598
+ """
599
+ Sort a list of strings in a natural way
600
+
601
+ Basic usage::
602
+
603
+ sort_nicely(l)
604
+
605
+ :param l: list of strings to sort
606
+ """
607
+
608
+ return sorted(l, key=alphanum_key)
@@ -0,0 +1,48 @@
1
+ # Third-party imports
2
+ import scipy.special as spp
3
+ from pytensor.gradient import grad_not_implemented
4
+ from pytensor.scalar.basic import BinaryScalarOp, upgrade_to_float
5
+
6
+
7
+ class KnuOp(BinaryScalarOp):
8
+ """
9
+ Modified Bessel function of the second kind, pytensor wrapper for scipy.special.kv
10
+ """
11
+
12
+ nfunc_spec = ("scipy.special.kv", 2, 1)
13
+
14
+ @staticmethod
15
+ def st_impl(p, x):
16
+ return spp.kv(p, x)
17
+
18
+ def impl(self, p, x):
19
+ return KnuOp.st_impl(p, x)
20
+
21
+ def grad(self, inputs, grads):
22
+ dp = 1e-16
23
+ (p, x) = inputs
24
+ (gz,) = grads
25
+ dfdp = (knuop(p + dp, x) - knuop(p - dp, x)) / (2 * dp)
26
+ return [gz * dfdp, gz * knupop(p, x)]
27
+
28
+
29
+ class KnuPrimeOp(BinaryScalarOp):
30
+ """
31
+ Derivative of the modified Bessel function of the second kind.
32
+ """
33
+
34
+ nfunc_spec = ("scipy.special.kvp", 2, 1)
35
+
36
+ @staticmethod
37
+ def st_impl(p, x):
38
+ return spp.kvp(p, x)
39
+
40
+ def impl(self, p, x):
41
+ return KnuPrimeOp.st_impl(p, x)
42
+
43
+ def grad(self, inputs, grads):
44
+ return [grad_not_implemented(self, 0, "p"), grad_not_implemented(self, 1, "x")]
45
+
46
+
47
+ knuop = KnuOp(upgrade_to_float, name="knuop")
48
+ knupop = KnuPrimeOp(upgrade_to_float, name="knupop")