photoprism / photoprism

AI-Powered Photos App for the Decentralized Web 🌈💎✨
https://www.photoprism.app
Other
34.94k stars 1.92k forks source link

RAW: `.NEF` files have wrong resolution information #2447

Closed Rolf-Smit closed 2 years ago

Rolf-Smit commented 2 years ago

I noticed that some of my NEF files (Nikon Raw) where being converted into super small sidecar images 160x120. Then I noticed that in the files section the RAW file itself was reporting 160x120 as a resolution as well. Switching to RawTherapee solved (see #2446) the sidecar resolution size, but the reported RAW resolution is still 160x120 (I assume RawTherapee and Darktable are not used to read metadata?).

Screenshot 2022-06-19 at 10 20 37

Result: Resolution shows as 160x120 while it should show as 4608x3072

Expectation: Resolution of RAW file should show as 4608x3072

Reproduce:

I've attached a sample image (in a .zip file, otherwise GitHub does not allow me to upload it). I hereby give permission to use this image and include it in the sample library if needed.

DSC_7534.NEF.zip

lastzero commented 2 years ago

Thanks for reporting this and adding examples! We use a generic Exif extractor for RAW files, which often include a thumbnail. Probably 160x120 is the resolution of this JPEG thumbnail, while the full RAW file may not conform to the Exif usage in JPEG.

lastzero commented 2 years ago

The output of exif-read-tool -f DSC_7534.NEF does not seem to contain any further information about the image size, presumably because the thumbnail is embedded in the file first and may have separate Exif headers, unlike in other common RAW formats:

IFD-PATH=[IFD] ID=(0x00fe) NAME=[NewSubfileType] COUNT=(1) TYPE=[LONG] VALUE=[[1]]
IFD-PATH=[IFD] ID=(0x0100) NAME=[ImageWidth] COUNT=(1) TYPE=[LONG] VALUE=[[160]]
IFD-PATH=[IFD] ID=(0x0101) NAME=[ImageLength] COUNT=(1) TYPE=[LONG] VALUE=[[120]]
IFD-PATH=[IFD] ID=(0x0102) NAME=[BitsPerSample] COUNT=(3) TYPE=[SHORT] VALUE=[[8 8 8]]
IFD-PATH=[IFD] ID=(0x0103) NAME=[Compression] COUNT=(1) TYPE=[SHORT] VALUE=[[1]]
IFD-PATH=[IFD] ID=(0x0106) NAME=[PhotometricInterpretation] COUNT=(1) TYPE=[SHORT] VALUE=[[2]]
IFD-PATH=[IFD] ID=(0x010f) NAME=[Make] COUNT=(18) TYPE=[ASCII] VALUE=[NIKON CORPORATION]
IFD-PATH=[IFD] ID=(0x0110) NAME=[Model] COUNT=(12) TYPE=[ASCII] VALUE=[NIKON D3100]
IFD-PATH=[IFD] ID=(0x0111) NAME=[StripOffsets] COUNT=(1) TYPE=[LONG] VALUE=[[115156]]
IFD-PATH=[IFD] ID=(0x0112) NAME=[Orientation] COUNT=(1) TYPE=[SHORT] VALUE=[[1]]
IFD-PATH=[IFD] ID=(0x0115) NAME=[SamplesPerPixel] COUNT=(1) TYPE=[SHORT] VALUE=[[3]]
IFD-PATH=[IFD] ID=(0x0116) NAME=[RowsPerStrip] COUNT=(1) TYPE=[LONG] VALUE=[[120]]
IFD-PATH=[IFD] ID=(0x0117) NAME=[StripByteCounts] COUNT=(1) TYPE=[LONG] VALUE=[[57600]]
IFD-PATH=[IFD] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[[300/1]]
IFD-PATH=[IFD] ID=(0x011b) NAME=[YResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[[300/1]]
IFD-PATH=[IFD] ID=(0x011c) NAME=[PlanarConfiguration] COUNT=(1) TYPE=[SHORT] VALUE=[[1]]
IFD-PATH=[IFD] ID=(0x0128) NAME=[ResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[[2]]
IFD-PATH=[IFD] ID=(0x0131) NAME=[Software] COUNT=(10) TYPE=[ASCII] VALUE=[Ver.1.01 ]
IFD-PATH=[IFD] ID=(0x0132) NAME=[DateTime] COUNT=(20) TYPE=[ASCII] VALUE=[2015:08:10 14:05:19]
IFD-PATH=[IFD] ID=(0x014a) NAME=[SubIFDs] COUNT=(2) TYPE=[LONG] VALUE=[[172756 172876]]
IFD-PATH=[IFD] ID=(0x0214) NAME=[ReferenceBlackWhite] COUNT=(6) TYPE=[RATIONAL] VALUE=[[0/1 255/1 0/1 255/1 0/1 255/1]]
IFD-PATH=[IFD] ID=(0x8769) NAME=[ExifTag] COUNT=(1) TYPE=[LONG] VALUE=[[480]]
IFD-PATH=[IFD/Exif] ID=(0x829a) NAME=[ExposureTime] COUNT=(1) TYPE=[RATIONAL] VALUE=[[10/5000]]
IFD-PATH=[IFD/Exif] ID=(0x829d) NAME=[FNumber] COUNT=(1) TYPE=[RATIONAL] VALUE=[[110/10]]
IFD-PATH=[IFD/Exif] ID=(0x8822) NAME=[ExposureProgram] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD/Exif] ID=(0x8827) NAME=[ISOSpeedRatings] COUNT=(1) TYPE=[SHORT] VALUE=[[400]]
IFD-PATH=[IFD/Exif] ID=(0x9003) NAME=[DateTimeOriginal] COUNT=(20) TYPE=[ASCII] VALUE=[2015:08:10 14:05:19]
IFD-PATH=[IFD/Exif] ID=(0x9004) NAME=[DateTimeDigitized] COUNT=(20) TYPE=[ASCII] VALUE=[2015:08:10 14:05:19]
IFD-PATH=[IFD/Exif] ID=(0x9204) NAME=[ExposureBiasValue] COUNT=(1) TYPE=[SRATIONAL] VALUE=[[0/6]]
IFD-PATH=[IFD/Exif] ID=(0x9205) NAME=[MaxApertureValue] COUNT=(1) TYPE=[RATIONAL] VALUE=[[44/10]]
IFD-PATH=[IFD/Exif] ID=(0x9207) NAME=[MeteringMode] COUNT=(1) TYPE=[SHORT] VALUE=[[5]]
IFD-PATH=[IFD/Exif] ID=(0x9208) NAME=[LightSource] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD/Exif] ID=(0x9209) NAME=[Flash] COUNT=(1) TYPE=[SHORT] VALUE=[[16]]
IFD-PATH=[IFD/Exif] ID=(0x920a) NAME=[FocalLength] COUNT=(1) TYPE=[RATIONAL] VALUE=[[300/10]]
IFD-PATH=[IFD/Exif] ID=(0x927c) NAME=[MakerNote] COUNT=(114126) TYPE=[UNDEFINED] VALUE=[MakerNote<TYPE-ID=[4e 69 6b 6f 6e 00 02 10 00 00 4d 4d 00 2a 00 00 00 08 00 39] LEN=(114126) SHA1=[a6e76bfe41046498665c00cc0b37b467c75c1315]>]
IFD-PATH=[IFD/Exif] ID=(0x9286) NAME=[UserComment] COUNT=(44) TYPE=[UNDEFINED] VALUE=[[ASCII]                                     ]
IFD-PATH=[IFD/Exif] ID=(0x9290) NAME=[SubSecTime] COUNT=(3) TYPE=[ASCII] VALUE=[70]
IFD-PATH=[IFD/Exif] ID=(0x9291) NAME=[SubSecTimeOriginal] COUNT=(3) TYPE=[ASCII] VALUE=[70]
IFD-PATH=[IFD/Exif] ID=(0x9292) NAME=[SubSecTimeDigitized] COUNT=(3) TYPE=[ASCII] VALUE=[70]
IFD-PATH=[IFD/Exif] ID=(0xa217) NAME=[SensingMethod] COUNT=(1) TYPE=[SHORT] VALUE=[[2]]
IFD-PATH=[IFD/Exif] ID=(0xa300) NAME=[FileSource] COUNT=(1) TYPE=[UNDEFINED] VALUE=[0x03000000]
IFD-PATH=[IFD/Exif] ID=(0xa301) NAME=[SceneType] COUNT=(1) TYPE=[UNDEFINED] VALUE=[0x01000000]
IFD-PATH=[IFD/Exif] ID=(0xa302) NAME=[CFAPattern] COUNT=(8) TYPE=[UNDEFINED] VALUE=[TagA302CfaPattern<HORZ-REPEAT=(2) VERT-REPEAT=(2) CFA-VALUE=(4)>]
IFD-PATH=[IFD/Exif] ID=(0xa401) NAME=[CustomRendered] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD/Exif] ID=(0xa402) NAME=[ExposureMode] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD/Exif] ID=(0xa403) NAME=[WhiteBalance] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD/Exif] ID=(0xa404) NAME=[DigitalZoomRatio] COUNT=(1) TYPE=[RATIONAL] VALUE=[[1/1]]
IFD-PATH=[IFD/Exif] ID=(0xa405) NAME=[FocalLengthIn35mmFilm] COUNT=(1) TYPE=[SHORT] VALUE=[[45]]
IFD-PATH=[IFD/Exif] ID=(0xa406) NAME=[SceneCaptureType] COUNT=(1) TYPE=[SHORT] VALUE=[[1]]
IFD-PATH=[IFD/Exif] ID=(0xa407) NAME=[GainControl] COUNT=(1) TYPE=[SHORT] VALUE=[[1]]
IFD-PATH=[IFD/Exif] ID=(0xa408) NAME=[Contrast] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD/Exif] ID=(0xa409) NAME=[Saturation] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD/Exif] ID=(0xa40a) NAME=[Sharpness] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD/Exif] ID=(0xa40c) NAME=[SubjectDistanceRange] COUNT=(1) TYPE=[SHORT] VALUE=[[0]]
IFD-PATH=[IFD] ID=(0x8825) NAME=[GPSTag] COUNT=(1) TYPE=[LONG] VALUE=[[115138]]
IFD-PATH=[IFD/GPSInfo] ID=(0x0000) NAME=[GPSVersionID] COUNT=(4) TYPE=[BYTE] VALUE=[02 02 00 00]
IFD-PATH=[IFD] ID=(0x9003) NAME=[DateTimeOriginal] COUNT=(20) TYPE=[ASCII] VALUE=[2015:08:10 14:05:19]
IFD-PATH=[IFD] ID=(0x9216) NAME=[TIFFEPStandardID] COUNT=(4) TYPE=[BYTE] VALUE=[01 00 00 00]

The information returned by exiftool should then be preferred as it appears to be correct. There is no need to use Darktable or any other external app to extract metadata. It would also be very slow. Converters are generally not intended for this purpose.

@Rolf-Smit :

Rolf-Smit commented 2 years ago

@lastzero thanks for the explanation.

Rolf-Smit commented 2 years ago

This is the output I see when running exiftool -H DSC_7534.NEF

Screenshot 2022-06-19 at 18 04 20

Or better:

Screenshot 2022-06-19 at 18 07 41

Are those Image Width and Height fields non-standard? Nikon specific?

lastzero commented 2 years ago

As written above, exiftool also shows the correct size for me. We have friends over and it is Sunday, so I cannot check at the moment what the reason is. Maybe it has a special parser for this RAW format or uses other heuristics.

For clarification, exif-read-tool is the CLI tool that belongs to the Exif library we use internally. It is implemented in Go and does not require C libraries or Perl to run exiftool, which may not be acceptable for some use cases, depending on your performance and security requirements.

Rolf-Smit commented 2 years ago

@lastzero no problem, there is absolutely no reason for you to work on this today, or to work on this at all. It is your software after all 🙂

Anyhow if you find time: is it exiftool (by phil harvey) that is used by PhotoPrism, or does PhotoPrism use a Go package or some other exif tool, that is what I don't understand currently.

lastzero commented 2 years ago

We use both plus additional sources for gathering metadata like XMP and Google Photos JSON. Data is extracted in a deterministic order, first using the internal library, then exiftool, and so on. This generally works well except in rare cases where the initial information is incorrect such as apparently in this case. This is great to know, so thanks for your report! Should I learn more about what is causing this, I'll let you know.

lastzero commented 2 years ago

I was not able to find anything that should cause Darktable to reduce the image size. The darktable-cli parameters are:

--width [maxSize] --height [maxSize] --hq true --upscale false

maxSize only depends on the config, not on the metadata of the RAW file. So MAYBE Darktable runs into the same issue when reading the size from the Exif headers. Could you verify this?

lastzero commented 2 years ago

I found the following remarks in the Exiftool forum at https://exiftool.org/forum/index.php?topic=7705.0:

The ExifTool "ImageSize" tag is a Composite tag based on ImageWidth and ImageHeight. None of these are writable because they represent the actual dimensions of the image, and ExifTool can't be used to edit the image.

To me it reads like ImageWidth and ImageHeight are NOT metadata, but the actual resolution based on the binary image data. That would explain why our exif header parser doesn't find them.

lastzero commented 2 years ago

These changes fixed it for me:

fixed

I'll merge this to preview and start a new build for testing :+1:

Rolf-Smit commented 2 years ago

Nice thanks! Currently on vacation in I presume your home country, so I will test this whenever I find some free WiFi.

Rolf-Smit commented 2 years ago

@lastzero I tested this and it seems to work fine now!

I had to remove any (from NEF) generated sidecar files and had to use the "Complete Rescan" check-mark in order for the sidecar files to be generated again.

There is also no need anymore to switch to RawTherapee, before I tried all this I put the settings so that Darktable would be used, and I don't see any problems.