-
-
Notifications
You must be signed in to change notification settings - Fork 471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"underlying array is read-only" while modifying pixel value in pydicom version 1.1.0 #717
Comments
Reproducible with current master (87b40f8) and python 2.7/3.6, numpy 1.14.5/1.14.3. >>> from pydicom import dcmread
>>> ds = dcmread('CT_small.dcm')
>>> arr = ds.pixel_array
>>> arr[0, 4]
139
>>> arr[0, 4] = 12
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: assignment destination is read-only
>>> arr.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : False
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
|
We use >>> import numpy as np
>>> np.frombuffer(b'\x00\x01', dtype='uint8').flags
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : False
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
>>> np.fromstring(b'\x00\x01', dtype='uint8').flags
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
>>> np.frombuffer(bytearray(b'\x00\x00'), dtype='uint8').flags
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False v0.9.9 used |
Oh yikes (remember that in pydicom >>> original = b'\x00\x01'
>>> arr = np.frombuffer(original, dtype='uint8')
>>> arr.setflags(write=1)
>>> arr[0] = 12
>>> original
b'\x0c\x01' I think we should change back to |
@xulunk - if you want to modify the returned pixel array, you have to copy it first using @scaramallion - this has been changed because |
Ah, that makes sense. I think its better to return a copy given in-place changes result in PixelData being changed as well. |
I don't see that as a problem. No one can use PixelData directly, it makes no sense on its own as 1d bytes. Copying the array is really not reasonable for very large images, which is a use case that many people do have. I think we need to avoid making copies. |
@scaramallion @mrbean-bremen @darcymason 👍
|
@xulunk - glad to hear you got it to work. |
@darcymason @scaramallion - do we have a decision for this one? Leave it as is? |
I think making the buffer writable restores previous behaviour. It does mean that PixelData is modified in-place, but as I said before, I don't see that as a problem - I don't see why anyone would be directly manipulating PixelData's 1-d array (except to write to it with Edit: we should still of course warn about this change in behaviour. |
So, setting |
Yes, that's what I think is the best way forward. I just realized too that a test for this is very important -- it should have been caught before 1.1 went out. And yes, IMO it's very important for 1.2 because it "broke" 1.1. |
There's a fair bit of inconsistency in that some of the handlers retain a link to the buffer and some don't. The numpy handler will sometimes return a copy (as with bit packed data) and sometimes not (may also depend on the exact parameters of the data). I don't know for sure about the other handlers, but given they decompress data they're almost certainly not linked to the original buffer. I'd prefer it if the behaviour was more predictable (either in-place modification never affects Pixel Data or always does), but I don't think the 'always affects Pixel Data' option is even possible. Also, as long as only the view (i.e. a reshape/transpose/dtype) on the read-only 1D array is changed (and I believe even then some particular view operations will return a copy instead), the returned ndarray will be read-only. |
@scaramallion - so what do you propose? Return a copy in the cases where the original pixel data is affected, and make the array writable otherwise? |
Returning a copy for the numpy handler would make it consistent with the others, which should all be writable. I guess its up to @darcymason to decide. I'll add some tests to try and nail down the circumstances where the returned array is read-only. |
Right, so:
The single frame JPEG-LS and the GDCM handlers return read-only arrays because the decompressed output from gdcm and jpeg_ls are turned into a numpy array from a bytes, they're disconnected from the original PixelData memory. The multi-frame JPEG-LS arrays use bytearray instead and hence are writeable. |
So if I understand that correctly, the returned arrays for the GDCM and JPEG-LS handlers could be made writable without problems, and the question is about the NumPy handler with the alternatives:
|
To step back from detail for a moment, I'd like pixel data to work exactly like this 1-D array example: >>> # load CT_small.dcm from testdata...
>>> pos = ds.ImagePositionPatient
>>> pos
['-158.135803', '-179.035797', '-75.699997']
>>> pos[0] = 0
>>> ds.ImagePositionPatient
['0.0', '-179.035797', '-75.699997'] The fully converted data element value should be a reference to a (mutable) numpy array. That's what we have given in the past for
Yes, if necessary. We used
Yes. But I'd be interested to hear specific code this would break. If there are common cases, we could re-think this.
I don't like this option. Consistency is important - I think it would be very confusing to users to have to copy pixel data themselves as a normal operation. And I can't help but put in a plug again for what I have argued before: this confusion all goes away if we indeed treat |
Agreed. I put this here to have all options listed (at least which I see), but I would also tend to option 1 or 2. I don't know of the specific use cases, so I have no strong opinion here.
This sounds very sensible to me - though there has been some arguing against that, IIRC. This is something that can be discussed again after the release, and it may not be that long term in my opinion, but that remains to be discussed. |
I'm in favour of the first option. For the numpy handler we could add a keyword argument to # Default, returns mutable array
arr = get_pixeldata(ds, copy=True)
# Returns immutable array linked to PixelData (if not bit-packed)
arr = get_pixeldata(ds, copy=False) I also agree with eventually deprecating |
+1 |
Can I suggest |
Ok, sounds good. I'll take care of this |
I tried to run a demo about reading dicom file, modify the pixel value and write new pixel data to another dicom file, however it failed in pydicom version 1.1.0 while works well for version 0.9.9-1.
In order to modify the pixel data in version 1.1.0 or later, what modification should I do?
Looking forward to your reply, thanks.
p.s. in order to run version 0.9.9-1, I just changed the module name from pydicom to dicom as recommended.
Description
Steps/Code to Reproduce
Expected Results
IMG_NEW.DCM is written successfully
Actual Results
Traceback (most recent call last):
File "readDicom.py", line 34, in
ds.pixel_array.flat[n] = 0
ValueError: underlying array is read-only
Versions
python 3.6.3
pydicom 1.1.0
The text was updated successfully, but these errors were encountered: