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:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pydicom/pydicom/pull/2163?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 98.39%. Comparing base [(`1ea7015`)](https://app.codecov.io/gh/pydicom/pydicom/commit/1ea701553268e34d7c6faece4ef454cb45aebeeb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) to head [(`0025396`)](https://app.codecov.io/gh/pydicom/pydicom/commit/002539644a52d9a0f437dbbe8356b9ebbb61d581?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #2163      +/-   ##
==========================================
+ Coverage   98.34%   98.39%   +0.04%     
==========================================
  Files          77       77              
  Lines       13515    13515              
==========================================
+ Hits        13292    13298       +6     
+ Misses        223      217       -6     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pydicom/pydicom/pull/2163?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom).


Patch:
diff --git a/doc/release_notes/v3.1.0.rst b/doc/release_notes/v3.1.0.rst
index e11718d7c..8b68d7ace 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 cc1316ed7..30d6c847e 100644
--- a/src/pydicom/data/download.py
+++ b/src/pydicom/data/download.py
@@ -42,6 +42,7 @@ from pydicom.misc import warn_and_log
 
 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 9ab695144..431e294f2 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 @@ class DataElement:
         # 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 103e661b6..8f7a3bf76 100644
--- a/src/pydicom/pixels/decoders/pillow.py
+++ b/src/pydicom/pixels/decoders/pillow.py
@@ -27,10 +27,10 @@ except ImportError:
 
 
 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 aa61d4733..955662b3a 100644
--- a/src/pydicom/pixels/decoders/pyjpegls.py
+++ b/src/pydicom/pixels/decoders/pyjpegls.py
@@ -18,14 +18,14 @@ except ImportError:
 
 
 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 a26f4022e..3ed80e724 100644
--- a/src/pydicom/pixels/decoders/pylibjpeg.py
+++ b/src/pydicom/pixels/decoders/pylibjpeg.py
@@ -24,17 +24,17 @@ except ImportError:
 
 
 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 5486cb89f..89457dced 100644
--- a/src/pydicom/pixels/encoders/pyjpegls.py
+++ b/src/pydicom/pixels/encoders/pyjpegls.py
@@ -18,14 +18,14 @@ except ImportError:
 
 
 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 7b5f9b3dc..752258bbd 100644
--- a/src/pydicom/pixels/encoders/pylibjpeg.py
+++ b/src/pydicom/pixels/encoders/pylibjpeg.py
@@ -17,8 +17,8 @@ except ImportError:
 
 
 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 5d07ba29e..2d49c5fb6 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 20903e890..cb198c5f4 100644
--- a/tests/pixels/test_decoder_pyjpegls.py
+++ b/tests/pixels/test_decoder_pyjpegls.py
@@ -61,7 +61,7 @@ class TestPyJpegLSDecoder:
         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 d378b1e2a..cbcf4d14e 100644
--- a/tests/pixels/test_decoder_pylibjpeg.py
+++ b/tests/pixels/test_decoder_pylibjpeg.py
@@ -155,7 +155,7 @@ class TestLibJpegDecoder:
         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 @@ class TestLibJpegDecoder:
         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 98b9f9b67..c249e3b19 100644
--- a/tests/pixels/test_encoder_pyjpegls.py
+++ b/tests/pixels/test_encoder_pyjpegls.py
@@ -43,7 +43,7 @@ class TestJpegLSLossless:
         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 @@ class TestJpegLSLossless:
         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 @@ class TestJpegLSLossless:
         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 @@ class TestJpegLSLossless:
         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 @@ class TestJpegLSLossless:
         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 @@ class TestJpegLSLossless:
             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 @@ class TestJpegLSLossless:
         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 @@ class TestJpegLSLossless:
         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 @@ class TestJpegLSLossless:
         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 @@ class TestJpegLSLossless:
             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 @@ class TestJpegLSNearLossless:
         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 @@ class TestJpegLSNearLossless:
         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 @@ class TestJpegLSNearLossless:
         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 @@ class TestJpegLSNearLossless:
         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 @@ class TestJpegLSNearLossless:
         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 @@ class TestJpegLSNearLossless:
             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 @@ class TestJpegLSNearLossless:
         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 @@ class TestJpegLSNearLossless:
         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 @@ class TestJpegLSNearLossless:
         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 @@ class TestJpegLSNearLossless:
             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 82e729503..485d5a7c9 100644
--- a/tests/pixels/test_encoder_pylibjpeg.py
+++ b/tests/pixels/test_encoder_pylibjpeg.py
@@ -117,7 +117,7 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
             )
 
             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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
             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 @@ class TestJ2KLosslessEncoding:
             )
 
             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 @@ class TestJ2KLosslessEncoding:
             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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
                 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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
         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 @@ class TestJ2KLosslessEncoding:
             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 @@ class TestJ2KLosslessEncoding:
             )
 
             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 @@ class TestJ2KLosslessEncoding:
             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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
             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 @@ class TestJ2KEncoding:
             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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
             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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
         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 @@ class TestJ2KEncoding:
             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 @@ class TestJ2KEncoding:
             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 @@ class TestJ2KEncoding:
             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 da4e89996..8faaf35ef 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 @@ class TestDownload:
             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 efd5ce403..e0034f2f4 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -1756,6 +1756,15 @@ class TestDataset:
         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):