deprecated image preprocessing apis from keras 1 import collections import multiprocessing import os import threading import warnings import numpy as np from keras import backend from keras apiexport import kerasexport from keras trainers dataadapters pydatasetadapter import pydataset from keras utils import imageutils from keras utils import ioutils from keras utils moduleutils import scipy kerasexportkeras legacy preprocessing image iterator class iteratorpydataset whitelistformats png jpg jpeg bmp ppm tif tiff def initself n batchsize shuffle seed self n n self batchsize batchsize self seed seed self shuffle shuffle self batchindex 0 self totalbatchesseen 0 self lock threading lock self indexarray none self indexgenerator self flowindex def setindexarrayself self indexarray np arangeself n if self shuffle self indexarray np random permutationself n def getitemself idx if idx lenself raise valueerror asked to retrieve element idx but the sequence has length length formatidxidx lengthlenself if self seed is not none np random seedself seed self totalbatchesseen self totalbatchesseen 1 if self indexarray is none self setindexarray indexarray self indexarray self batchsize idx self batchsize idx 1 return self getbatchesoftransformedsamplesindexarray def lenself return self n self batchsize 1 self batchsize round up def onepochendself self setindexarray def resetself self batchindex 0 def flowindexself ensure self batchindex is 0 self reset while 1 if self seed is not none np random seedself seed self totalbatchesseen if self batchindex 0 self setindexarray if self n 0 avoiding modulo by zero error currentindex 0 else currentindex self batchindex self batchsize self n if self n currentindex self batchsize self batchindex 1 else self batchindex 0 self totalbatchesseen 1 yield self indexarray currentindex currentindex self batchsize def iterself needed if we want to do something like for x y in datagen flow return self def nextself with self lock indexarray nextself indexgenerator the transformation of images is not under thread lock so it can be done in parallel return self getbatchesoftransformedsamplesindexarray def getbatchesoftransformedsamplesself indexarray raise notimplementederror def itervalidfilesdirectory whitelistformats followlinks def recursivelistsubpath return sorted os walksubpath followlinksfollowlinks keylambda x x0 for root files in recursivelistdirectory for fname in sortedfiles if fname lower endswith tiff warnings warn using tiff files with multiple bands will cause distortion please verify your output if fname lower endswithwhitelistformats yield root fname def listvalidfilenamesindirectory directory whitelistformats split classindices followlinks dirname os path basenamedirectory if split allfiles list itervalidfilesdirectory whitelistformats followlinks numfiles lenallfiles start stop intsplit0 numfiles intsplit1 numfiles validfiles allfilesstart stop else validfiles itervalidfiles directory whitelistformats followlinks classes filenames for root fname in validfiles classes appendclassindicesdirname absolutepath os path joinroot fname relativepath os path join dirname os path relpathabsolutepath directory filenames appendrelativepath return classes filenames class batchfromfilesmixin def setprocessingattrs self imagedatagenerator targetsize colormode dataformat savetodir saveprefix saveformat subset interpolation keepaspectratio self imagedatagenerator imagedatagenerator self targetsize tupletargetsize self keepaspectratio keepaspectratio if colormode not in rgb rgba grayscale raise valueerror finvalid color mode colormode expected rgb rgba or grayscale self colormode colormode self dataformat dataformat if self colormode rgba if self dataformat channelslast self imageshape self targetsize 4 else self imageshape 4 self targetsize elif self colormode rgb if self dataformat channelslast self imageshape self targetsize 3 else self imageshape 3 self targetsize else if self dataformat channelslast self imageshape self targetsize 1 else self imageshape 1 self targetsize self savetodir savetodir self saveprefix saveprefix self saveformat saveformat self interpolation interpolation if subset is not none validationsplit self imagedatagenerator validationsplit if subset validation split 0 validationsplit elif subset training split validationsplit 1 else raise valueerror finvalid subset name subset expected training or validation else split none self split split self subset subset def getbatchesoftransformedsamplesself indexarray batchx np zeros lenindexarray self imageshape dtypeself dtype build batch of image data self filepaths is dynamic is better to call it once outside the loop filepaths self filepaths for i j in enumerateindexarray img imageutils loadimg filepathsj colormodeself colormode targetsizeself targetsize interpolationself interpolation keepaspectratioself keepaspectratio x imageutils imgtoarrayimg dataformatself dataformat pillow images should be closed after loadimg but not pil images if hasattrimg close img close if self imagedatagenerator params self imagedatagenerator getrandomtransformx shape x self imagedatagenerator applytransformx params x self imagedatagenerator standardizex batchxi x optionally save augmented images to disk for debugging purposes if self savetodir for i j in enumerateindexarray img imageutils arraytoimg batchxi self dataformat scaletrue fname prefixindexhash format format prefixself saveprefix indexj hashnp random randint1e7 formatself saveformat img saveos path joinself savetodir fname build batch of labels if self classmode input batchy batchx copy elif self classmode in binary sparse batchy np emptylenbatchx dtypeself dtype for i nobservation in enumerateindexarray batchyi self classesnobservation elif self classmode categorical batchy np zeros lenbatchx lenself classindices dtypeself dtype for i nobservation in enumerateindexarray batchyi self classesnobservation 1 0 elif self classmode multioutput batchy outputindexarray for output in self labels elif self classmode raw batchy self labelsindexarray else return batchx if self sampleweight is none return batchx batchy else return batchx batchy self sampleweightindexarray property def filepathsself class labels of every observation raise notimplementederror labels property method has not been implemented in format typeself name property def sampleweightself raise notimplementederror sampleweight property method has not been implemented in formattypeself name kerasexportkeras legacy preprocessing image directoryiterator class directoryiteratorbatchfromfilesmixin iterator allowedclassmodes categorical binary sparse input none def init self directory imagedatagenerator targetsize256 256 colormodergb classesnone classmodecategorical batchsize32 shuffletrue seednone dataformatnone savetodirnone saveprefix saveformatpng followlinksfalse subsetnone interpolationnearest keepaspectratiofalse dtypenone if dataformat is none dataformat backend imagedataformat if dtype is none dtype backend floatx super setprocessingattrs imagedatagenerator targetsize colormode dataformat savetodir saveprefix saveformat subset interpolation keepaspectratio self directory directory self classes classes if classmode not in self allowedclassmodes raise valueerror invalid classmode expected one of format classmode self allowedclassmodes self classmode classmode self dtype dtype first count the number of samples and classes self samples 0 if not classes classes for subdir in sortedos listdirdirectory if os path isdiros path joindirectory subdir classes appendsubdir self numclasses lenclasses self classindices dictzipclasses rangelenclasses pool multiprocessing pool threadpool second build an index of the images in the different class subfolders results self filenames i 0 for dirpath in os path joindirectory subdir for subdir in classes results append pool applyasync listvalidfilenamesindirectory dirpath self whitelistformats self split self classindices followlinks classeslist for res in results classes filenames res get classeslist appendclasses self filenames filenames self samples lenself filenames self classes np zerosself samples dtypeint32 for classes in classeslist self classesi i lenclasses classes i lenclasses ioutils printmsg ffound self samples images belonging to fself numclasses classes pool close pool join self filepaths os path joinself directory fname for fname in self filenames super initself samples batchsize shuffle seed property def filepathsself return self filepaths property def labelsself return self classes property mixin needs this property to work def sampleweightself no sample weights will be returned return none kerasexportkeras legacy preprocessing image numpyarrayiterator class numpyarrayiteratoriterator def init self x y imagedatagenerator batchsize32 shufflefalse sampleweightnone seednone dataformatnone savetodirnone saveprefix saveformatpng subsetnone ignoreclasssplitfalse dtypenone if dataformat is none dataformat backend imagedataformat if dtype is none dtype backend floatx self dtype dtype if isinstancex tuple or isinstancex list if not isinstancex1 list xmisc np asarrayx1 else xmisc np asarrayxx for xx in x1 x x0 for xx in xmisc if lenx lenxx raise valueerror all of the arrays in x should have the same length found a pair with flenx0 lenx lenx lenxx else xmisc if y is not none and lenx leny raise valueerror x images tensor and y labels should have the same length ffound x shape np asarrayx shape fy shape np asarrayy shape if sampleweight is not none and lenx lensampleweight raise valueerror x images tensor and sampleweight should have the same length ffound x shape np asarrayx shape fsampleweight shape np asarraysampleweight shape if subset is not none if subset not in training validation raise valueerror finvalid subset name subset expected training or validation splitidx intlenx imagedatagenerator validationsplit if y is not none and not ignoreclasssplit and not np arrayequal np uniquey splitidx np uniqueysplitidx raise valueerror training and validation subsets have different number of classes after the split if your numpy arrays are sorted by the label you might want to shuffle them if subset validation x x splitidx xmisc np asarrayxx splitidx for xx in xmisc if y is not none y y splitidx else x xsplitidx xmisc np asarrayxxsplitidx for xx in xmisc if y is not none y ysplitidx self x np asarrayx dtypeself dtype self xmisc xmisc if self x ndim 4 raise valueerror input data in numpyarrayiterator should have rank 4 you passed an array fwith shape self x shape channelsaxis 3 if dataformat channelslast else 1 if self x shapechannelsaxis not in 1 3 4 warnings warn numpyarrayiterator is set to use the data format convention dataformat channels on axis strchannelsaxis i e expected either 1 3 or 4 channels on axis strchannelsaxis however it was passed an array with shape strself x shape strself x shapechannelsaxis channels if y is not none self y np asarrayy else self y none if sampleweight is not none self sampleweight np asarraysampleweight else self sampleweight none self imagedatagenerator imagedatagenerator self dataformat dataformat self savetodir savetodir self saveprefix saveprefix self saveformat saveformat super initx shape0 batchsize shuffle seed def getbatchesoftransformedsamplesself indexarray batchx np zeros tuplelenindexarray listself x shape1 dtypeself dtype for i j in enumerateindexarray x self xj params self imagedatagenerator getrandomtransformx shape x self imagedatagenerator applytransform x astypeself dtype params x self imagedatagenerator standardizex batchxi x if self savetodir for i j in enumerateindexarray img imageutils arraytoimg batchxi self dataformat scaletrue fname prefixindexhash format format prefixself saveprefix indexj hashnp random randint1e4 formatself saveformat img saveos path joinself savetodir fname batchxmiscs xxindexarray for xx in self xmisc output batchx if not batchxmiscs else batchx batchxmiscs if self y is none return output0 output self yindexarray if self sampleweight is not none output self sampleweightindexarray return output def validatefilenamefilename whitelistformats return filename lower endswithwhitelistformats and os path isfile filename class dataframeiteratorbatchfromfilesmixin iterator check that inputs match the required classmode build an index of all the unique classes retrieve only training or validation set get labels for each observation check class mode is one of the currently supported check that ycol has several column names if classmode is multioutput check that filenamesfilepaths column values are all strings check labels are string if classmode is binary or sparse check that if binary there are only 2 different classes check values are string list or tuple if classmode is categorical raise warning if classes are given but will be unused check that if weight column that the values are numerical prepare for membership lookup keep only dataframe rows with valid filenames args df pandas dataframe containing filenames in a column xcol string column in df that contains the filenames or filepaths returns absolute paths to image files deprecated def init self featurewisecenterfalse samplewisecenterfalse featurewisestdnormalizationfalse samplewisestdnormalizationfalse zcawhiteningfalse zcaepsilon1e6 rotationrange0 widthshiftrange0 0 heightshiftrange0 0 brightnessrangenone shearrange0 0 zoomrange0 0 channelshiftrange0 0 fillmodenearest cval0 0 horizontalflipfalse verticalflipfalse rescalenone preprocessingfunctionnone dataformatnone validationsplit0 0 interpolationorder1 dtypenone if dataformat is none dataformat backend imagedataformat if dtype is none dtype backend floatx self featurewisecenter featurewisecenter self samplewisecenter samplewisecenter self featurewisestdnormalization featurewisestdnormalization self samplewisestdnormalization samplewisestdnormalization self zcawhitening zcawhitening self zcaepsilon zcaepsilon self rotationrange rotationrange self widthshiftrange widthshiftrange self heightshiftrange heightshiftrange self shearrange shearrange self zoomrange zoomrange self channelshiftrange channelshiftrange self fillmode fillmode self cval cval self horizontalflip horizontalflip self verticalflip verticalflip self rescale rescale self preprocessingfunction preprocessingfunction self dtype dtype self interpolationorder interpolationorder if dataformat not in channelslast channelsfirst raise valueerror dataformat should be channelslast channel after row and column or channelsfirst channel before row and column freceived dataformat self dataformat dataformat if dataformat channelsfirst self channelaxis 1 self rowaxis 2 self colaxis 3 if dataformat channelslast self channelaxis 3 self rowaxis 1 self colaxis 2 if validationsplit and not 0 validationsplit 1 raise valueerror validationsplit must be strictly between 0 and 1 f received validationsplit self validationsplit validationsplit self mean none self std none self zcawhiteningmatrix none if isinstancezoomrange float int self zoomrange 1 zoomrange 1 zoomrange elif lenzoomrange 2 and all isinstanceval float int for val in zoomrange self zoomrange zoomrange0 zoomrange1 else raise valueerror zoomrange should be a float or a tuple or list of two floats freceived zoomrange if zcawhitening if not featurewisecenter self featurewisecenter true warnings warn this imagedatagenerator specifies zcawhitening which overrides setting of featurewisecenter if featurewisestdnormalization self featurewisestdnormalization false warnings warn this imagedatagenerator specifies zcawhitening which overrides setting of featurewisestdnormalization if featurewisestdnormalization if not featurewisecenter self featurewisecenter true warnings warn this imagedatagenerator specifies featurewisestdnormalization which overrides setting of featurewisecenter if samplewisestdnormalization if not samplewisecenter self samplewisecenter true warnings warn this imagedatagenerator specifies samplewisestdnormalization which overrides setting of samplewisecenter if brightnessrange is not none if not isinstancebrightnessrange tuple list or lenbrightnessrange 2 raise valueerror brightnessrange should be tuple or list of two floats freceived brightnessrange self brightnessrange brightnessrange def flow self x ynone batchsize32 shuffletrue sampleweightnone seednone savetodirnone saveprefix saveformatpng ignoreclasssplitfalse subsetnone return numpyarrayiterator x y self batchsizebatchsize shuffleshuffle sampleweightsampleweight seedseed dataformatself dataformat savetodirsavetodir saveprefixsaveprefix saveformatsaveformat ignoreclasssplitignoreclasssplit subsetsubset dtypeself dtype def flowfromdirectory self directory targetsize256 256 colormodergb classesnone classmodecategorical batchsize32 shuffletrue seednone savetodirnone saveprefix saveformatpng followlinksfalse subsetnone interpolationnearest keepaspectratiofalse return directoryiterator directory self targetsizetargetsize colormodecolormode keepaspectratiokeepaspectratio classesclasses classmodeclassmode dataformatself dataformat batchsizebatchsize shuffleshuffle seedseed savetodirsavetodir saveprefixsaveprefix saveformatsaveformat followlinksfollowlinks subsetsubset interpolationinterpolation dtypeself dtype def flowfromdataframe self dataframe directorynone xcolfilename ycolclass weightcolnone targetsize256 256 colormodergb classesnone classmodecategorical batchsize32 shuffletrue seednone savetodirnone saveprefix saveformatpng subsetnone interpolationnearest validatefilenamestrue kwargs if hasext in kwargs warnings warn hasext is deprecated filenames in the dataframe have to match the exact filenames in disk deprecationwarning if sort in kwargs warnings warn sort is deprecated batches will be created in the same order than the filenames provided if shuffle is set to false deprecationwarning if classmode other warnings warn classmode other is deprecated please use classmode raw deprecationwarning classmode raw if dropduplicates in kwargs warnings warn dropduplicates is deprecated you can drop duplicates by using the pandas dataframe dropduplicates method deprecationwarning return dataframeiterator dataframe directory self xcolxcol ycolycol weightcolweightcol targetsizetargetsize colormodecolormode classesclasses classmodeclassmode dataformatself dataformat batchsizebatchsize shuffleshuffle seedseed savetodirsavetodir saveprefixsaveprefix saveformatsaveformat subsetsubset interpolationinterpolation validatefilenamesvalidatefilenames dtypeself dtype def standardizeself x if self preprocessingfunction x self preprocessingfunctionx if self rescale x self rescale if self samplewisecenter x np meanx keepdimstrue if self samplewisestdnormalization x np stdx keepdimstrue 1e6 if self featurewisecenter if self mean is not none x self mean else warnings warn this imagedatagenerator specifies featurewisecenter but it hasn t been fit on any training data fit it first by calling fitnumpydata if self featurewisestdnormalization if self std is not none x self std 1e6 else warnings warn this imagedatagenerator specifies featurewisestdnormalization but it hasn t been fit on any training data fit it first by calling fitnumpydata if self zcawhitening if self zcawhiteningmatrix is not none flatx x reshape1 np prodx shape3 whitex flatx self zcawhiteningmatrix x np reshapewhitex x shape else warnings warn this imagedatagenerator specifies zcawhitening but it hasn t been fit on any training data fit it first by calling fitnumpydata return x def getrandomtransformself imgshape seednone imgrowaxis self rowaxis 1 imgcolaxis self colaxis 1 if seed is not none np random seedseed if self rotationrange theta np random uniformself rotationrange self rotationrange else theta 0 if self heightshiftrange try 1d arraylike or int tx np random choiceself heightshiftrange tx np random choice1 1 except valueerror floating point tx np random uniform self heightshiftrange self heightshiftrange if np maxself heightshiftrange 1 tx imgshapeimgrowaxis else tx 0 if self widthshiftrange try 1d arraylike or int ty np random choiceself widthshiftrange ty np random choice1 1 except valueerror floating point ty np random uniform self widthshiftrange self widthshiftrange if np maxself widthshiftrange 1 ty imgshapeimgcolaxis else ty 0 if self shearrange shear np random uniformself shearrange self shearrange else shear 0 if self zoomrange0 1 and self zoomrange1 1 zx zy 1 1 else zx zy np random uniform self zoomrange0 self zoomrange1 2 fliphorizontal np random random 0 5 self horizontalflip flipvertical np random random 0 5 self verticalflip channelshiftintensity none if self channelshiftrange 0 channelshiftintensity np random uniform self channelshiftrange self channelshiftrange brightness none if self brightnessrange is not none brightness np random uniform self brightnessrange0 self brightnessrange1 transformparameters theta theta tx tx ty ty shear shear zx zx zy zy fliphorizontal fliphorizontal flipvertical flipvertical channelshiftintensity channelshiftintensity brightness brightness return transformparameters def applytransformself x transformparameters x is a single image so it doesn t have image number at index 0 imgrowaxis self rowaxis 1 imgcolaxis self colaxis 1 imgchannelaxis self channelaxis 1 x applyaffinetransform x transformparameters gettheta 0 transformparameters gettx 0 transformparameters getty 0 transformparameters getshear 0 transformparameters getzx 1 transformparameters getzy 1 rowaxisimgrowaxis colaxisimgcolaxis channelaxisimgchannelaxis fillmodeself fillmode cvalself cval orderself interpolationorder if transformparameters getchannelshiftintensity is not none x applychannelshift x transformparameterschannelshiftintensity imgchannelaxis if transformparameters getfliphorizontal false x flipaxisx imgcolaxis if transformparameters getflipvertical false x flipaxisx imgrowaxis if transformparameters getbrightness is not none x applybrightnessshift x transformparametersbrightness false return x def randomtransformself x seednone params self getrandomtransformx shape seed return self applytransformx params def fitself x augmentfalse rounds1 seednone x np asarrayx dtypeself dtype if x ndim 4 raise valueerror input to fit should have rank 4 got array with shape strx shape if x shapeself channelaxis not in 1 3 4 warnings warn expected input to be images as numpy array following the data format convention self dataformat channels on axis strself channelaxis i e expected either 1 3 or 4 channels on axis strself channelaxis however it was passed an array with shape strx shape strx shapeself channelaxis channels if seed is not none np random seedseed x np copyx if self rescale x self rescale if augment ax np zeros tuplerounds x shape0 listx shape1 dtypeself dtype for r in rangerounds for i in rangex shape0 axi r x shape0 self randomtransformxi x ax if self featurewisecenter self mean np meanx axis0 self rowaxis self colaxis broadcastshape 1 1 1 broadcastshapeself channelaxis 1 x shapeself channelaxis self mean np reshapeself mean broadcastshape x self mean if self featurewisestdnormalization self std np stdx axis0 self rowaxis self colaxis broadcastshape 1 1 1 broadcastshapeself channelaxis 1 x shapeself channelaxis self std np reshapeself std broadcastshape x self std 1e6 if self zcawhitening n lenx flatx np reshapex n 1 u s np linalg svdflatx t fullmatricesfalse sinv np sqrtn s self zcaepsilon self zcawhiteningmatrix u sinv dotu t kerasexportkeras legacy preprocessing image randomrotation def randomrotation x rg rowaxis1 colaxis2 channelaxis0 fillmodenearest cval0 0 interpolationorder1 deprecated h w x shaperowaxis x shapecolaxis tx np random uniformhrg hrg h ty np random uniformwrg wrg w x applyaffinetransform x txtx tyty rowaxisrowaxis colaxiscolaxis channelaxischannelaxis fillmodefillmode cvalcval orderinterpolationorder return x kerasexportkeras legacy preprocessing image randomshear def randomshear x intensity rowaxis1 colaxis2 channelaxis0 fillmodenearest cval0 0 interpolationorder1 deprecated if lenzoomrange 2 raise valueerror zoomrange should be a tuple or list of two floats freceived zoomrange if zoomrange0 1 and zoomrange1 1 zx zy 1 1 else zx zy np random uniformzoomrange0 zoomrange1 2 x applyaffinetransform x zxzx zyzy rowaxisrowaxis colaxiscolaxis channelaxischannelaxis fillmodefillmode cvalcval orderinterpolationorder return x kerasexportkeras legacy preprocessing image applychannelshift def applychannelshiftx intensity channelaxis0 x np rollaxisx channelaxis 0 minx maxx np minx np maxx channelimages np clipxchannel intensity minx maxx for xchannel in x x np stackchannelimages axis0 x np rollaxisx 0 channelaxis 1 return x kerasexportkeras legacy preprocessing image randomchannelshift def randomchannelshiftx intensityrange channelaxis0 intensity np random uniformintensityrange intensityrange return applychannelshiftx intensity channelaxischannelaxis kerasexportkeras legacy preprocessing image applybrightnessshift def applybrightnessshiftx brightness scaletrue from pil import imageenhance xmin xmax np minx np maxx localscale xmin 0 or xmax 255 x imageutils arraytoimgx scalelocalscale or scale x imgenhancerbrightness imageenhance brightnessx x imgenhancerbrightness enhancebrightness x imageutils imgtoarrayx if not scale and localscale x x 255 xmax xmin xmin return x kerasexportkeras legacy preprocessing image randombrightness def randombrightnessx brightnessrange scaletrue if lenbrightnessrange 2 raise valueerror brightnessrange should be tuple or list of two floats freceived brightnessrange u np random uniformbrightnessrange0 brightnessrange1 return applybrightnessshiftx u scale def transformmatrixoffsetcentermatrix x y ox floatx 2 0 5 oy floaty 2 0 5 offsetmatrix np array1 0 ox 0 1 oy 0 0 1 resetmatrix np array1 0 ox 0 1 oy 0 0 1 transformmatrix np dotnp dotoffsetmatrix matrix resetmatrix return transformmatrix kerasexportkeras legacy preprocessing image applyaffinetransform def applyaffinetransform x theta0 tx0 ty0 shear0 zx1 zy1 rowaxis1 colaxis2 channelaxis0 fillmodenearest cval0 0 order1 input sanity checks 1 x must 2d image with one or more channels i e a 3d tensor 2 channels must be either first or last dimension if np uniquerowaxis colaxis channelaxis size 3 raise valueerror rowaxis colaxis and channelaxis must be distinct shall we support negative indices validindices set0 1 2 actualindices setrowaxis colaxis channelaxis if actualindices validindices raise valueerror finvalid axis indices actualindices validindices if x ndim 3 raise valueerrorinput arrays must be multichannel 2d images if channelaxis not in 0 2 raise valueerror channels are allowed and the first and last dimensions transformmatrix none if theta 0 theta np deg2radtheta rotationmatrix np array np costheta np sintheta 0 np sintheta np costheta 0 0 0 1 transformmatrix rotationmatrix if tx 0 or ty 0 shiftmatrix np array1 0 tx 0 1 ty 0 0 1 if transformmatrix is none transformmatrix shiftmatrix else transformmatrix np dottransformmatrix shiftmatrix if shear 0 shear np deg2radshear shearmatrix np array 1 np sinshear 0 0 np cosshear 0 0 0 1 if transformmatrix is none transformmatrix shearmatrix else transformmatrix np dottransformmatrix shearmatrix if zx 1 or zy 1 zoommatrix np arrayzx 0 0 0 zy 0 0 0 1 if transformmatrix is none transformmatrix zoommatrix else transformmatrix np dottransformmatrix zoommatrix if transformmatrix is not none h w x shaperowaxis x shapecolaxis transformmatrix transformmatrixoffsetcenter transformmatrix h w x np rollaxisx channelaxis 0 matrix construction assumes that coordinates are x y in that order however regular numpy arrays use y x aka i j indexing possible solution is 1 swap the x and y axes 2 apply transform 3 swap the x and y axes again to restore imagelike data ordering mathematically it is equivalent to the following transformation m pmp where p is the permutation matrix m is the original transformation matrix if colaxis rowaxis transformmatrix 0 1 transformmatrix 1 0 transformmatrix0 1 transformmatrix1 0 finalaffinematrix transformmatrix 2 2 finaloffset transformmatrix 2 2 channelimages scipy ndimage interpolation affinetransform xchannel finalaffinematrix finaloffset orderorder modefillmode cvalcval for xchannel in x x np stackchannelimages axis0 x np rollaxisx 0 channelaxis 1 return x base class for image data iterators deprecated every iterator must implement the _get_batches_of_transformed_samples method args n integer total number of samples in the dataset to loop over batch_size integer size of a batch shuffle boolean whether to shuffle the data between epochs seed random seeding for data shuffling round up ensure self batch_index is 0 avoiding modulo by zero error needed if we want to do something like for x y in data_gen flow the transformation of images is not under thread lock so it can be done in parallel gets a batch of transformed samples args index_array array of sample indices to include in batch returns a batch of transformed samples iterates on files with extension args directory absolute path to the directory containing files to be counted white_list_formats set of strings containing allowed extensions for the files to be counted follow_links boolean follow symbolic links to subdirectories yields tuple of root filename with extension in white_list_formats lists paths of files in subdir with extensions in white_list_formats args directory absolute path to a directory containing the files to list the directory name is used as class label and must be a key of class_indices white_list_formats set of strings containing allowed extensions for the files to be counted split tuple of floats e g 0 2 0 6 to only take into account a certain fraction of files in each directory e g segment 0 6 1 0 would only account for last 40 percent of images in each directory class_indices dictionary mapping a class name to its index follow_links boolean follow symbolic links to subdirectories returns classes a list of class indices filenames the path of valid files in directory relative from directory s parent e g if directory is dataset class1 the filenames will be class1 file1 jpg class1 file2 jpg adds methods related to getting batches from filenames it includes the logic to transform image files to batches sets attributes to use later for processing files into a batch args image_data_generator instance of imagedatagenerator to use for random transformations and normalization target_size tuple of integers dimensions to resize input images to color_mode one of rgb rgba grayscale color mode to read images data_format string one of channels_first channels_last save_to_dir optional directory where to save the pictures being yielded in a viewable format this is useful for visualizing the random transformations being applied for debugging purposes save_prefix string prefix to use for saving sample images if save_to_dir is set save_format format to use for saving sample images if save_to_dir is set subset subset of data training or validation if validation_split is set in imagedatagenerator interpolation interpolation method used to resample the image if the target size is different from that of the loaded image supported methods are nearest bilinear and bicubic if pil version 1 1 3 or newer is installed lanczos is also supported if pil version 3 4 0 or newer is installed box and hamming are also supported by default nearest is used keep_aspect_ratio boolean whether to resize images to a target size without aspect ratio distortion the image is cropped in the center with target aspect ratio before resizing gets a batch of transformed samples args index_array array of sample indices to include in batch returns a batch of transformed samples build batch of image data self filepaths is dynamic is better to call it once outside the loop pillow images should be closed after load_img but not pil images optionally save augmented images to disk for debugging purposes build batch of labels list of absolute paths to image files class labels of every observation iterator capable of reading images from a directory on disk deprecated first count the number of samples and classes second build an index of the images in the different class subfolders mixin needs this property to work no sample weights will be returned iterator yielding data from a numpy array deprecated check if a filename refers to a valid file args filename string absolute path to a file white_list_formats set allowed file extensions returns a boolean value indicating if the filename is valid or not iterator capable of reading images from a directory as a dataframe check that inputs match the required class_mode check which image files are valid and keep them build an index of all the unique classes retrieve only training or validation set get labels for each observation check class mode is one of the currently supported check that y_col has several column names if class_mode is multi_output check that filenames filepaths column values are all strings check labels are string if class_mode is binary or sparse check that if binary there are only 2 different classes check values are string list or tuple if class_mode is categorical raise warning if classes are given but will be unused check that if weight column that the values are numerical prepare for membership lookup keep only dataframe rows with valid filenames args df pandas dataframe containing filenames in a column x_col string column in df that contains the filenames or filepaths returns absolute paths to image files deprecated applies the normalization configuration in place to a batch of inputs x is changed in place since the function is mainly used internally to standardize images and feed them to your network if a copy of x would be created instead it would have a significant performance cost if you want to apply this method without changing the input in place you can call the method creating a copy before standardize np copy x args x batch of inputs to be normalized returns the inputs normalized generates random parameters for a transformation args img_shape tuple of integers shape of the image that is transformed seed random seed returns a dictionary containing randomly chosen parameters describing the transformation 1 d array like or int floating point 1 d array like or int floating point applies a transformation to an image according to given parameters args x 3d tensor single image transform_parameters dictionary with string parameter pairs describing the transformation currently the following parameters from the dictionary are used theta float rotation angle in degrees tx float shift in the x direction ty float shift in the y direction shear float shear angle in degrees zx float zoom in the x direction zy float zoom in the y direction flip_horizontal boolean horizontal flip flip_vertical boolean vertical flip channel_shift_intensity float channel shift intensity brightness float brightness shift intensity returns a transformed version of the input same shape x is a single image so it doesn t have image number at index 0 applies a random transformation to an image args x 3d tensor single image seed random seed returns a randomly transformed version of the input same shape fits the data generator to some sample data this computes the internal data stats related to the data dependent transformations based on an array of sample data only required if featurewise_center or featurewise_std_normalization or zca_whitening are set to true when rescale is set to a value rescaling is applied to sample data before computing the internal data stats args x sample data should have rank 4 in case of grayscale data the channels axis should have value 1 in case of rgb data it should have value 3 and in case of rgba data it should have value 4 augment boolean default false whether to fit on randomly augmented samples rounds int default 1 if using data augmentation augment true this is how many augmentation passes over the data to use seed int default none random seed deprecated deprecated deprecated deprecated performs a channel shift deprecated args x input tensor must be 3d intensity transformation intensity channel_axis index of axis for channels in the input tensor returns numpy image tensor performs a random channel shift deprecated args x input tensor must be 3d intensity_range transformation intensity channel_axis index of axis for channels in the input tensor returns numpy image tensor performs a brightness shift deprecated args x input tensor must be 3d brightness float the new brightness value scale whether to rescale the image such that minimum and maximum values are 0 and 255 respectively default true returns numpy image tensor raises importerror if pil is not available performs a random brightness shift deprecated args x input tensor must be 3d brightness_range tuple of floats brightness range scale whether to rescale the image such that minimum and maximum values are 0 and 255 respectively default true returns numpy image tensor raises valueerror if brightness_range isn t a tuple applies an affine transformation specified by the parameters given deprecated input sanity checks 1 x must 2d image with one or more channels i e a 3d tensor 2 channels must be either first or last dimension shall we support negative indices matrix construction assumes that coordinates are x y in that order however regular numpy arrays use y x aka i j indexing possible solution is 1 swap the x and y axes 2 apply transform 3 swap the x and y axes again to restore image like data ordering mathematically it is equivalent to the following transformation m pmp where p is the permutation matrix m is the original transformation matrix
import collections
import multiprocessing
import os
import threading
import warnings

import numpy as np

from keras import backend
from keras.api_export import keras_export
from keras.trainers.data_adapters.py_dataset_adapter import PyDataset
from keras.utils import image_utils
from keras.utils import io_utils
from keras.utils.module_utils import scipy


@keras_export("keras._legacy.preprocessing.image.Iterator")
class Iterator(PyDataset):
    

    white_list_formats = ("png", "jpg", "jpeg", "bmp", "ppm", "tif", "tiff")

    def __init__(self, n, batch_size, shuffle, seed):
        self.n = n
        self.batch_size = batch_size
        self.seed = seed
        self.shuffle = shuffle
        self.batch_index = 0
        self.total_batches_seen = 0
        self.lock = threading.Lock()
        self.index_array = None
        self.index_generator = self._flow_index()

    def _set_index_array(self):
        self.index_array = np.arange(self.n)
        if self.shuffle:
            self.index_array = np.random.permutation(self.n)

    def __getitem__(self, idx):
        if idx >= len(self):
            raise ValueError(
                "Asked to retrieve element {idx}, "
                "but the Sequence "
                "has length {length}".format(idx=idx, length=len(self))
            )
        if self.seed is not None:
            np.random.seed(self.seed + self.total_batches_seen)
        self.total_batches_seen += 1
        if self.index_array is None:
            self._set_index_array()
        index_array = self.index_array[
            self.batch_size * idx : self.batch_size * (idx + 1)
        ]
        return self._get_batches_of_transformed_samples(index_array)

    def __len__(self):
        return (self.n + self.batch_size - 1) // self.batch_size  

    def on_epoch_end(self):
        self._set_index_array()

    def reset(self):
        self.batch_index = 0

    def _flow_index(self):
        
        self.reset()
        while 1:
            if self.seed is not None:
                np.random.seed(self.seed + self.total_batches_seen)
            if self.batch_index == 0:
                self._set_index_array()

            if self.n == 0:
                
                current_index = 0
            else:
                current_index = (self.batch_index * self.batch_size) % self.n
            if self.n > current_index + self.batch_size:
                self.batch_index += 1
            else:
                self.batch_index = 0
            self.total_batches_seen += 1
            yield self.index_array[
                current_index : current_index + self.batch_size
            ]

    def __iter__(self):
        
        
        return self

    def __next__(self):
        with self.lock:
            index_array = next(self.index_generator)
        
        
        return self._get_batches_of_transformed_samples(index_array)

    def _get_batches_of_transformed_samples(self, index_array):
        
        raise NotImplementedError


def _iter_valid_files(directory, white_list_formats, follow_links):
    

    def _recursive_list(subpath):
        return sorted(
            os.walk(subpath, followlinks=follow_links), key=lambda x: x[0]
        )

    for root, _, files in _recursive_list(directory):
        for fname in sorted(files):
            if fname.lower().endswith(".tiff"):
                warnings.warn(
                    'Using ".tiff" files with multiple bands '
                    "will cause distortion. Please verify your output."
                )
            if fname.lower().endswith(white_list_formats):
                yield root, fname


def _list_valid_filenames_in_directory(
    directory, white_list_formats, split, class_indices, follow_links
):
    
    dirname = os.path.basename(directory)
    if split:
        all_files = list(
            _iter_valid_files(directory, white_list_formats, follow_links)
        )
        num_files = len(all_files)
        start, stop = int(split[0] * num_files), int(split[1] * num_files)
        valid_files = all_files[start:stop]
    else:
        valid_files = _iter_valid_files(
            directory, white_list_formats, follow_links
        )
    classes = []
    filenames = []
    for root, fname in valid_files:
        classes.append(class_indices[dirname])
        absolute_path = os.path.join(root, fname)
        relative_path = os.path.join(
            dirname, os.path.relpath(absolute_path, directory)
        )
        filenames.append(relative_path)

    return classes, filenames


class BatchFromFilesMixin:
    

    def set_processing_attrs(
        self,
        image_data_generator,
        target_size,
        color_mode,
        data_format,
        save_to_dir,
        save_prefix,
        save_format,
        subset,
        interpolation,
        keep_aspect_ratio,
    ):
        
        self.image_data_generator = image_data_generator
        self.target_size = tuple(target_size)
        self.keep_aspect_ratio = keep_aspect_ratio
        if color_mode not in {"rgb", "rgba", "grayscale"}:
            raise ValueError(
                f"Invalid color mode: {color_mode}"
                '; expected "rgb", "rgba", or "grayscale".'
            )
        self.color_mode = color_mode
        self.data_format = data_format
        if self.color_mode == "rgba":
            if self.data_format == "channels_last":
                self.image_shape = self.target_size + (4,)
            else:
                self.image_shape = (4,) + self.target_size
        elif self.color_mode == "rgb":
            if self.data_format == "channels_last":
                self.image_shape = self.target_size + (3,)
            else:
                self.image_shape = (3,) + self.target_size
        else:
            if self.data_format == "channels_last":
                self.image_shape = self.target_size + (1,)
            else:
                self.image_shape = (1,) + self.target_size
        self.save_to_dir = save_to_dir
        self.save_prefix = save_prefix
        self.save_format = save_format
        self.interpolation = interpolation
        if subset is not None:
            validation_split = self.image_data_generator._validation_split
            if subset == "validation":
                split = (0, validation_split)
            elif subset == "training":
                split = (validation_split, 1)
            else:
                raise ValueError(
                    f"Invalid subset name: {subset};"
                    'expected "training" or "validation"'
                )
        else:
            split = None
        self.split = split
        self.subset = subset

    def _get_batches_of_transformed_samples(self, index_array):
        
        batch_x = np.zeros(
            (len(index_array),) + self.image_shape, dtype=self.dtype
        )
        
        
        filepaths = self.filepaths
        for i, j in enumerate(index_array):
            img = image_utils.load_img(
                filepaths[j],
                color_mode=self.color_mode,
                target_size=self.target_size,
                interpolation=self.interpolation,
                keep_aspect_ratio=self.keep_aspect_ratio,
            )
            x = image_utils.img_to_array(img, data_format=self.data_format)
            
            
            if hasattr(img, "close"):
                img.close()
            if self.image_data_generator:
                params = self.image_data_generator.get_random_transform(x.shape)
                x = self.image_data_generator.apply_transform(x, params)
                x = self.image_data_generator.standardize(x)
            batch_x[i] = x
        
        if self.save_to_dir:
            for i, j in enumerate(index_array):
                img = image_utils.array_to_img(
                    batch_x[i], self.data_format, scale=True
                )
                fname = "{prefix}_{index}_{hash}.{format}".format(
                    prefix=self.save_prefix,
                    index=j,
                    hash=np.random.randint(1e7),
                    format=self.save_format,
                )
                img.save(os.path.join(self.save_to_dir, fname))
        
        if self.class_mode == "input":
            batch_y = batch_x.copy()
        elif self.class_mode in {"binary", "sparse"}:
            batch_y = np.empty(len(batch_x), dtype=self.dtype)
            for i, n_observation in enumerate(index_array):
                batch_y[i] = self.classes[n_observation]
        elif self.class_mode == "categorical":
            batch_y = np.zeros(
                (len(batch_x), len(self.class_indices)), dtype=self.dtype
            )
            for i, n_observation in enumerate(index_array):
                batch_y[i, self.classes[n_observation]] = 1.0
        elif self.class_mode == "multi_output":
            batch_y = [output[index_array] for output in self.labels]
        elif self.class_mode == "raw":
            batch_y = self.labels[index_array]
        else:
            return batch_x
        if self.sample_weight is None:
            return batch_x, batch_y
        else:
            return batch_x, batch_y, self.sample_weight[index_array]

    @property
    def filepaths(self):
        
        raise NotImplementedError(
            "`filepaths` property method has not "
            "been implemented in {}.".format(type(self).__name__)
        )

    @property
    def labels(self):
        
        raise NotImplementedError(
            "`labels` property method has not been implemented in {}.".format(
                type(self).__name__
            )
        )

    @property
    def sample_weight(self):
        raise NotImplementedError(
            "`sample_weight` property method has not "
            "been implemented in {}.".format(type(self).__name__)
        )


@keras_export("keras._legacy.preprocessing.image.DirectoryIterator")
class DirectoryIterator(BatchFromFilesMixin, Iterator):
    

    allowed_class_modes = {"categorical", "binary", "sparse", "input", None}

    def __init__(
        self,
        directory,
        image_data_generator,
        target_size=(256, 256),
        color_mode="rgb",
        classes=None,
        class_mode="categorical",
        batch_size=32,
        shuffle=True,
        seed=None,
        data_format=None,
        save_to_dir=None,
        save_prefix="",
        save_format="png",
        follow_links=False,
        subset=None,
        interpolation="nearest",
        keep_aspect_ratio=False,
        dtype=None,
    ):
        if data_format is None:
            data_format = backend.image_data_format()
        if dtype is None:
            dtype = backend.floatx()
        super().set_processing_attrs(
            image_data_generator,
            target_size,
            color_mode,
            data_format,
            save_to_dir,
            save_prefix,
            save_format,
            subset,
            interpolation,
            keep_aspect_ratio,
        )
        self.directory = directory
        self.classes = classes
        if class_mode not in self.allowed_class_modes:
            raise ValueError(
                "Invalid class_mode: {}; expected one of: {}".format(
                    class_mode, self.allowed_class_modes
                )
            )
        self.class_mode = class_mode
        self.dtype = dtype
        
        self.samples = 0

        if not classes:
            classes = []
            for subdir in sorted(os.listdir(directory)):
                if os.path.isdir(os.path.join(directory, subdir)):
                    classes.append(subdir)
        self.num_classes = len(classes)
        self.class_indices = dict(zip(classes, range(len(classes))))

        pool = multiprocessing.pool.ThreadPool()

        
        
        results = []
        self.filenames = []
        i = 0
        for dirpath in (os.path.join(directory, subdir) for subdir in classes):
            results.append(
                pool.apply_async(
                    _list_valid_filenames_in_directory,
                    (
                        dirpath,
                        self.white_list_formats,
                        self.split,
                        self.class_indices,
                        follow_links,
                    ),
                )
            )
        classes_list = []
        for res in results:
            classes, filenames = res.get()
            classes_list.append(classes)
            self.filenames += filenames
        self.samples = len(self.filenames)
        self.classes = np.zeros((self.samples,), dtype="int32")
        for classes in classes_list:
            self.classes[i : i + len(classes)] = classes
            i += len(classes)

        io_utils.print_msg(
            f"Found {self.samples} images belonging to "
            f"{self.num_classes} classes."
        )
        pool.close()
        pool.join()
        self._filepaths = [
            os.path.join(self.directory, fname) for fname in self.filenames
        ]
        super().__init__(self.samples, batch_size, shuffle, seed)

    @property
    def filepaths(self):
        return self._filepaths

    @property
    def labels(self):
        return self.classes

    @property  
    def sample_weight(self):
        
        return None


@keras_export("keras._legacy.preprocessing.image.NumpyArrayIterator")
class NumpyArrayIterator(Iterator):
    

    def __init__(
        self,
        x,
        y,
        image_data_generator,
        batch_size=32,
        shuffle=False,
        sample_weight=None,
        seed=None,
        data_format=None,
        save_to_dir=None,
        save_prefix="",
        save_format="png",
        subset=None,
        ignore_class_split=False,
        dtype=None,
    ):
        if data_format is None:
            data_format = backend.image_data_format()
        if dtype is None:
            dtype = backend.floatx()
        self.dtype = dtype
        if isinstance(x, tuple) or isinstance(x, list):
            if not isinstance(x[1], list):
                x_misc = [np.asarray(x[1])]
            else:
                x_misc = [np.asarray(xx) for xx in x[1]]
            x = x[0]
            for xx in x_misc:
                if len(x) != len(xx):
                    raise ValueError(
                        "All of the arrays in `x` "
                        "should have the same length. "
                        "Found a pair with: "
                        f"len(x[0]) = {len(x)}, len(x[?]) = {len(xx)}"
                    )
        else:
            x_misc = []

        if y is not None and len(x) != len(y):
            raise ValueError(
                "`x` (images tensor) and `y` (labels) "
                "should have the same length. "
                f"Found: x.shape = {np.asarray(x).shape}, "
                f"y.shape = {np.asarray(y).shape}"
            )
        if sample_weight is not None and len(x) != len(sample_weight):
            raise ValueError(
                "`x` (images tensor) and `sample_weight` "
                "should have the same length. "
                f"Found: x.shape = {np.asarray(x).shape}, "
                f"sample_weight.shape = {np.asarray(sample_weight).shape}"
            )
        if subset is not None:
            if subset not in {"training", "validation"}:
                raise ValueError(
                    f"Invalid subset name: {subset}"
                    '; expected "training" or "validation".'
                )
            split_idx = int(len(x) * image_data_generator._validation_split)

            if (
                y is not None
                and not ignore_class_split
                and not np.array_equal(
                    np.unique(y[:split_idx]), np.unique(y[split_idx:])
                )
            ):
                raise ValueError(
                    "Training and validation subsets "
                    "have different number of classes after "
                    "the split. If your numpy arrays are "
                    "sorted by the label, you might want "
                    "to shuffle them."
                )

            if subset == "validation":
                x = x[:split_idx]
                x_misc = [np.asarray(xx[:split_idx]) for xx in x_misc]
                if y is not None:
                    y = y[:split_idx]
            else:
                x = x[split_idx:]
                x_misc = [np.asarray(xx[split_idx:]) for xx in x_misc]
                if y is not None:
                    y = y[split_idx:]

        self.x = np.asarray(x, dtype=self.dtype)
        self.x_misc = x_misc
        if self.x.ndim != 4:
            raise ValueError(
                "Input data in `NumpyArrayIterator` "
                "should have rank 4. You passed an array "
                f"with shape {self.x.shape}"
            )
        channels_axis = 3 if data_format == "channels_last" else 1
        if self.x.shape[channels_axis] not in {1, 3, 4}:
            warnings.warn(
                'NumpyArrayIterator is set to use the data format convention "'
                + data_format
                + '" (channels on axis '
                + str(channels_axis)
                + "), i.e. expected either 1, 3, or 4 channels on axis "
                + str(channels_axis)
                + ". However, it was passed an array with shape "
                + str(self.x.shape)
                + " ("
                + str(self.x.shape[channels_axis])
                + " channels)."
            )
        if y is not None:
            self.y = np.asarray(y)
        else:
            self.y = None
        if sample_weight is not None:
            self.sample_weight = np.asarray(sample_weight)
        else:
            self.sample_weight = None
        self.image_data_generator = image_data_generator
        self.data_format = data_format
        self.save_to_dir = save_to_dir
        self.save_prefix = save_prefix
        self.save_format = save_format
        super().__init__(x.shape[0], batch_size, shuffle, seed)

    def _get_batches_of_transformed_samples(self, index_array):
        batch_x = np.zeros(
            tuple([len(index_array)] + list(self.x.shape)[1:]), dtype=self.dtype
        )
        for i, j in enumerate(index_array):
            x = self.x[j]
            params = self.image_data_generator.get_random_transform(x.shape)
            x = self.image_data_generator.apply_transform(
                x.astype(self.dtype), params
            )
            x = self.image_data_generator.standardize(x)
            batch_x[i] = x

        if self.save_to_dir:
            for i, j in enumerate(index_array):
                img = image_utils.array_to_img(
                    batch_x[i], self.data_format, scale=True
                )
                fname = "{prefix}_{index}_{hash}.{format}".format(
                    prefix=self.save_prefix,
                    index=j,
                    hash=np.random.randint(1e4),
                    format=self.save_format,
                )
                img.save(os.path.join(self.save_to_dir, fname))
        batch_x_miscs = [xx[index_array] for xx in self.x_misc]
        output = (batch_x if not batch_x_miscs else [batch_x] + batch_x_miscs,)
        if self.y is None:
            return output[0]
        output += (self.y[index_array],)
        if self.sample_weight is not None:
            output += (self.sample_weight[index_array],)
        return output


def validate_filename(filename, white_list_formats):
    
    return filename.lower().endswith(white_list_formats) and os.path.isfile(
        filename
    )


class DataFrameIterator(BatchFromFilesMixin, Iterator):
    

    allowed_class_modes = {
        "binary",
        "categorical",
        "input",
        "multi_output",
        "raw",
        "sparse",
        None,
    }

    def __init__(
        self,
        dataframe,
        directory=None,
        image_data_generator=None,
        x_col="filename",
        y_col="class",
        weight_col=None,
        target_size=(256, 256),
        color_mode="rgb",
        classes=None,
        class_mode="categorical",
        batch_size=32,
        shuffle=True,
        seed=None,
        data_format="channels_last",
        save_to_dir=None,
        save_prefix="",
        save_format="png",
        subset=None,
        interpolation="nearest",
        keep_aspect_ratio=False,
        dtype="float32",
        validate_filenames=True,
    ):
        super().set_processing_attrs(
            image_data_generator,
            target_size,
            color_mode,
            data_format,
            save_to_dir,
            save_prefix,
            save_format,
            subset,
            interpolation,
            keep_aspect_ratio,
        )
        df = dataframe.copy()
        self.directory = directory or ""
        self.class_mode = class_mode
        self.dtype = dtype
        
        self._check_params(df, x_col, y_col, weight_col, classes)
        if (
            validate_filenames
        ):  
            df = self._filter_valid_filepaths(df, x_col)
        if class_mode not in ["input", "multi_output", "raw", None]:
            df, classes = self._filter_classes(df, y_col, classes)
            num_classes = len(classes)
            
            self.class_indices = dict(zip(classes, range(len(classes))))
        
        if self.split:
            num_files = len(df)
            start = int(self.split[0] * num_files)
            stop = int(self.split[1] * num_files)
            df = df.iloc[start:stop, :]
        
        if class_mode not in ["input", "multi_output", "raw", None]:
            self.classes = self.get_classes(df, y_col)
        self.filenames = df[x_col].tolist()
        self._sample_weight = df[weight_col].values if weight_col else None

        if class_mode == "multi_output":
            self._targets = [np.array(df[col].tolist()) for col in y_col]
        if class_mode == "raw":
            self._targets = df[y_col].values
        self.samples = len(self.filenames)
        validated_string = (
            "validated" if validate_filenames else "non-validated"
        )
        if class_mode in ["input", "multi_output", "raw", None]:
            io_utils.print_msg(
                f"Found {self.samples} {validated_string} image filenames."
            )
        else:
            io_utils.print_msg(
                f"Found {self.samples} {validated_string} image filenames "
                f"belonging to {num_classes} classes."
            )
        self._filepaths = [
            os.path.join(self.directory, fname) for fname in self.filenames
        ]
        super().__init__(self.samples, batch_size, shuffle, seed)

    def _check_params(self, df, x_col, y_col, weight_col, classes):
        
        if self.class_mode not in self.allowed_class_modes:
            raise ValueError(
                "Invalid class_mode: {}; expected one of: {}".format(
                    self.class_mode, self.allowed_class_modes
                )
            )
        
        
        if (self.class_mode == "multi_output") and not isinstance(y_col, list):
            raise TypeError(
                'If class_mode="{}", y_col must be a list. Received {}.'.format(
                    self.class_mode, type(y_col).__name__
                )
            )
        
        if not all(df[x_col].apply(lambda x: isinstance(x, str))):
            raise TypeError(
                f"All values in column x_col={x_col} must be strings."
            )
        
        if self.class_mode in {"binary", "sparse"}:
            if not all(df[y_col].apply(lambda x: isinstance(x, str))):
                raise TypeError(
                    'If class_mode="{}", y_col="{}" column '
                    "values must be strings.".format(self.class_mode, y_col)
                )
        
        if self.class_mode == "binary":
            if classes:
                classes = set(classes)
                if len(classes) != 2:
                    raise ValueError(
                        'If class_mode="binary" there must be 2 '
                        "classes. {} class/es were given.".format(len(classes))
                    )
            elif df[y_col].nunique() != 2:
                raise ValueError(
                    'If class_mode="binary" there must be 2 classes. '
                    "Found {} classes.".format(df[y_col].nunique())
                )
        
        if self.class_mode == "categorical":
            types = (str, list, tuple)
            if not all(df[y_col].apply(lambda x: isinstance(x, types))):
                raise TypeError(
                    'If class_mode="{}", y_col="{}" column '
                    "values must be type string, list or tuple.".format(
                        self.class_mode, y_col
                    )
                )
        
        if classes and self.class_mode in {
            "input",
            "multi_output",
            "raw",
            None,
        }:
            warnings.warn(
                '`classes` will be ignored given the class_mode="{}"'.format(
                    self.class_mode
                )
            )
        
        if weight_col and not issubclass(df[weight_col].dtype.type, np.number):
            raise TypeError(f"Column weight_col={weight_col} must be numeric.")

    def get_classes(self, df, y_col):
        labels = []
        for label in df[y_col]:
            if isinstance(label, (list, tuple)):
                labels.append([self.class_indices[lbl] for lbl in label])
            else:
                labels.append(self.class_indices[label])
        return labels

    @staticmethod
    def _filter_classes(df, y_col, classes):
        df = df.copy()

        def remove_classes(labels, classes):
            if isinstance(labels, (list, tuple)):
                labels = [cls for cls in labels if cls in classes]
                return labels or None
            elif isinstance(labels, str):
                return labels if labels in classes else None
            else:
                raise TypeError(
                    "Expect string, list or tuple "
                    "but found {} in {} column ".format(type(labels), y_col)
                )

        if classes:
            
            classes = list(collections.OrderedDict.fromkeys(classes).keys())
            df[y_col] = df[y_col].apply(lambda x: remove_classes(x, classes))
        else:
            classes = set()
            for v in df[y_col]:
                if isinstance(v, (list, tuple)):
                    classes.update(v)
                else:
                    classes.add(v)
            classes = sorted(classes)
        return df.dropna(subset=[y_col]), classes

    def _filter_valid_filepaths(self, df, x_col):
        
        filepaths = df[x_col].map(
            lambda fname: os.path.join(self.directory, fname)
        )
        mask = filepaths.apply(
            validate_filename, args=(self.white_list_formats,)
        )
        n_invalid = (~mask).sum()
        if n_invalid:
            warnings.warn(
                'Found {} invalid image filename(s) in x_col="{}". '
                "These filename(s) will be ignored.".format(n_invalid, x_col)
            )
        return df[mask]

    @property
    def filepaths(self):
        return self._filepaths

    @property
    def labels(self):
        if self.class_mode in {"multi_output", "raw"}:
            return self._targets
        else:
            return self.classes

    @property
    def sample_weight(self):
        return self._sample_weight


def flip_axis(x, axis):
    x = np.asarray(x).swapaxes(axis, 0)
    x = x[::-1, ...]
    x = x.swapaxes(0, axis)
    return x


@keras_export("keras._legacy.preprocessing.image.ImageDataGenerator")
class ImageDataGenerator:
    

    def __init__(
        self,
        featurewise_center=False,
        samplewise_center=False,
        featurewise_std_normalization=False,
        samplewise_std_normalization=False,
        zca_whitening=False,
        zca_epsilon=1e-6,
        rotation_range=0,
        width_shift_range=0.0,
        height_shift_range=0.0,
        brightness_range=None,
        shear_range=0.0,
        zoom_range=0.0,
        channel_shift_range=0.0,
        fill_mode="nearest",
        cval=0.0,
        horizontal_flip=False,
        vertical_flip=False,
        rescale=None,
        preprocessing_function=None,
        data_format=None,
        validation_split=0.0,
        interpolation_order=1,
        dtype=None,
    ):
        if data_format is None:
            data_format = backend.image_data_format()
        if dtype is None:
            dtype = backend.floatx()

        self.featurewise_center = featurewise_center
        self.samplewise_center = samplewise_center
        self.featurewise_std_normalization = featurewise_std_normalization
        self.samplewise_std_normalization = samplewise_std_normalization
        self.zca_whitening = zca_whitening
        self.zca_epsilon = zca_epsilon
        self.rotation_range = rotation_range
        self.width_shift_range = width_shift_range
        self.height_shift_range = height_shift_range
        self.shear_range = shear_range
        self.zoom_range = zoom_range
        self.channel_shift_range = channel_shift_range
        self.fill_mode = fill_mode
        self.cval = cval
        self.horizontal_flip = horizontal_flip
        self.vertical_flip = vertical_flip
        self.rescale = rescale
        self.preprocessing_function = preprocessing_function
        self.dtype = dtype
        self.interpolation_order = interpolation_order

        if data_format not in {"channels_last", "channels_first"}:
            raise ValueError(
                '`data_format` should be `"channels_last"` '
                "(channel after row and column) or "
                '`"channels_first"` (channel before row and column). '
                f"Received: {data_format}"
            )
        self.data_format = data_format
        if data_format == "channels_first":
            self.channel_axis = 1
            self.row_axis = 2
            self.col_axis = 3
        if data_format == "channels_last":
            self.channel_axis = 3
            self.row_axis = 1
            self.col_axis = 2
        if validation_split and not 0 < validation_split < 1:
            raise ValueError(
                "`validation_split` must be strictly between 0 and 1. "
                f" Received: {validation_split}"
            )
        self._validation_split = validation_split

        self.mean = None
        self.std = None
        self.zca_whitening_matrix = None

        if isinstance(zoom_range, (float, int)):
            self.zoom_range = [1 - zoom_range, 1 + zoom_range]
        elif len(zoom_range) == 2 and all(
            isinstance(val, (float, int)) for val in zoom_range
        ):
            self.zoom_range = [zoom_range[0], zoom_range[1]]
        else:
            raise ValueError(
                "`zoom_range` should be a float or "
                "a tuple or list of two floats. "
                f"Received: {zoom_range}"
            )
        if zca_whitening:
            if not featurewise_center:
                self.featurewise_center = True
                warnings.warn(
                    "This ImageDataGenerator specifies "
                    "`zca_whitening`, which overrides "
                    "setting of `featurewise_center`."
                )
            if featurewise_std_normalization:
                self.featurewise_std_normalization = False
                warnings.warn(
                    "This ImageDataGenerator specifies "
                    "`zca_whitening` "
                    "which overrides setting of"
                    "`featurewise_std_normalization`."
                )
        if featurewise_std_normalization:
            if not featurewise_center:
                self.featurewise_center = True
                warnings.warn(
                    "This ImageDataGenerator specifies "
                    "`featurewise_std_normalization`, "
                    "which overrides setting of "
                    "`featurewise_center`."
                )
        if samplewise_std_normalization:
            if not samplewise_center:
                self.samplewise_center = True
                warnings.warn(
                    "This ImageDataGenerator specifies "
                    "`samplewise_std_normalization`, "
                    "which overrides setting of "
                    "`samplewise_center`."
                )
        if brightness_range is not None:
            if (
                not isinstance(brightness_range, (tuple, list))
                or len(brightness_range) != 2
            ):
                raise ValueError(
                    "`brightness_range should be tuple or list of two floats. "
                    f"Received: {brightness_range}"
                )
        self.brightness_range = brightness_range

    def flow(
        self,
        x,
        y=None,
        batch_size=32,
        shuffle=True,
        sample_weight=None,
        seed=None,
        save_to_dir=None,
        save_prefix="",
        save_format="png",
        ignore_class_split=False,
        subset=None,
    ):
        return NumpyArrayIterator(
            x,
            y,
            self,
            batch_size=batch_size,
            shuffle=shuffle,
            sample_weight=sample_weight,
            seed=seed,
            data_format=self.data_format,
            save_to_dir=save_to_dir,
            save_prefix=save_prefix,
            save_format=save_format,
            ignore_class_split=ignore_class_split,
            subset=subset,
            dtype=self.dtype,
        )

    def flow_from_directory(
        self,
        directory,
        target_size=(256, 256),
        color_mode="rgb",
        classes=None,
        class_mode="categorical",
        batch_size=32,
        shuffle=True,
        seed=None,
        save_to_dir=None,
        save_prefix="",
        save_format="png",
        follow_links=False,
        subset=None,
        interpolation="nearest",
        keep_aspect_ratio=False,
    ):
        return DirectoryIterator(
            directory,
            self,
            target_size=target_size,
            color_mode=color_mode,
            keep_aspect_ratio=keep_aspect_ratio,
            classes=classes,
            class_mode=class_mode,
            data_format=self.data_format,
            batch_size=batch_size,
            shuffle=shuffle,
            seed=seed,
            save_to_dir=save_to_dir,
            save_prefix=save_prefix,
            save_format=save_format,
            follow_links=follow_links,
            subset=subset,
            interpolation=interpolation,
            dtype=self.dtype,
        )

    def flow_from_dataframe(
        self,
        dataframe,
        directory=None,
        x_col="filename",
        y_col="class",
        weight_col=None,
        target_size=(256, 256),
        color_mode="rgb",
        classes=None,
        class_mode="categorical",
        batch_size=32,
        shuffle=True,
        seed=None,
        save_to_dir=None,
        save_prefix="",
        save_format="png",
        subset=None,
        interpolation="nearest",
        validate_filenames=True,
        **kwargs,
    ):
        if "has_ext" in kwargs:
            warnings.warn(
                "has_ext is deprecated, filenames in the dataframe have "
                "to match the exact filenames in disk.",
                DeprecationWarning,
            )
        if "sort" in kwargs:
            warnings.warn(
                "sort is deprecated, batches will be created in the"
                "same order than the filenames provided if shuffle"
                "is set to False.",
                DeprecationWarning,
            )
        if class_mode == "other":
            warnings.warn(
                '`class_mode` "other" is deprecated, please use '
                '`class_mode` "raw".',
                DeprecationWarning,
            )
            class_mode = "raw"
        if "drop_duplicates" in kwargs:
            warnings.warn(
                "drop_duplicates is deprecated, you can drop duplicates "
                "by using the pandas.DataFrame.drop_duplicates method.",
                DeprecationWarning,
            )

        return DataFrameIterator(
            dataframe,
            directory,
            self,
            x_col=x_col,
            y_col=y_col,
            weight_col=weight_col,
            target_size=target_size,
            color_mode=color_mode,
            classes=classes,
            class_mode=class_mode,
            data_format=self.data_format,
            batch_size=batch_size,
            shuffle=shuffle,
            seed=seed,
            save_to_dir=save_to_dir,
            save_prefix=save_prefix,
            save_format=save_format,
            subset=subset,
            interpolation=interpolation,
            validate_filenames=validate_filenames,
            dtype=self.dtype,
        )

    def standardize(self, x):
        
        if self.preprocessing_function:
            x = self.preprocessing_function(x)
        if self.rescale:
            x *= self.rescale
        if self.samplewise_center:
            x -= np.mean(x, keepdims=True)
        if self.samplewise_std_normalization:
            x /= np.std(x, keepdims=True) + 1e-6

        if self.featurewise_center:
            if self.mean is not None:
                x -= self.mean
            else:
                warnings.warn(
                    "This ImageDataGenerator specifies "
                    "`featurewise_center`, but it hasn't "
                    "been fit on any training data. Fit it "
                    "first by calling `.fit(numpy_data)`."
                )
        if self.featurewise_std_normalization:
            if self.std is not None:
                x /= self.std + 1e-6
            else:
                warnings.warn(
                    "This ImageDataGenerator specifies "
                    "`featurewise_std_normalization`, "
                    "but it hasn't "
                    "been fit on any training data. Fit it "
                    "first by calling `.fit(numpy_data)`."
                )
        if self.zca_whitening:
            if self.zca_whitening_matrix is not None:
                flat_x = x.reshape(-1, np.prod(x.shape[-3:]))
                white_x = flat_x @ self.zca_whitening_matrix
                x = np.reshape(white_x, x.shape)
            else:
                warnings.warn(
                    "This ImageDataGenerator specifies "
                    "`zca_whitening`, but it hasn't "
                    "been fit on any training data. Fit it "
                    "first by calling `.fit(numpy_data)`."
                )
        return x

    def get_random_transform(self, img_shape, seed=None):
        
        img_row_axis = self.row_axis - 1
        img_col_axis = self.col_axis - 1

        if seed is not None:
            np.random.seed(seed)

        if self.rotation_range:
            theta = np.random.uniform(-self.rotation_range, self.rotation_range)
        else:
            theta = 0

        if self.height_shift_range:
            try:  
                tx = np.random.choice(self.height_shift_range)
                tx *= np.random.choice([-1, 1])
            except ValueError:  
                tx = np.random.uniform(
                    -self.height_shift_range, self.height_shift_range
                )
            if np.max(self.height_shift_range) < 1:
                tx *= img_shape[img_row_axis]
        else:
            tx = 0

        if self.width_shift_range:
            try:  
                ty = np.random.choice(self.width_shift_range)
                ty *= np.random.choice([-1, 1])
            except ValueError:  
                ty = np.random.uniform(
                    -self.width_shift_range, self.width_shift_range
                )
            if np.max(self.width_shift_range) < 1:
                ty *= img_shape[img_col_axis]
        else:
            ty = 0

        if self.shear_range:
            shear = np.random.uniform(-self.shear_range, self.shear_range)
        else:
            shear = 0

        if self.zoom_range[0] == 1 and self.zoom_range[1] == 1:
            zx, zy = 1, 1
        else:
            zx, zy = np.random.uniform(
                self.zoom_range[0], self.zoom_range[1], 2
            )

        flip_horizontal = (np.random.random() < 0.5) * self.horizontal_flip
        flip_vertical = (np.random.random() < 0.5) * self.vertical_flip

        channel_shift_intensity = None
        if self.channel_shift_range != 0:
            channel_shift_intensity = np.random.uniform(
                -self.channel_shift_range, self.channel_shift_range
            )

        brightness = None
        if self.brightness_range is not None:
            brightness = np.random.uniform(
                self.brightness_range[0], self.brightness_range[1]
            )

        transform_parameters = {
            "theta": theta,
            "tx": tx,
            "ty": ty,
            "shear": shear,
            "zx": zx,
            "zy": zy,
            "flip_horizontal": flip_horizontal,
            "flip_vertical": flip_vertical,
            "channel_shift_intensity": channel_shift_intensity,
            "brightness": brightness,
        }

        return transform_parameters

    def apply_transform(self, x, transform_parameters):
        
        
        img_row_axis = self.row_axis - 1
        img_col_axis = self.col_axis - 1
        img_channel_axis = self.channel_axis - 1

        x = apply_affine_transform(
            x,
            transform_parameters.get("theta", 0),
            transform_parameters.get("tx", 0),
            transform_parameters.get("ty", 0),
            transform_parameters.get("shear", 0),
            transform_parameters.get("zx", 1),
            transform_parameters.get("zy", 1),
            row_axis=img_row_axis,
            col_axis=img_col_axis,
            channel_axis=img_channel_axis,
            fill_mode=self.fill_mode,
            cval=self.cval,
            order=self.interpolation_order,
        )

        if transform_parameters.get("channel_shift_intensity") is not None:
            x = apply_channel_shift(
                x,
                transform_parameters["channel_shift_intensity"],
                img_channel_axis,
            )

        if transform_parameters.get("flip_horizontal", False):
            x = flip_axis(x, img_col_axis)

        if transform_parameters.get("flip_vertical", False):
            x = flip_axis(x, img_row_axis)

        if transform_parameters.get("brightness") is not None:
            x = apply_brightness_shift(
                x, transform_parameters["brightness"], False
            )

        return x

    def random_transform(self, x, seed=None):
        
        params = self.get_random_transform(x.shape, seed)
        return self.apply_transform(x, params)

    def fit(self, x, augment=False, rounds=1, seed=None):
        
        x = np.asarray(x, dtype=self.dtype)
        if x.ndim != 4:
            raise ValueError(
                "Input to `.fit()` should have rank 4. Got array with shape: "
                + str(x.shape)
            )
        if x.shape[self.channel_axis] not in {1, 3, 4}:
            warnings.warn(
                "Expected input to be images (as Numpy array) "
                'following the data format convention "'
                + self.data_format
                + '" (channels on axis '
                + str(self.channel_axis)
                + "), i.e. expected either 1, 3 or 4 channels on axis "
                + str(self.channel_axis)
                + ". However, it was passed an array with shape "
                + str(x.shape)
                + " ("
                + str(x.shape[self.channel_axis])
                + " channels)."
            )

        if seed is not None:
            np.random.seed(seed)

        x = np.copy(x)
        if self.rescale:
            x *= self.rescale

        if augment:
            ax = np.zeros(
                tuple([rounds * x.shape[0]] + list(x.shape)[1:]),
                dtype=self.dtype,
            )
            for r in range(rounds):
                for i in range(x.shape[0]):
                    ax[i + r * x.shape[0]] = self.random_transform(x[i])
            x = ax

        if self.featurewise_center:
            self.mean = np.mean(x, axis=(0, self.row_axis, self.col_axis))
            broadcast_shape = [1, 1, 1]
            broadcast_shape[self.channel_axis - 1] = x.shape[self.channel_axis]
            self.mean = np.reshape(self.mean, broadcast_shape)
            x -= self.mean

        if self.featurewise_std_normalization:
            self.std = np.std(x, axis=(0, self.row_axis, self.col_axis))
            broadcast_shape = [1, 1, 1]
            broadcast_shape[self.channel_axis - 1] = x.shape[self.channel_axis]
            self.std = np.reshape(self.std, broadcast_shape)
            x /= self.std + 1e-6

        if self.zca_whitening:
            n = len(x)
            flat_x = np.reshape(x, (n, -1))

            u, s, _ = np.linalg.svd(flat_x.T, full_matrices=False)
            s_inv = np.sqrt(n) / (s + self.zca_epsilon)
            self.zca_whitening_matrix = (u * s_inv).dot(u.T)


@keras_export("keras._legacy.preprocessing.image.random_rotation")
def random_rotation(
    x,
    rg,
    row_axis=1,
    col_axis=2,
    channel_axis=0,
    fill_mode="nearest",
    cval=0.0,
    interpolation_order=1,
):
    
    theta = np.random.uniform(-rg, rg)
    x = apply_affine_transform(
        x,
        theta=theta,
        row_axis=row_axis,
        col_axis=col_axis,
        channel_axis=channel_axis,
        fill_mode=fill_mode,
        cval=cval,
        order=interpolation_order,
    )
    return x


@keras_export("keras._legacy.preprocessing.image.random_shift")
def random_shift(
    x,
    wrg,
    hrg,
    row_axis=1,
    col_axis=2,
    channel_axis=0,
    fill_mode="nearest",
    cval=0.0,
    interpolation_order=1,
):
    
    h, w = x.shape[row_axis], x.shape[col_axis]
    tx = np.random.uniform(-hrg, hrg) * h
    ty = np.random.uniform(-wrg, wrg) * w
    x = apply_affine_transform(
        x,
        tx=tx,
        ty=ty,
        row_axis=row_axis,
        col_axis=col_axis,
        channel_axis=channel_axis,
        fill_mode=fill_mode,
        cval=cval,
        order=interpolation_order,
    )
    return x


@keras_export("keras._legacy.preprocessing.image.random_shear")
def random_shear(
    x,
    intensity,
    row_axis=1,
    col_axis=2,
    channel_axis=0,
    fill_mode="nearest",
    cval=0.0,
    interpolation_order=1,
):
    
    shear = np.random.uniform(-intensity, intensity)
    x = apply_affine_transform(
        x,
        shear=shear,
        row_axis=row_axis,
        col_axis=col_axis,
        channel_axis=channel_axis,
        fill_mode=fill_mode,
        cval=cval,
        order=interpolation_order,
    )
    return x


@keras_export("keras._legacy.preprocessing.image.random_zoom")
def random_zoom(
    x,
    zoom_range,
    row_axis=1,
    col_axis=2,
    channel_axis=0,
    fill_mode="nearest",
    cval=0.0,
    interpolation_order=1,
):
    
    if len(zoom_range) != 2:
        raise ValueError(
            "`zoom_range` should be a tuple or list of two floats. "
            f"Received: {zoom_range}"
        )

    if zoom_range[0] == 1 and zoom_range[1] == 1:
        zx, zy = 1, 1
    else:
        zx, zy = np.random.uniform(zoom_range[0], zoom_range[1], 2)
    x = apply_affine_transform(
        x,
        zx=zx,
        zy=zy,
        row_axis=row_axis,
        col_axis=col_axis,
        channel_axis=channel_axis,
        fill_mode=fill_mode,
        cval=cval,
        order=interpolation_order,
    )
    return x


@keras_export("keras._legacy.preprocessing.image.apply_channel_shift")
def apply_channel_shift(x, intensity, channel_axis=0):
    
    x = np.rollaxis(x, channel_axis, 0)
    min_x, max_x = np.min(x), np.max(x)
    channel_images = [
        np.clip(x_channel + intensity, min_x, max_x) for x_channel in x
    ]
    x = np.stack(channel_images, axis=0)
    x = np.rollaxis(x, 0, channel_axis + 1)
    return x


@keras_export("keras._legacy.preprocessing.image.random_channel_shift")
def random_channel_shift(x, intensity_range, channel_axis=0):
    
    intensity = np.random.uniform(-intensity_range, intensity_range)
    return apply_channel_shift(x, intensity, channel_axis=channel_axis)


@keras_export("keras._legacy.preprocessing.image.apply_brightness_shift")
def apply_brightness_shift(x, brightness, scale=True):
    
    from PIL import ImageEnhance

    x_min, x_max = np.min(x), np.max(x)
    local_scale = (x_min < 0) or (x_max > 255)
    x = image_utils.array_to_img(x, scale=local_scale or scale)
    x = imgenhancer_Brightness = ImageEnhance.Brightness(x)
    x = imgenhancer_Brightness.enhance(brightness)
    x = image_utils.img_to_array(x)
    if not scale and local_scale:
        x = x / 255 * (x_max - x_min) + x_min
    return x


@keras_export("keras._legacy.preprocessing.image.random_brightness")
def random_brightness(x, brightness_range, scale=True):
    
    if len(brightness_range) != 2:
        raise ValueError(
            "`brightness_range should be tuple or list of two floats. "
            f"Received: {brightness_range}"
        )

    u = np.random.uniform(brightness_range[0], brightness_range[1])
    return apply_brightness_shift(x, u, scale)


def transform_matrix_offset_center(matrix, x, y):
    o_x = float(x) / 2 - 0.5
    o_y = float(y) / 2 - 0.5
    offset_matrix = np.array([[1, 0, o_x], [0, 1, o_y], [0, 0, 1]])
    reset_matrix = np.array([[1, 0, -o_x], [0, 1, -o_y], [0, 0, 1]])
    transform_matrix = np.dot(np.dot(offset_matrix, matrix), reset_matrix)
    return transform_matrix


@keras_export("keras._legacy.preprocessing.image.apply_affine_transform")
def apply_affine_transform(
    x,
    theta=0,
    tx=0,
    ty=0,
    shear=0,
    zx=1,
    zy=1,
    row_axis=1,
    col_axis=2,
    channel_axis=0,
    fill_mode="nearest",
    cval=0.0,
    order=1,
):
    
    
    
    
    if np.unique([row_axis, col_axis, channel_axis]).size != 3:
        raise ValueError(
            "'row_axis', 'col_axis', and 'channel_axis' must be distinct"
        )

    
    valid_indices = set([0, 1, 2])
    actual_indices = set([row_axis, col_axis, channel_axis])
    if actual_indices != valid_indices:
        raise ValueError(
            f"Invalid axis' indices: {actual_indices - valid_indices}"
        )

    if x.ndim != 3:
        raise ValueError("Input arrays must be multi-channel 2D images.")
    if channel_axis not in [0, 2]:
        raise ValueError(
            "Channels are allowed and the first and last dimensions."
        )

    transform_matrix = None
    if theta != 0:
        theta = np.deg2rad(theta)
        rotation_matrix = np.array(
            [
                [np.cos(theta), -np.sin(theta), 0],
                [np.sin(theta), np.cos(theta), 0],
                [0, 0, 1],
            ]
        )
        transform_matrix = rotation_matrix

    if tx != 0 or ty != 0:
        shift_matrix = np.array([[1, 0, tx], [0, 1, ty], [0, 0, 1]])
        if transform_matrix is None:
            transform_matrix = shift_matrix
        else:
            transform_matrix = np.dot(transform_matrix, shift_matrix)

    if shear != 0:
        shear = np.deg2rad(shear)
        shear_matrix = np.array(
            [[1, -np.sin(shear), 0], [0, np.cos(shear), 0], [0, 0, 1]]
        )
        if transform_matrix is None:
            transform_matrix = shear_matrix
        else:
            transform_matrix = np.dot(transform_matrix, shear_matrix)

    if zx != 1 or zy != 1:
        zoom_matrix = np.array([[zx, 0, 0], [0, zy, 0], [0, 0, 1]])
        if transform_matrix is None:
            transform_matrix = zoom_matrix
        else:
            transform_matrix = np.dot(transform_matrix, zoom_matrix)

    if transform_matrix is not None:
        h, w = x.shape[row_axis], x.shape[col_axis]
        transform_matrix = transform_matrix_offset_center(
            transform_matrix, h, w
        )
        x = np.rollaxis(x, channel_axis, 0)

        
        
        
        
        
        
        
        
        
        if col_axis > row_axis:
            transform_matrix[:, [0, 1]] = transform_matrix[:, [1, 0]]
            transform_matrix[[0, 1]] = transform_matrix[[1, 0]]
        final_affine_matrix = transform_matrix[:2, :2]
        final_offset = transform_matrix[:2, 2]

        channel_images = [
            scipy.ndimage.interpolation.affine_transform(
                x_channel,
                final_affine_matrix,
                final_offset,
                order=order,
                mode=fill_mode,
                cval=cval,
            )
            for x_channel in x
        ]
        x = np.stack(channel_images, axis=0)
        x = np.rollaxis(x, 0, channel_axis + 1)
    return x