Repository: pydicom/pydicom

Issue:
Pickling `DataElement` fails due to `_skip_conversion`
**Describe the bug**
After upgrading to Pydicom 3.0 I encountered issues working with certain DICOMs in a multiprocessing (Pytorch DataLoader) environment. The cause seems to be the following nested function, which cannot be pickled: https://github.com/pydicom/pydicom/blob/1ea701553268e34d7c6faece4ef454cb45aebeeb/src/pydicom/dataelem.py#L601-L602

If this function is moved out of a nested scope, things work as expected. It seems like the issue can also be mitigated by opening and processing DICOMs entirely within a worker, but I do have some use cases in which pre-loaded DICOMs are being dispatched to workers.

**Expected behavior**
Support for pickling consistent with previous Pydicom versions. I often use multiprocessing to speed up processing of multiple DICOM files.

**Steps To Reproduce**

```python
from concurrent.futures import ProcessPoolExecutor
import pydicom
from pydicom.data import get_testdata_file
from pydicom.dataset import DataElement, FileDataset


# Load the DICOM and set attribute with something that will trigger closure
dicom_file = get_testdata_file("CT_small.dcm")
dicom_object = pydicom.dcmread(str(dicom_file))
dicom_object["LUTDescriptor"] = DataElement(0x00283002, "LO", [1, 2])


def func(dcm: FileDataset):
    return dcm.pixel_array
    

with ProcessPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(func, [dicom_object] * 10))
```

```
Traceback (most recent call last):
  File "/usr/lib/python3.12/multiprocessing/queues.py", line 264, in _feed
    obj = _ForkingPickler.dumps(obj)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
AttributeError: Can't get local object 'DataElement._convert_value.<locals>._skip_conversion'
```

**Your environment**
```
module       | version
------       | -------
platform     | Linux-6.10.8-artix1-1-x86_64-with-glibc2.40
Python       | 3.12.6 (main, Sep  8 2024, 17:35:09) [GCC 14.2.1 20240805]
pydicom      | 3.0.1
gdcm         | 3.0.24
jpeg_ls      | _module not found_
numpy        | 2.1.2
PIL          | 11.0.0
pylibjpeg    | 2.0.1
openjpeg     | 2.3.0
libjpeg      | _module not found_
```

Discussion:

Solution patch:
diff --git a/doc/release_notes/v3.1.0.rst b/doc/release_notes/v3.1.0.rst
index e11718d7ca..8b68d7ace9 100644
--- a/doc/release_notes/v3.1.0.rst
+++ b/doc/release_notes/v3.1.0.rst
@@ -13,6 +13,8 @@ Fixes
   are not aligned with byte boundaries (:issue:`2134`).
 * Fixed a number of issues with decoding pixel data on big endian systems such as s390x
   (:issue:`2147`).
+* Fixed being unable to pickle datasets containing LUT descriptor elements such as
+  *LUT Descriptor* and *Red Palette Color LUT Descriptor* (:issue:`2160`).
 
 Enhancements
 ------------
diff --git a/src/pydicom/data/download.py b/src/pydicom/data/download.py
index cc1316ed77..30d6c847e5 100644
--- a/src/pydicom/data/download.py
+++ b/src/pydicom/data/download.py
@@ -42,6 +42,7 @@ def update_to(
 
 HERE = pathlib.Path(__file__).resolve().parent
 _SIMULATE_NETWORK_OUTAGE = False  # For testing network outages
+_CONFIG_DIRECTORY = None  # For setting an alternative config directory
 
 
 def calculate_file_hash(fpath: pathlib.Path) -> str:
@@ -74,6 +75,12 @@ def get_config_dir() -> pathlib.Path:
     The config directory will be named ``.pydicom`` and will be created in the
     local user's home directory.
     """
+    if _CONFIG_DIRECTORY is not None:
+        p = pathlib.Path(_CONFIG_DIRECTORY)
+        p.mkdir(exist_ok=True)
+
+        return p
+
     config_dir = pathlib.Path.home() / ".pydicom"
     config_dir.mkdir(exist_ok=True)
 
diff --git a/src/pydicom/dataelem.py b/src/pydicom/dataelem.py
index 9ab695144f..431e294f29 100644
--- a/src/pydicom/dataelem.py
+++ b/src/pydicom/dataelem.py
@@ -101,6 +101,11 @@ def empty_value_for_VR(
     return None
 
 
+def _pass_through(val: Any) -> Any:
+    """Pass through function to skip DataElement value validation."""
+    return val
+
+
 class DataElement:
     """Contain and manipulate a DICOM Element.
 
@@ -597,15 +602,11 @@ def _convert_value(self, val: Any) -> Any:
         # e.g. LUT Descriptor is 'US or SS' and VM 3, but the first and
         #   third values are always US (the third should be <= 16, so SS is OK)
         if self.tag in _LUT_DESCRIPTOR_TAGS and val:
-
-            def _skip_conversion(val: Any) -> Any:
-                return val
-
             validate_value(VR_.US, val[0], self.validation_mode)
             for value in val[1:]:
                 validate_value(self.VR, value, self.validation_mode)
 
-            return MultiValue(_skip_conversion, val)
+            return MultiValue(_pass_through, val)
 
         return MultiValue(self._convert, val)
 
diff --git a/src/pydicom/pixels/decoders/pillow.py b/src/pydicom/pixels/decoders/pillow.py
index 103e661b6d..8f7a3bf769 100644
--- a/src/pydicom/pixels/decoders/pillow.py
+++ b/src/pydicom/pixels/decoders/pillow.py
@@ -27,10 +27,10 @@
 
 
 DECODER_DEPENDENCIES = {
-    uid.JPEGBaseline8Bit: ("pillow>=10.0",),
-    uid.JPEGExtended12Bit: ("pillow>=10.0",),
-    uid.JPEG2000Lossless: ("numpy", "pillow>=10.0"),
-    uid.JPEG2000: ("numpy", "pillow>=10.0"),
+    uid.JPEGBaseline8Bit: ("pillow>=10.3",),
+    uid.JPEGExtended12Bit: ("pillow>=10.3",),
+    uid.JPEG2000Lossless: ("numpy", "pillow>=10.3"),
+    uid.JPEG2000: ("numpy", "pillow>=10.3"),
 }
 
 _LIBJPEG_SYNTAXES = [uid.JPEGBaseline8Bit, uid.JPEGExtended12Bit]
@@ -41,7 +41,7 @@ def is_available(uid: str) -> bool:
     """Return ``True`` if a pixel data decoder for `uid` is available for use,
     ``False`` otherwise.
     """
-    if not _passes_version_check("PIL", (10, 0)):
+    if not _passes_version_check("PIL", (10, 3)):
         return False
 
     if uid in _LIBJPEG_SYNTAXES:
diff --git a/src/pydicom/pixels/decoders/pyjpegls.py b/src/pydicom/pixels/decoders/pyjpegls.py
index aa61d4733b..955662b3a2 100644
--- a/src/pydicom/pixels/decoders/pyjpegls.py
+++ b/src/pydicom/pixels/decoders/pyjpegls.py
@@ -18,14 +18,14 @@
 
 
 DECODER_DEPENDENCIES = {
-    uid.JPEGLSLossless: ("numpy", "pyjpegls>=1.2"),
-    uid.JPEGLSNearLossless: ("numpy", "pyjpegls>=1.2"),
+    uid.JPEGLSLossless: ("numpy", "pyjpegls>=1.5"),
+    uid.JPEGLSNearLossless: ("numpy", "pyjpegls>=1.5"),
 }
 
 
 def is_available(uid: str) -> bool:
     """Return ``True`` if the decoder has its dependencies met, ``False`` otherwise"""
-    return _passes_version_check("jpeg_ls", (1, 2))
+    return _passes_version_check("jpeg_ls", (1, 5))
 
 
 def _decode_frame(src: bytes, runner: DecodeRunner) -> bytearray:
diff --git a/src/pydicom/pixels/decoders/pylibjpeg.py b/src/pydicom/pixels/decoders/pylibjpeg.py
index a26f4022e4..3ed80e724e 100644
--- a/src/pydicom/pixels/decoders/pylibjpeg.py
+++ b/src/pydicom/pixels/decoders/pylibjpeg.py
@@ -24,17 +24,17 @@
 
 
 DECODER_DEPENDENCIES = {
-    uid.JPEGBaseline8Bit: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
-    uid.JPEGExtended12Bit: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
-    uid.JPEGLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
-    uid.JPEGLosslessSV1: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
-    uid.JPEGLSLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
-    uid.JPEGLSNearLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
-    uid.JPEG2000Lossless: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
-    uid.JPEG2000: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
-    uid.HTJ2KLossless: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
-    uid.HTJ2KLosslessRPCL: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
-    uid.HTJ2K: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
+    uid.JPEGBaseline8Bit: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.3"),
+    uid.JPEGExtended12Bit: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.3"),
+    uid.JPEGLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.3"),
+    uid.JPEGLosslessSV1: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.3"),
+    uid.JPEGLSLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.3"),
+    uid.JPEGLSNearLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.3"),
+    uid.JPEG2000Lossless: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.4"),
+    uid.JPEG2000: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.4"),
+    uid.HTJ2KLossless: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.4"),
+    uid.HTJ2KLosslessRPCL: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.4"),
+    uid.HTJ2K: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.4"),
     uid.RLELossless: ("pylibjpeg>=2.0", "pylibjpeg-rle>=2.0"),
 }
 
@@ -62,10 +62,10 @@ def is_available(uid: str) -> bool:
         return False
 
     if uid in _LIBJPEG_SYNTAXES:
-        return _passes_version_check("libjpeg", (2, 0, 2))
+        return _passes_version_check("libjpeg", (2, 3))
 
     if uid in _OPENJPEG_SYNTAXES:
-        return _passes_version_check("openjpeg", (2, 0))
+        return _passes_version_check("openjpeg", (2, 4))
 
     if uid in _RLE_SYNTAXES:
         return _passes_version_check("rle", (2, 0))
diff --git a/src/pydicom/pixels/encoders/pyjpegls.py b/src/pydicom/pixels/encoders/pyjpegls.py
index 5486cb89f8..89457dcedc 100644
--- a/src/pydicom/pixels/encoders/pyjpegls.py
+++ b/src/pydicom/pixels/encoders/pyjpegls.py
@@ -18,14 +18,14 @@
 
 
 ENCODER_DEPENDENCIES = {
-    uid.JPEGLSLossless: ("numpy", "pyjpegls>=1.3"),
-    uid.JPEGLSNearLossless: ("numpy", "pyjpegls>=1.3"),
+    uid.JPEGLSLossless: ("numpy", "pyjpegls>=1.5"),
+    uid.JPEGLSNearLossless: ("numpy", "pyjpegls>=1.5"),
 }
 
 
 def is_available(uid: str) -> bool:
     """Return ``True`` if the decoder has its dependencies met, ``False`` otherwise"""
-    return _passes_version_check("jpeg_ls", (1, 3))
+    return _passes_version_check("jpeg_ls", (1, 5))
 
 
 def _encode_frame(src: bytes, runner: EncodeRunner) -> bytearray:
diff --git a/src/pydicom/pixels/encoders/pylibjpeg.py b/src/pydicom/pixels/encoders/pylibjpeg.py
index 7b5f9b3dc8..752258bbdf 100644
--- a/src/pydicom/pixels/encoders/pylibjpeg.py
+++ b/src/pydicom/pixels/encoders/pylibjpeg.py
@@ -17,8 +17,8 @@
 
 
 ENCODER_DEPENDENCIES = {
-    uid.JPEG2000Lossless: ("numpy", "pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.2"),
-    uid.JPEG2000: ("numpy", "pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.2"),
+    uid.JPEG2000Lossless: ("numpy", "pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.4"),
+    uid.JPEG2000: ("numpy", "pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.4"),
     uid.RLELossless: ("numpy", "pylibjpeg>=2.0", "pylibjpeg-rle>=2.0"),
 }
 _OPENJPEG_SYNTAXES = [uid.JPEG2000Lossless, uid.JPEG2000]
@@ -33,7 +33,7 @@ def is_available(uid: str) -> bool:
         return False
 
     if uid in _OPENJPEG_SYNTAXES:
-        return _passes_version_check("openjpeg", (2, 2))
+        return _passes_version_check("openjpeg", (2, 4))
 
     if uid in _RLE_SYNTAXES:
         return _passes_version_check("rle", (2, 0))
diff --git a/src/pydicom/pixels/utils.py b/src/pydicom/pixels/utils.py
index 5d07ba29ea..2d49c5fb6e 100644
--- a/src/pydicom/pixels/utils.py
+++ b/src/pydicom/pixels/utils.py
@@ -1333,9 +1333,11 @@ def _passes_version_check(package_name: str, minimum_version: tuple[int, ...]) -
     """Return True if `package_name` is available and its version is greater or
     equal to `minimum_version`
     """
+
     try:
         module = importlib.import_module(package_name, "__version__")
-        return tuple(int(x) for x in module.__version__.split(".")) >= minimum_version
+        available_version = module.__version__.split(".")[: len(minimum_version)]
+        return tuple(int(x) for x in available_version) >= minimum_version
     except Exception as exc:
         LOGGER.debug(exc)
 


Test patch:
diff --git a/tests/pixels/test_decoder_pyjpegls.py b/tests/pixels/test_decoder_pyjpegls.py
index 20903e8907..cb198c5f41 100644
--- a/tests/pixels/test_decoder_pyjpegls.py
+++ b/tests/pixels/test_decoder_pyjpegls.py
@@ -61,7 +61,7 @@ def test_bits_allocated_mismatch_as_array(self):
         JLSN_08_01_1_0_1F.test(arr)
         assert arr.shape == JLSN_08_01_1_0_1F.shape
         assert arr.dtype != JLSN_08_01_1_0_1F.dtype
-        assert arr.dtype == np.uint16
+        assert arr.dtype == "<u2"
         assert arr.flags.writeable
         assert meta["bits_allocated"] == 16
         assert meta["bits_stored"] == 8
diff --git a/tests/pixels/test_decoder_pylibjpeg.py b/tests/pixels/test_decoder_pylibjpeg.py
index d378b1e2a3..cbcf4d14e8 100644
--- a/tests/pixels/test_decoder_pylibjpeg.py
+++ b/tests/pixels/test_decoder_pylibjpeg.py
@@ -155,7 +155,7 @@ def test_bits_allocated_mismatch(self):
         JLSN_08_01_1_0_1F.test(arr)
         assert arr.shape == JLSN_08_01_1_0_1F.shape
         assert arr.dtype != JLSN_08_01_1_0_1F.dtype
-        assert arr.dtype == np.uint16
+        assert arr.dtype == "<u2"
         assert arr.flags.writeable
         assert meta["bits_allocated"] == 16
         assert meta["bits_stored"] == 8
@@ -235,11 +235,11 @@ def test_mixed_container_sizes(self):
         decoder = get_decoder(JPEGLSLossless)
         buffer, meta = decoder.as_buffer(ds, decoding_plugin="pylibjpeg")
         assert meta["bits_allocated"] == 16
-        arr = np.frombuffer(buffer, dtype="u2")
+        arr = np.frombuffer(buffer, dtype="<u2")
         arr = arr.reshape(2, ds.Rows, ds.Columns)
         JLSL_08_07_1_0_1F.test(arr[0], plugin="pylibjpeg")
 
-        arr = arr.astype("i2")
+        arr = arr.astype("<i2")
         # Needs bit-shifting to convert values to signed
         np.left_shift(arr, 1, out=arr)
         np.right_shift(arr, 1, out=arr)
diff --git a/tests/pixels/test_encoder_pyjpegls.py b/tests/pixels/test_encoder_pyjpegls.py
index 98b9f9b675..c249e3b19a 100644
--- a/tests/pixels/test_encoder_pyjpegls.py
+++ b/tests/pixels/test_encoder_pyjpegls.py
@@ -43,7 +43,7 @@ def setup_method(self):
         arr = ds.pixel_array
 
         # Rescale to (0, 1)
-        arr = arr.astype("float32")
+        arr = arr.astype("<f4")
         arr -= arr.min()
         arr /= arr.max()
         self.ref = arr
@@ -51,7 +51,7 @@ def setup_method(self):
         ds = examples.rgb_color
         arr = ds.pixel_array
 
-        arr = arr.astype("float32")
+        arr = arr.astype("<f4")
         arr -= arr.min()
         arr /= arr.max()
         self.ref3 = arr
@@ -176,7 +176,7 @@ def test_arr_u2_spp1(self):
         for bits_stored in range(2, 17):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSLosslessEncoder.encode(ref, encoding_plugin="pyjpegls", **opts)
@@ -216,7 +216,7 @@ def test_arr_u2_spp3(self):
         for bits_stored in range(2, 9):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSLosslessEncoder.encode(ref, encoding_plugin="pyjpegls", **opts)
@@ -236,7 +236,7 @@ def test_arr_u2_spp3(self):
         for bits_stored in range(2, 9):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSLosslessEncoder.encode(ref, encoding_plugin="pyjpegls", **opts)
@@ -313,7 +313,7 @@ def test_arr_i2_spp1(self):
             ref = self.ref * (2**bits_stored - 1)
             ref -= 2 ** (bits_stored - 1)
             ref = ref.clip(-(2**15), 2**15 - 1)
-            ref = ref.astype("int16")
+            ref = ref.astype("<i2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSLosslessEncoder.encode(ref, encoding_plugin="pyjpegls", **opts)
@@ -459,7 +459,7 @@ def test_buffer_u2_spp1(self):
         for bits_stored in range(2, 17):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 2
@@ -503,7 +503,7 @@ def test_buffer_u2_spp3(self):
         for bits_stored in range(2, 9):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 6
@@ -528,7 +528,7 @@ def test_buffer_u2_spp3(self):
         for bits_stored in range(2, 9):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSLosslessEncoder.encode(ref, encoding_plugin="pyjpegls", **opts)
@@ -610,7 +610,7 @@ def test_buffer_i2_spp1(self):
             ref = self.ref * (2**bits_stored - 1)
             ref -= 2 ** (bits_stored - 1)
             ref = ref.clip(-(2**15), 2**15 - 1)
-            ref = ref.astype("int16")
+            ref = ref.astype("<i2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 2
@@ -669,7 +669,7 @@ def setup_method(self):
         arr = ds.pixel_array
 
         # Rescale to (0, 1)
-        arr = arr.astype("float32")
+        arr = arr.astype("<f4")
         arr -= arr.min()
         arr /= arr.max()
         self.ref = arr
@@ -677,7 +677,7 @@ def setup_method(self):
         ds = examples.rgb_color
         arr = ds.pixel_array
 
-        arr = arr.astype("float32")
+        arr = arr.astype("<f4")
         arr -= arr.min()
         arr /= arr.max()
         self.ref3 = arr
@@ -813,7 +813,7 @@ def test_arr_u2_spp1(self):
         for bits_stored in range(2, 17):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSNearLosslessEncoder.encode(
@@ -857,7 +857,7 @@ def test_arr_u2_spp3(self):
         for bits_stored in range(2, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSNearLosslessEncoder.encode(
@@ -880,7 +880,7 @@ def test_arr_u2_spp3(self):
         for bits_stored in range(2, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSNearLosslessEncoder.encode(
@@ -972,7 +972,7 @@ def test_arr_i2_spp1(self):
             ref -= 2 ** (bits_stored - 1)
             # Clip within the min/max values for the given bits_stored
             ref = ref.clip(-(2 ** (bits_stored - 1)) + 1, 2 ** (bits_stored - 1) - 2)
-            ref = ref.astype("int16")
+            ref = ref.astype("<i2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSNearLosslessEncoder.encode(
@@ -1132,7 +1132,7 @@ def test_buffer_u2_spp1(self):
         for bits_stored in range(2, 17):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 2
@@ -1179,7 +1179,7 @@ def test_buffer_u2_spp3(self):
         for bits_stored in range(2, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 6
@@ -1205,7 +1205,7 @@ def test_buffer_u2_spp3(self):
         for bits_stored in range(2, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEGLSNearLosslessEncoder.encode(
@@ -1300,7 +1300,7 @@ def test_buffer_i2_spp1(self):
             ref -= 2 ** (bits_stored - 1)
             # Clip within the min/max values for the given bits_stored
             ref = ref.clip(-(2 ** (bits_stored - 1)) + 1, 2 ** (bits_stored - 1) - 2)
-            ref = ref.astype("int16")
+            ref = ref.astype("<i2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 2
diff --git a/tests/pixels/test_encoder_pylibjpeg.py b/tests/pixels/test_encoder_pylibjpeg.py
index 82e729503a..485d5a7c9d 100644
--- a/tests/pixels/test_encoder_pylibjpeg.py
+++ b/tests/pixels/test_encoder_pylibjpeg.py
@@ -117,7 +117,7 @@ def setup_method(self):
         arr = ds.pixel_array
 
         # Rescale to (0, 1)
-        arr = arr.astype("float32")
+        arr = arr.astype("<f4")
         arr -= arr.min()
         arr /= arr.max()
         self.ref = arr
@@ -125,7 +125,7 @@ def setup_method(self):
         ds = dcmread(RGB)
         arr = ds.pixel_array
 
-        arr = arr.astype("float32")
+        arr = arr.astype("<f4")
         arr -= arr.min()
         arr /= arr.max()
         self.ref3 = arr
@@ -188,7 +188,7 @@ def test_arr_u2_spp1(self):
         for bits_stored in range(1, 17):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000LosslessEncoder.encode(
@@ -196,10 +196,6 @@ def test_arr_u2_spp1(self):
             )
 
             for plugin in plugins:
-                # Pillow doesn't decode 9-bit J2K correctly
-                if plugin == "pillow" and bits_stored == 9:
-                    continue
-
                 out, _ = JPEG2000LosslessDecoder.as_array(
                     encapsulate([cs]),
                     decoding_plugin=plugin,
@@ -226,7 +222,7 @@ def test_arr_u4_spp1(self):
         for bits_stored in range(1, 25):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**24 - 1)
-            ref = ref.astype("uint32")
+            ref = ref.astype("<u4")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000LosslessEncoder.encode(
@@ -298,7 +294,7 @@ def test_arr_u2_spp3(self):
         for bits_stored in range(1, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000LosslessEncoder.encode(
@@ -329,7 +325,7 @@ def test_arr_u4_spp3(self):
         for bits_stored in range(1, 25):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**24 - 1)
-            ref = ref.astype("uint32")
+            ref = ref.astype("<u4")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000LosslessEncoder.encode(
@@ -403,7 +399,7 @@ def test_arr_i2_spp1(self):
             ref = self.ref * (2**bits_stored - 1)
             ref -= 2 ** (bits_stored - 1)
             ref = ref.clip(-32768, 32767)
-            ref = ref.astype("int16")
+            ref = ref.astype("<i2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000LosslessEncoder.encode(
@@ -411,10 +407,6 @@ def test_arr_i2_spp1(self):
             )
 
             for plugin in plugins:
-                # Pillow doesn't decode 9-bit J2K correctly
-                if plugin == "pillow" and bits_stored == 9:
-                    continue
-
                 out, _ = JPEG2000LosslessDecoder.as_array(
                     encapsulate([cs]),
                     decoding_plugin=plugin,
@@ -443,7 +435,7 @@ def test_arr_i4_spp1(self):
             ref = self.ref * (2**bits_stored - 1)
             ref -= 2 ** (bits_stored - 1)
             ref = ref.clip(-8388608, 8388607)
-            ref = ref.astype("int32")
+            ref = ref.astype("<i4")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000LosslessEncoder.encode(
@@ -518,7 +510,7 @@ def test_buffer_u2_spp1(self):
         for bits_stored in range(1, 17):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 65535)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 2
@@ -528,9 +520,6 @@ def test_buffer_u2_spp1(self):
                 buffer, encoding_plugin="pylibjpeg", **opts
             )
             for plugin in plugins:
-                if plugin == "pillow" and bits_stored == 9:
-                    continue
-
                 out, _ = JPEG2000LosslessDecoder.as_array(
                     encapsulate([cs]),
                     decoding_plugin=plugin,
@@ -554,7 +543,7 @@ def test_buffer_u4_spp1(self):
         for bits_stored in range(1, 25):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**24 - 1)
-            ref = ref.astype("uint32")
+            ref = ref.astype("<u4")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 4
@@ -631,7 +620,7 @@ def test_buffer_u2_spp3(self):
         for bits_stored in range(1, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**16 - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 3 * 2
@@ -665,7 +654,7 @@ def test_buffer_u4_spp3(self):
         for bits_stored in range(1, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**24 - 1)
-            ref = ref.astype("uint32")
+            ref = ref.astype("<u4")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 3 * 4
@@ -745,7 +734,7 @@ def test_buffer_i2_spp1(self):
             ref = self.ref * (2**bits_stored - 1)
             ref -= 2 ** (bits_stored - 1)
             ref = ref.clip(-(2**15), 2**15 - 1)
-            ref = ref.astype("int16")
+            ref = ref.astype("<i2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 2
@@ -756,9 +745,6 @@ def test_buffer_i2_spp1(self):
             )
 
             for plugin in plugins:
-                if plugin == "pillow" and bits_stored == 9:
-                    continue
-
                 out, _ = JPEG2000LosslessDecoder.as_array(
                     encapsulate([cs]),
                     decoding_plugin=plugin,
@@ -783,7 +769,7 @@ def test_buffer_i4_spp1(self):
             ref = self.ref * (2**bits_stored - 1)
             ref -= 2 ** (bits_stored - 1)
             ref = ref.clip(-(2**23), 2**23 - 1)
-            ref = ref.astype("int32")
+            ref = ref.astype("<i4")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 4
@@ -889,7 +875,7 @@ def setup_method(self):
         arr = ds.pixel_array
 
         # Rescale to (0, 1)
-        arr = arr.astype("float32")
+        arr = arr.astype("<f4")
         arr -= arr.min()
         arr /= arr.max()
         self.ref = arr
@@ -897,7 +883,7 @@ def setup_method(self):
         ds = dcmread(RGB)
         arr = ds.pixel_array
 
-        arr = arr.astype("float32")
+        arr = arr.astype("<f4")
         arr -= arr.min()
         arr /= arr.max()
         self.ref3 = arr
@@ -961,16 +947,12 @@ def test_arr_u2_spp1(self):
         for bits_stored in range(1, 17):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**bits_stored - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000Encoder.encode(ref, encoding_plugin="pylibjpeg", **opts)
 
             for plugin in plugins:
-                # Pillow doesn't decode 9-bit J2K correctly
-                if plugin == "pillow" and bits_stored == 9:
-                    continue
-
                 out, _ = JPEG2000Decoder.as_array(
                     encapsulate([cs]),
                     decoding_plugin=plugin,
@@ -998,7 +980,7 @@ def test_arr_u4_spp1(self):
         for bits_stored, atol in zip(range(1, 20), atols):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**bits_stored - 1)
-            ref = ref.astype("uint32")
+            ref = ref.astype("<u4")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000Encoder.encode(ref, encoding_plugin="pylibjpeg", **opts)
@@ -1069,7 +1051,7 @@ def test_arr_u2_spp3(self):
         for bits_stored in range(1, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**bits_stored - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000Encoder.encode(ref, encoding_plugin="pylibjpeg", **opts)
@@ -1103,7 +1085,7 @@ def test_arr_u4_spp3(self):
         for bits_stored, atol in zip(range(1, 20), atols):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**bits_stored - 1)
-            ref = ref.astype("uint32")
+            ref = ref.astype("<u4")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000Encoder.encode(ref, encoding_plugin="pylibjpeg", **opts)
@@ -1181,16 +1163,12 @@ def test_arr_i2_spp1(self):
             minimum = -(2 ** (bits_stored - 1))
             maximum = 2 ** (bits_stored - 1) - 1
             ref = ref.clip(minimum, maximum)
-            ref = ref.astype("int16")
+            ref = ref.astype("<i2")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000Encoder.encode(ref, encoding_plugin="pylibjpeg", **opts)
 
             for plugin in plugins:
-                # Pillow doesn't decode 9-bit J2K correctly
-                if plugin == "pillow" and bits_stored == 9:
-                    continue
-
                 out, _ = JPEG2000Decoder.as_array(
                     encapsulate([cs]),
                     decoding_plugin=plugin,
@@ -1222,7 +1200,7 @@ def test_arr_i4_spp1(self):
             minimum = -(2 ** (bits_stored - 1))
             maximum = 2 ** (bits_stored - 1) - 1
             ref = ref.clip(minimum, maximum)
-            ref = ref.astype("int32")
+            ref = ref.astype("<i4")
 
             opts["bits_stored"] = bits_stored
             cs = JPEG2000Encoder.encode(ref, encoding_plugin="pylibjpeg", **opts)
@@ -1296,7 +1274,7 @@ def test_buffer_u2_spp1(self):
         for bits_stored in range(1, 17):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**bits_stored - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 2
@@ -1304,9 +1282,6 @@ def test_buffer_u2_spp1(self):
             opts["bits_stored"] = bits_stored
             cs = JPEG2000Encoder.encode(buffer, encoding_plugin="pylibjpeg", **opts)
             for plugin in plugins:
-                if plugin == "pillow" and bits_stored == 9:
-                    continue
-
                 out, _ = JPEG2000Decoder.as_array(
                     encapsulate([cs]),
                     decoding_plugin=plugin,
@@ -1335,7 +1310,7 @@ def test_buffer_u4_spp1(self):
         for bits_stored, atol in zip(range(1, 21), atols):
             ref = self.ref * (2**bits_stored - 1)
             ref = ref.clip(0, 2**bits_stored - 1)
-            ref = ref.astype("uint32")
+            ref = ref.astype("<u4")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 4
@@ -1412,7 +1387,7 @@ def test_buffer_u2_spp3(self):
         for bits_stored in range(1, 17):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**bits_stored - 1)
-            ref = ref.astype("uint16")
+            ref = ref.astype("<u2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 3 * 2
@@ -1449,7 +1424,7 @@ def test_buffer_u4_spp3(self):
         for bits_stored, atol in zip(range(1, 20), atols):
             ref = self.ref3 * (2**bits_stored - 1)
             ref = ref.clip(0, 2**bits_stored - 1)
-            ref = ref.astype("uint32")
+            ref = ref.astype("<u4")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 3 * 4
@@ -1534,7 +1509,7 @@ def test_buffer_i2_spp1(self):
             minimum = -(2 ** (bits_stored - 1))
             maximum = 2 ** (bits_stored - 1) - 1
             ref = ref.clip(minimum, maximum)
-            ref = ref.astype("int16")
+            ref = ref.astype("<i2")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 2
@@ -1543,9 +1518,6 @@ def test_buffer_i2_spp1(self):
             cs = JPEG2000Encoder.encode(buffer, encoding_plugin="pylibjpeg", **opts)
 
             for plugin in plugins:
-                if plugin == "pillow" and bits_stored == 9:
-                    continue
-
                 out, _ = JPEG2000Decoder.as_array(
                     encapsulate([cs]),
                     decoding_plugin=plugin,
@@ -1577,7 +1549,7 @@ def test_buffer_i4_spp1(self):
             minimum = -(2 ** (bits_stored - 1))
             maximum = 2 ** (bits_stored - 1) - 1
             ref = ref.clip(minimum, maximum)
-            ref = ref.astype("int32")
+            ref = ref.astype("<i4")
 
             buffer = ref.tobytes()
             assert len(buffer) == ds.Rows * ds.Columns * 4
diff --git a/tests/test_data_manager.py b/tests/test_data_manager.py
index da4e899962..8faaf35ef8 100644
--- a/tests/test_data_manager.py
+++ b/tests/test_data_manager.py
@@ -45,6 +45,20 @@ def download_failure():
     download._SIMULATE_NETWORK_OUTAGE = False
 
 
+@pytest.fixture()
+def data_directory(tmp_path_factory):
+    src = get_data_dir()
+    original = download._CONFIG_DIRECTORY
+    path = tmp_path_factory.mktemp("data")
+
+    # Copy contents of the cache to the new temporary directory
+    shutil.copytree(src, path / "data")
+
+    download._CONFIG_DIRECTORY = path
+    yield
+    download._CONFIG_DIRECTORY = original
+
+
 class TestGetData:
     def test_get_dataset(self):
         """Test the different functions to get lists of data files."""
@@ -310,9 +324,9 @@ def test_get_testdata_files_network_outage(self, download_failure):
             assert [] == get_testdata_files("693_UN*")
 
 
-def test_fetch_data_files():
+def test_fetch_data_files(data_directory):
     """Test fetch_data_files()."""
-    # Remove a single file from the cache
+    # Remove a single file from the temporary cache
     cache = get_data_dir()
     path = cache / "693_J2KR.dcm"
     if path.exists():
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index efd5ce4037..e0034f2f46 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -1756,6 +1756,15 @@ def test_is_decompressed(self):
         ds.file_meta.TransferSyntaxUID = JPEGBaseline8Bit
         assert ds.is_decompressed is False
 
+    def test_pickle_lut(self):
+        """Reversion test for #2160"""
+        ds = Dataset()
+        ds["LUTDescriptor"] = DataElement(0x00283002, "US", [1, 2])
+        s = pickle.dumps({"ds": ds})
+        ds1 = pickle.loads(s)["ds"]
+        assert ds == ds1
+        assert ds1.LUTDescriptor == [1, 2]
+
 
 class TestDatasetSaveAs:
     def test_no_transfer_syntax(self):