def	image_normalize(input_image, warning=True, debug=True):
	'''
	normalize an image to an uint8 with range of [0, 255]
	note that: the input might not be an image because the value range might be arbitrary

	parameters:
		input_image:		pil image or image-like array, color or gray, float or uint

	outputs:
		np_image:			numpy uint8 image, normalized to [0, 255]
	'''
	np_image, isnan = safe_image_like(input_image, warning=warning, debug=debug)
	if isuintnparray(np_image): np_image = np_image.astype('float32') / 255.		
	else: assert isfloatnparray(np_image), 'the input image-like array should be either an uint8 or float32 array' 

	min_val = np.min(np_image)
	max_val = np.max(np_image)
	if isnan: np_image.fill(0)
	elif min_val == max_val:								# all same
		if warning:
			print('the input image has the same value over all the pixels')
		np_image.fill(0)
	else:													# normal case
		np_image -= min_val
		np_image = ((np_image / (max_val - min_val)) * 255.).astype('uint8')
		if debug: assert np.min(np_image) == 0 and np.max(np_image) == 255, 'the value range is not right [%f, %f]' % (np.min(np_image), np.max(np_image))

	return np_image.astype('uint8')
def preprocess_batch_deep_image(input_image, pixel_mean=None, pixel_std=None, rgb2bgr=True, warning=True, debug=True):
	'''
	this function preprocesses batch of images to a deep image, 
	1: from rgb to bgr
	2: normalize the batch image based on mean and std
	3: convert (N)HWC to NCHW

	parameters:
		input_image:			NHWC numpy color image, where C is 3, uint8 or float32
		pixel_mean:				a float32 numpy array mean over 3 channels with shape of (3, ) or (1, )
		pixel_std:				a float32 numpy array std over 3 channels with shape of (3, ) or (1, )
		rgb2bgr:				true if the input image is rgb format, such that the output is bgr image

	outputs:
		np_image: 				NCHW float32 color numpy image, bgr format
	'''
	np_image, isnan = safe_batch_image(input_image, warning=warning, debug=debug)
	if debug: assert np_image.shape[-1] == 3, 'the input image should be a color image'
	if isuintnparray(np_image): np_image = np_image.astype('float32') / 255.		
	else: assert isfloatnparray(np_image), 'the input image-like array should be either an uint8 or float32 array' 
	if rgb2bgr: np_image = np_image[:, :, :, [2, 1, 0]]                 	# from rgb to bgr, currently NHWC

	# normalize the numpy image data
	if pixel_mean is not None:
		pixel_mean = safe_npdata(pixel_mean, warning=warning, debug=debug)
		if debug: 
			assert pixel_mean.shape == (3, ) or pixel_mean.shape == (1, ), 'pixel mean is not correct'
			assert (pixel_mean <= 1.0).all() and (pixel_mean >= 0.0).all(), 'mean value should be in range [0, 1].'
		
		if pixel_mean.shape == (3, ): pixel_mean_reshape = np.reshape(pixel_mean, (1, 1, 1, 3))
		elif pixel_mean.shape == (1, ): pixel_mean_reshape = np.reshape(np.repeat(pixel_mean, 3), (1, 1, 1, 3))
		else: assert False, 'pixel mean is not correct'
		np_image -= pixel_mean_reshape

	if pixel_std is not None:
		pixel_std = safe_npdata(pixel_std, warning=warning, debug=debug)
		if debug: 
			assert pixel_std.shape == (3, ) or pixel_std.shape == (1, ), 'pixel std is not correct'
			assert (pixel_std <= 1.0).all() and (pixel_std >= 0.0).all(), 'std value should be in range [0, 1].'
		if pixel_std.shape == (3, ): pixel_std_reshape = np.reshape(pixel_std, (1, 1, 1, 3))
		elif pixel_std.shape == (1, ): pixel_std_reshape = np.reshape(np.repeat(pixel_std, 3), (1, 1, 1, 3))
		else: assert False, 'pixel std is not correct'
		np_image /= pixel_std_reshape

	np_image = np.transpose(np_image, (0, 3, 1, 2))         				# NHWC to NCHW

	return np_image
def image_mean(input_image, warning=True, debug=True):
	'''
	this function computes the mean image over batch of images

	parameters:
		input_image: 		NHWC numpy image, uint8 or float32

	outputs:
		mean_image:			HWC numpy image, uint8	
	'''
	np_image, isnan = safe_batch_image(input_image, warning=warning, debug=debug)
	if isuintnparray(np_image): np_image = np_image.astype('float32') / 255.		
	else: assert isfloatnparray(np_image), 'the input image-like array should be either an uint8 or float32 array' 

	mean_image = (np.mean(np_image, axis=0) * 255.).astype('uint8')

	return mean_image
def unpreprocess_batch_deep_image(input_image, pixel_mean=None, pixel_std=None, bgr2rgb=True, warning=True, debug=True):
	'''
	this function unpreprocesses batch of deep images, which uses chw format instead of hwc format in general
	1: un-normalize the batch image based on mean and std
	2: convert NCHW to NHWC
	3. from bgr to rgb

	parameters:
		input_image:			NCHW / CHW float32 numpy array, where C is 3
		pixel_mean:				a float32 numpy array mean over 3 channels with shape of (3, ) or (1, )
		pixel_std:				a float32 numpy array std over 3 channels with shape of (3, ) or (1, )
		bgr2rgb:				true if the input image is bgr format, such that the output is rgb image

	outputs:
		np_image: 				NHWC uint8 color numpy image, rgb format
	'''
	np_image, isnan = safe_batch_deep_image(input_image, warning=warning, debug=debug)
	if debug: assert isfloatnparray(np_image), 'the input image-like array should be either an uint8 or float32 array' 

	if pixel_std is not None:
		pixel_std = safe_npdata(pixel_std, warning=warning, debug=debug)
		if debug: 
			assert pixel_std.shape == (3, ) or pixel_std.shape == (1, ), 'pixel std is not correct'
			assert (pixel_std <= 1.0).all() and (pixel_std >= 0.0).all(), 'std value should be in range [0, 1].'
		if pixel_std.shape == (3, ): pixel_std_reshape = np.reshape(pixel_std, (1, 3, 1, 1))
		elif pixel_std.shape == (1, ): pixel_std_reshape = np.reshape(np.repeat(pixel_std, 3), (1, 3, 1, 1))
		else: assert False, 'pixel std is not correct'
		np_image *= pixel_std_reshape

	if pixel_mean is not None:
		pixel_mean = safe_npdata(pixel_mean, warning=warning, debug=debug)
		if debug: 
			assert pixel_mean.shape == (3, ) or pixel_mean.shape == (1, ), 'pixel mean is not correct'
			assert (pixel_mean <= 1.0).all() and (pixel_mean >= 0.0).all(), 'mean value should be in range [0, 1].'
		if pixel_mean.shape == (3, ): pixel_mean_reshape = np.reshape(pixel_mean, (1, 3, 1, 1))
		elif pixel_mean.shape == (1, ): pixel_mean_reshape = np.reshape(np.repeat(pixel_mean, 3), (1, 3, 1, 1))
		else: assert False, 'pixel mean is not correct'
		np_image += pixel_mean_reshape

	np_image = np.transpose(np_image, (0, 2, 3, 1))         			# permute to [batch, height, weight, channel]	
	if bgr2rgb:	np_image = np_image[:, :, :, [2, 1, 0]]             	# from bgr to rgb for color image
	np_image = (np.clip(np_image, 0.0, 1.0) * 255.).astype('uint8')

	return np_image