2015-11-18 22:44:07 +00:00
import numpy . core . multiarray # important this comes before cv!
2016-09-21 19:54:04 +00:00
import ClientConstants as CC
2015-11-18 22:44:07 +00:00
import cv2
2017-08-23 21:34:25 +00:00
import HydrusConstants as HC
2018-10-03 21:00:15 +00:00
import HydrusData
2015-11-18 22:44:07 +00:00
import HydrusImageHandling
2017-05-10 21:33:58 +00:00
import HydrusGlobals as HG
2015-11-18 22:44:07 +00:00
if cv2 . __version__ . startswith ( ' 2 ' ) :
2017-08-23 21:34:25 +00:00
CV_IMREAD_FLAGS_SUPPORTS_ALPHA = cv2 . CV_LOAD_IMAGE_UNCHANGED
2017-08-30 20:27:47 +00:00
CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION = CV_IMREAD_FLAGS_SUPPORTS_ALPHA
# there's something wrong with these, but I don't have an easy test env for it atm
# CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION = cv2.CV_LOAD_IMAGE_ANYDEPTH | cv2.CV_LOAD_IMAGE_ANYCOLOR
2015-11-18 22:44:07 +00:00
2017-11-08 22:07:12 +00:00
CV_JPEG_THUMBNAIL_ENCODE_PARAMS = [ ]
CV_PNG_THUMBNAIL_ENCODE_PARAMS = [ ]
2015-11-18 22:44:07 +00:00
else :
2017-08-23 21:34:25 +00:00
CV_IMREAD_FLAGS_SUPPORTS_ALPHA = cv2 . IMREAD_UNCHANGED
CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION = cv2 . IMREAD_ANYDEPTH | cv2 . IMREAD_ANYCOLOR # this preserves colour info but does EXIF reorientation and flipping
2015-11-18 22:44:07 +00:00
2017-11-08 22:07:12 +00:00
CV_JPEG_THUMBNAIL_ENCODE_PARAMS = [ cv2 . IMWRITE_JPEG_QUALITY , 92 ]
CV_PNG_THUMBNAIL_ENCODE_PARAMS = [ cv2 . IMWRITE_PNG_COMPRESSION , 9 ]
2016-09-21 19:54:04 +00:00
cv_interpolation_enum_lookup = { }
cv_interpolation_enum_lookup [ CC . ZOOM_NEAREST ] = cv2 . INTER_NEAREST
cv_interpolation_enum_lookup [ CC . ZOOM_LINEAR ] = cv2 . INTER_LINEAR
cv_interpolation_enum_lookup [ CC . ZOOM_AREA ] = cv2 . INTER_AREA
cv_interpolation_enum_lookup [ CC . ZOOM_CUBIC ] = cv2 . INTER_CUBIC
cv_interpolation_enum_lookup [ CC . ZOOM_LANCZOS4 ] = cv2 . INTER_LANCZOS4
2017-11-08 22:07:12 +00:00
2015-11-18 22:44:07 +00:00
def EfficientlyResizeNumpyImage ( numpy_image , ( target_x , target_y ) ) :
( im_y , im_x , depth ) = numpy_image . shape
2018-03-22 00:03:33 +00:00
if target_x > = im_x and target_y > = im_y :
return numpy_image
2015-11-18 22:44:07 +00:00
# this seems to slow things down a lot, at least for cv!
#if im_x > 2 * target_x and im_y > 2 * target_y: result = cv2.resize( numpy_image, ( 2 * target_x, 2 * target_y ), interpolation = cv2.INTER_NEAREST )
2016-04-14 01:54:29 +00:00
return cv2 . resize ( numpy_image , ( target_x , target_y ) , interpolation = cv2 . INTER_AREA )
2015-11-18 22:44:07 +00:00
def EfficientlyThumbnailNumpyImage ( numpy_image , ( target_x , target_y ) ) :
( im_y , im_x , depth ) = numpy_image . shape
2018-03-22 00:03:33 +00:00
if target_x > = im_x and target_y > = im_y :
return numpy_image
2015-11-18 22:44:07 +00:00
( target_x , target_y ) = HydrusImageHandling . GetThumbnailResolution ( ( im_x , im_y ) , ( target_x , target_y ) )
return cv2 . resize ( numpy_image , ( target_x , target_y ) , interpolation = cv2 . INTER_AREA )
2017-08-23 21:34:25 +00:00
def GenerateNumpyImage ( path , mime ) :
2015-11-18 22:44:07 +00:00
2018-10-03 21:00:15 +00:00
if HG . media_load_report_mode :
HydrusData . ShowText ( ' Loading media: ' + path )
2017-12-06 22:06:56 +00:00
if mime == HC . IMAGE_GIF or HG . client_controller . new_options . GetBoolean ( ' load_images_with_pil ' ) :
2016-09-21 19:54:04 +00:00
2018-10-03 21:00:15 +00:00
if HG . media_load_report_mode :
HydrusData . ShowText ( ' Loading with PIL ' )
2016-09-21 19:54:04 +00:00
# a regular cv.imread call, can crash the whole process on random thumbs, hooray, so have this as backup
# it was just the read that was the problem, so this seems to work fine, even if pil is only about half as fast
pil_image = HydrusImageHandling . GeneratePILImage ( path )
numpy_image = GenerateNumPyImageFromPILImage ( pil_image )
else :
2018-10-03 21:00:15 +00:00
if HG . media_load_report_mode :
HydrusData . ShowText ( ' Loading with OpenCV ' )
2017-08-23 21:34:25 +00:00
if mime == HC . IMAGE_JPEG :
flags = CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION
else :
flags = CV_IMREAD_FLAGS_SUPPORTS_ALPHA
numpy_image = cv2 . imread ( path , flags = flags )
2016-09-21 19:54:04 +00:00
if numpy_image is None : # doesn't support static gifs and some random other stuff
2018-10-03 21:00:15 +00:00
if HG . media_load_report_mode :
HydrusData . ShowText ( ' OpenCV Failed, loading with PIL ' )
2016-09-21 19:54:04 +00:00
pil_image = HydrusImageHandling . GeneratePILImage ( path )
numpy_image = GenerateNumPyImageFromPILImage ( pil_image )
else :
2016-11-23 20:37:53 +00:00
if numpy_image . dtype == ' uint16 ' :
numpy_image / = 256
numpy_image = numpy . array ( numpy_image , dtype = ' uint8 ' )
2016-09-28 18:48:01 +00:00
shape = numpy_image . shape
2016-09-21 19:54:04 +00:00
2016-09-28 18:48:01 +00:00
if len ( shape ) == 2 :
2016-09-21 19:54:04 +00:00
2016-09-28 18:48:01 +00:00
# monochrome image
convert = cv2 . COLOR_GRAY2RGB
2016-09-21 19:54:04 +00:00
else :
2016-09-28 18:48:01 +00:00
( im_y , im_x , depth ) = shape
if depth == 4 :
convert = cv2 . COLOR_BGRA2RGBA
else :
convert = cv2 . COLOR_BGR2RGB
2016-09-21 19:54:04 +00:00
numpy_image = cv2 . cvtColor ( numpy_image , convert )
2015-11-18 22:44:07 +00:00
return numpy_image
def GenerateNumPyImageFromPILImage ( pil_image ) :
2016-06-29 19:55:46 +00:00
pil_image = HydrusImageHandling . Dequantize ( pil_image )
2015-11-18 22:44:07 +00:00
( w , h ) = pil_image . size
s = pil_image . tobytes ( )
return numpy . fromstring ( s , dtype = ' uint8 ' ) . reshape ( ( h , w , len ( s ) / / ( w * h ) ) )
2017-08-23 21:34:25 +00:00
def GenerateShapePerceptualHashes ( path , mime ) :
2015-11-18 22:44:07 +00:00
2017-08-23 21:34:25 +00:00
numpy_image = GenerateNumpyImage ( path , mime )
2015-11-18 22:44:07 +00:00
( y , x , depth ) = numpy_image . shape
if depth == 4 :
2017-05-10 21:33:58 +00:00
# doing this on 10000x10000 pngs eats ram like mad
numpy_image = EfficientlyThumbnailNumpyImage ( numpy_image , ( 1024 , 1024 ) )
( y , x , depth ) = numpy_image . shape
2015-11-18 22:44:07 +00:00
# create weight and transform numpy_image to greyscale
numpy_alpha = numpy_image [ : , : , 3 ]
2016-08-24 18:36:56 +00:00
numpy_alpha_float = numpy_alpha / 255.0
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
numpy_image_bgr = numpy_image [ : , : , : 3 ]
2015-11-18 22:44:07 +00:00
2016-11-16 20:21:43 +00:00
numpy_image_gray_bare = cv2 . cvtColor ( numpy_image_bgr , cv2 . COLOR_RGB2GRAY )
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
# create a white greyscale canvas
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
white = numpy . ones ( ( y , x ) ) * 255.0
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
# paste the grayscale image onto the white canvas using: pixel * alpha + white * ( 1 - alpha )
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
numpy_image_gray = numpy . uint8 ( ( numpy_image_gray_bare * numpy_alpha_float ) + ( white * ( numpy . ones ( ( y , x ) ) - numpy_alpha_float ) ) )
2015-11-18 22:44:07 +00:00
else :
2016-11-16 20:21:43 +00:00
numpy_image_gray = cv2 . cvtColor ( numpy_image , cv2 . COLOR_RGB2GRAY )
2015-11-18 22:44:07 +00:00
numpy_image_tiny = cv2 . resize ( numpy_image_gray , ( 32 , 32 ) , interpolation = cv2 . INTER_AREA )
# convert to float and calc dct
numpy_image_tiny_float = numpy . float32 ( numpy_image_tiny )
dct = cv2 . dct ( numpy_image_tiny_float )
# take top left 8x8 of dct
dct_88 = dct [ : 8 , : 8 ]
2017-01-18 22:52:39 +00:00
# get median of dct
# exclude [0,0], which represents flat colour
# this [0,0] exclusion is apparently important for mean, but maybe it ain't so important for median--w/e
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
# old mean code
# mask = numpy.ones( ( 8, 8 ) )
# mask[0,0] = 0
# average = numpy.average( dct_88, weights = mask )
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
median = numpy . median ( dct_88 . reshape ( 64 ) [ 1 : ] )
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
# make a monochromatic, 64-bit hash of whether the entry is above or below the median
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
dct_88_boolean = dct_88 > median
# convert TTTFTFTF to 11101010 by repeatedly shifting answer and adding 0 or 1
2017-03-08 23:23:12 +00:00
# you can even go ( a << 1 ) + b and leave out the initial param on the reduce call as bools act like ints for this
2017-01-18 22:52:39 +00:00
# but let's not go crazy for another two nanoseconds
2017-03-08 23:23:12 +00:00
def collapse_bools_to_binary_uint ( a , b ) :
return ( a << 1 ) + int ( b )
2015-11-18 22:44:07 +00:00
bytes = [ ]
for i in range ( 8 ) :
2017-01-18 22:52:39 +00:00
'''
# old way of doing it, which compared value to median every time
2015-11-18 22:44:07 +00:00
byte = 0
for j in range ( 8 ) :
byte << = 1 # shift byte one left
value = dct_88 [ i , j ]
2017-01-18 22:52:39 +00:00
if value > median :
byte | = 1
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
'''
byte = reduce ( collapse_bools_to_binary_uint , dct_88_boolean [ i ] , 0 )
2015-11-18 22:44:07 +00:00
bytes . append ( byte )
2016-11-30 20:24:17 +00:00
phash = str ( bytearray ( bytes ) )
2017-01-18 22:52:39 +00:00
# now discard the blank hash, which is 1000000... and not useful
phashes = set ( )
phashes . add ( phash )
phashes . discard ( CC . BLANK_PHASH )
2015-11-18 22:44:07 +00:00
# we good
2016-11-30 20:24:17 +00:00
return phashes
2016-09-21 19:54:04 +00:00
2017-11-15 22:35:49 +00:00
def GenerateThumbnailFromStaticImageCV ( path , dimensions = HC . UNSCALED_THUMBNAIL_DIMENSIONS , mime = None ) :
2017-11-08 22:07:12 +00:00
2017-11-15 22:35:49 +00:00
if mime is None :
mime = HydrusFileHandling . GetMime ( path )
if mime == HC . IMAGE_GIF :
return HydrusFileHandling . GenerateThumbnailFromStaticImagePIL ( path , dimensions , mime )
2017-11-08 22:07:12 +00:00
numpy_image = GenerateNumpyImage ( path , mime )
thumbnail_numpy_image = EfficientlyThumbnailNumpyImage ( numpy_image , dimensions )
( im_y , im_x , depth ) = thumbnail_numpy_image . shape
if depth == 4 :
convert = cv2 . COLOR_RGBA2BGRA
else :
convert = cv2 . COLOR_RGB2BGR
thumbnail_numpy_image = cv2 . cvtColor ( thumbnail_numpy_image , convert )
if mime == HC . IMAGE_JPEG :
ext = ' .jpg '
params = CV_JPEG_THUMBNAIL_ENCODE_PARAMS
else :
ext = ' .png '
params = CV_PNG_THUMBNAIL_ENCODE_PARAMS
( result_success , result_byte_array ) = cv2 . imencode ( ext , thumbnail_numpy_image , params )
if result_success :
thumbnail = result_byte_array . tostring ( )
return thumbnail
else :
2017-11-15 22:35:49 +00:00
return HydrusFileHandling . GenerateThumbnailFromStaticImagePIL ( path , dimensions , mime )
2017-11-08 22:07:12 +00:00
import HydrusFileHandling
HydrusFileHandling . GenerateThumbnailFromStaticImage = GenerateThumbnailFromStaticImageCV
2016-09-21 19:54:04 +00:00
def ResizeNumpyImage ( mime , numpy_image , ( target_x , target_y ) ) :
2017-12-06 22:06:56 +00:00
new_options = HG . client_controller . new_options
2016-09-21 19:54:04 +00:00
( scale_up_quality , scale_down_quality ) = new_options . GetMediaZoomQuality ( mime )
( im_y , im_x , depth ) = numpy_image . shape
if ( target_x , target_y ) == ( im_x , im_y ) :
return numpy_image
else :
if target_x > im_x or target_y > im_y :
interpolation = cv_interpolation_enum_lookup [ scale_up_quality ]
else :
interpolation = cv_interpolation_enum_lookup [ scale_down_quality ]
return cv2 . resize ( numpy_image , ( target_x , target_y ) , interpolation = interpolation )
2017-01-18 22:52:39 +00:00