diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 49beaf83d423cd3a0e784b2af0bb8cebd2a745c7..e0d0e7d2dd6df4327e3ac20e1f97e7da1365f7ac 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -156,12 +156,6 @@ variables:
     - bash ./.ci/test_template.sh
 
 
-test:3.8:
-  stage: test
-  image: pauldmccarthy/fsleyes-py38-wxpy4-gtk3
-  <<: *test_template
-
-
 test:3.9:
   stage: test
   image: pauldmccarthy/fsleyes-py39-wxpy4-gtk3
@@ -180,6 +174,12 @@ test:3.11:
   <<: *test_template
 
 
+test:3.12:
+  stage: test
+  image: pauldmccarthy/fsleyes-py312-wxpy4-gtk3
+  <<: *test_template
+
+
 test:build-pypi-dist:
   stage: test
   image: python:3.10
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9ad4ee613fa477ff4f90a20ad91b411cd6cb0843..21686ee766975302b58522165542242c619391db 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -10,7 +10,16 @@ Added
 ^^^^^
 
 
-* New :func:`.bedpostx_postproc` wrapper function (!454).
+* New :func:`.bedpostx` and :func:`.bedpostx_gpu` wrapper functions (!455).
+
+
+Deprecated
+^^^^^^^^^^
+
+
+* Deprecated the :func:`.bedpostx_postproc_gpu` wrapper function - the
+  ``bedpostx_postproc_gpu.sh`` and ``bedpostx_postproc.sh`` scripts are not
+  intended to be called directly (!455).
 
 
 3.18.3 (Saturday 4th May 2024)
diff --git a/fsl/tests/test_wrappers/test_wrappers.py b/fsl/tests/test_wrappers/test_wrappers.py
index cca8c26dd240abdc10979ada84302c3ad600d2e6..f0b1af63dbea6f9c1ef835a2ce84f3526cd4a87a 100755
--- a/fsl/tests/test_wrappers/test_wrappers.py
+++ b/fsl/tests/test_wrappers/test_wrappers.py
@@ -578,13 +578,18 @@ def test_split_parts_gpu():
         assert res.stdout[0] == exp
 
 
-def test_bedpostx_postproc():
-    with testenv('bedpostx_postproc.sh') as bpg:
-        res = fw.bedpostx_postproc('data', 'mask', 'bvecs', 'bvals',
-                                   100, 10, 'subdir', 'bindir', nf=20)
-        exp = f'{bpg} --data=data --mask=mask --bvecs=bvecs --bvals=bvals ' \
-               '--nf=20 100 10 subdir bindir'
-        assert res.stdout[0] == exp
+def test_bedpostx():
+    with testenv('bedpostx') as bpx:
+        res = fw.bedpostx('data', noard=True, nf=2)
+        exp = f'{bpx} data --noard --nf=2'
+        assert res[0] == exp
+
+
+def test_bedpostx_gpu():
+    with testenv('bedpostx_gpu') as bpx:
+        res = fw.bedpostx_gpu('data', noard=True, nf=2)
+        exp = f'{bpx} data --noard --nf=2'
+        assert res[0] == exp
 
 
 def test_bedpostx_postproc_gpu():
diff --git a/fsl/wrappers/__init__.py b/fsl/wrappers/__init__.py
index bed50b2645baff4c9def31deb4b3ecbefc520b7b..8ce7a94b21d3d3ada0c4f6a601cb6f4f5c65920f 100755
--- a/fsl/wrappers/__init__.py
+++ b/fsl/wrappers/__init__.py
@@ -165,8 +165,9 @@ from fsl.wrappers.fdt                import (dtifit,
 from fsl.wrappers.bedpostx           import (xfibres,
                                              xfibres_gpu,
                                              split_parts_gpu,
+                                             bedpostx,
+                                             bedpostx_gpu,
                                              bedpostx_postproc_gpu,
-                                             bedpostx_postproc,
                                              probtrackx,
                                              probtrackx2,
                                              probtrackx2_gpu)
diff --git a/fsl/wrappers/bedpostx.py b/fsl/wrappers/bedpostx.py
index 075af9698890282b9702ee54196f8d7e06256f02..84e284ac72cc7dd9caf58035788ce1a4c40ca528 100644
--- a/fsl/wrappers/bedpostx.py
+++ b/fsl/wrappers/bedpostx.py
@@ -14,14 +14,14 @@ commands.
    xfibres
    xfibres_gpu
    split_parts_gpu
-   bedpostx_postproc
-   bedpostx_postproc_gpu
+   bedpostx
+   bedpostx_gpu
    probtrackx
    probtrackx2
    probtrackx2_gpu
 """
 
-
+import fsl.utils.deprecated as deprecated
 import fsl.utils.assertions as asrt
 from . import wrapperutils  as wutils
 
@@ -78,6 +78,50 @@ by the corresponding wrapper functions.
 """
 
 
+@wutils.fslwrapper
+def bedpostx(data_dir, **kwargs):
+    """Wrapper for the ``bedpostx`` command."""
+
+    cmd = ['bedpostx', data_dir]
+
+    # Uses same VALMAP as xfibres
+    cmd += wutils.applyArgStyle('--=', valmap=XFIBRES_VALMAP, **kwargs)
+    return cmd
+
+
+@wutils.fslwrapper
+def bedpostx_gpu(data_dir, **kwargs):
+    """Wrapper for the ``bedpostx_gpu`` command."""
+
+    cmd = ['bedpostx_gpu', data_dir]
+
+    # Uses same VALMAP as xfibres
+    cmd += wutils.applyArgStyle('--=', valmap=XFIBRES_VALMAP, **kwargs)
+    return cmd
+
+
+@wutils.fileOrImage('data', 'mask',)
+@wutils.fileOrArray('bvecs', 'bvals')
+@wutils.fslwrapper
+@deprecated.deprecated('3.19.0', '4.0.0', 'Use bedpostx_gpu directly')
+def bedpostx_postproc_gpu(data, mask, bvecs, bvals, TotalNumVoxels,
+                          TotalNumParts, SubjectDir, bindir, **kwargs):
+    """Wrapper for the ``bedpostx_postproc_gpu`` command."""
+
+    asrt.assertFileExists(data, bvecs, bvals)
+    asrt.assertIsNifti(mask)
+
+    cmd = ['bedpostx_postproc_gpu.sh',
+           '--data='  + data,
+           '--mask='  + mask,
+           '--bvecs=' + bvecs,
+           '--bvals=' + bvals]
+
+    cmd += wutils.applyArgStyle('--=', valmap=XFIBRES_VALMAP, **kwargs)
+    cmd += [str(TotalNumVoxels), str(TotalNumParts), SubjectDir, bindir]
+    return cmd
+
+
 @wutils.fileOrImage('data', 'mask',)
 @wutils.fileOrArray('bvecs', 'bvals')
 @wutils.fslwrapper
@@ -140,48 +184,6 @@ def split_parts_gpu(Datafile, Maskfile, Bvalsfile, Bvecsfile, TotalNumParts,
     return cmd
 
 
-@wutils.fileOrImage('data', 'mask',)
-@wutils.fileOrArray('bvecs', 'bvals')
-@wutils.fslwrapper
-def bedpostx_postproc_gpu(data, mask, bvecs, bvals, TotalNumVoxels,
-                          TotalNumParts, SubjectDir, bindir, **kwargs):
-    """Wrapper for the ``bedpostx_postproc_gpu`` command."""
-
-    asrt.assertFileExists(data, bvecs, bvals)
-    asrt.assertIsNifti(mask)
-
-    cmd = ['bedpostx_postproc_gpu.sh',
-           '--data='  + data,
-           '--mask='  + mask,
-           '--bvecs=' + bvecs,
-           '--bvals=' + bvals]
-
-    cmd += wutils.applyArgStyle('--=', valmap=XFIBRES_VALMAP, **kwargs)
-    cmd += [str(TotalNumVoxels), str(TotalNumParts), SubjectDir, bindir]
-    return cmd
-
-
-@wutils.fileOrImage('data', 'mask',)
-@wutils.fileOrArray('bvecs', 'bvals')
-@wutils.fslwrapper
-def bedpostx_postproc(data, mask, bvecs, bvals, TotalNumVoxels,
-                      TotalNumParts, SubjectDir, bindir, **kwargs):
-    """Wrapper for the ``bedpostx_postproc`` command."""
-
-    asrt.assertFileExists(data, bvecs, bvals)
-    asrt.assertIsNifti(mask)
-
-    cmd = ['bedpostx_postproc.sh',
-           '--data='  + data,
-           '--mask='  + mask,
-           '--bvecs=' + bvecs,
-           '--bvals=' + bvals]
-
-    cmd += wutils.applyArgStyle('--=', valmap=XFIBRES_VALMAP, **kwargs)
-    cmd += [str(TotalNumVoxels), str(TotalNumParts), SubjectDir, bindir]
-    return cmd
-
-
 @wutils.fileOrImage('mask', 'seed')
 @wutils.fslwrapper
 def probtrackx(samples, mask, seed, **kwargs):