_tifffile.py 363 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675
  1. #! /usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # tifffile.py
  4. # Copyright (c) 2008-2018, Christoph Gohlke
  5. # Copyright (c) 2008-2018, The Regents of the University of California
  6. # Produced at the Laboratory for Fluorescence Dynamics
  7. # All rights reserved.
  8. #
  9. # Redistribution and use in source and binary forms, with or without
  10. # modification, are permitted provided that the following conditions are met:
  11. #
  12. # * Redistributions of source code must retain the above copyright
  13. # notice, this list of conditions and the following disclaimer.
  14. # * Redistributions in binary form must reproduce the above copyright
  15. # notice, this list of conditions and the following disclaimer in the
  16. # documentation and/or other materials provided with the distribution.
  17. # * Neither the name of the copyright holders nor the names of any
  18. # contributors may be used to endorse or promote products derived
  19. # from this software without specific prior written permission.
  20. #
  21. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  22. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  24. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  25. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  26. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  27. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  28. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  29. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  30. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  31. # POSSIBILITY OF SUCH DAMAGE.
  32. """Read image and meta data from (bio) TIFF(R) files. Save numpy arrays as TIFF.
  33. Image and metadata can be read from TIFF, BigTIFF, OME-TIFF, STK, LSM, NIH,
  34. SGI, ImageJ, MicroManager, FluoView, ScanImage, SEQ, GEL, and GeoTIFF files.
  35. Tifffile is not a general-purpose TIFF library.
  36. Only a subset of the TIFF specification is supported, mainly uncompressed and
  37. losslessly compressed 1, 8, 16, 32 and 64 bit integer, 16, 32 and 64-bit float,
  38. grayscale and RGB(A) images, which are commonly used in scientific imaging.
  39. Specifically, reading slices of image data, image trees defined via SubIFDs,
  40. CCITT and OJPEG compression, chroma subsampling without JPEG compression,
  41. or IPTC and XMP metadata are not implemented.
  42. TIFF(R), the tagged Image File Format, is a trademark and under control of
  43. Adobe Systems Incorporated. BigTIFF allows for files greater than 4 GB.
  44. STK, LSM, FluoView, SGI, SEQ, GEL, and OME-TIFF, are custom extensions
  45. defined by Molecular Devices (Universal Imaging Corporation), Carl Zeiss
  46. MicroImaging, Olympus, Silicon Graphics International, Media Cybernetics,
  47. Molecular Dynamics, and the Open Microscopy Environment consortium
  48. respectively.
  49. For command line usage run C{python -m tifffile --help}
  50. :Author:
  51. `Christoph Gohlke <https://www.lfd.uci.edu/~gohlke/>`_
  52. :Organization:
  53. Laboratory for Fluorescence Dynamics, University of California, Irvine
  54. :Version: 2018.06.15
  55. Requirements
  56. ------------
  57. * `CPython 3.6 64-bit <https://www.python.org>`_
  58. * `Numpy 1.14 <http://www.numpy.org>`_
  59. * `Matplotlib 2.2 <https://www.matplotlib.org>`_ (optional for plotting)
  60. * `Tifffile.c 2018.02.10 <https://www.lfd.uci.edu/~gohlke/>`_
  61. (recommended for faster decoding of PackBits and LZW encoded strings)
  62. * `Tifffile_geodb.py 2018.02.10 <https://www.lfd.uci.edu/~gohlke/>`_
  63. (optional enums for GeoTIFF metadata)
  64. * Python 2 requires 'futures', 'enum34', 'pathlib'.
  65. Revisions
  66. ---------
  67. 2018.06.15
  68. Pass 2680 tests.
  69. Towards reading JPEG and other compressions via imagecodecs package (WIP).
  70. Add function to validate TIFF using 'jhove -m TIFF-hul'.
  71. Save bool arrays as bilevel TIFF.
  72. Accept pathlib.Path as filenames.
  73. Move 'software' argument from TiffWriter __init__ to save.
  74. Raise DOS limit to 16 TB.
  75. Lazy load lzma and zstd compressors and decompressors.
  76. Add option to save IJMetadata tags.
  77. Return correct number of pages for truncated series (bug fix).
  78. Move EXIF tags to TIFF.TAG as per TIFF/EP standard.
  79. 2018.02.18
  80. Pass 2293 tests.
  81. Always save RowsPerStrip and Resolution tags as required by TIFF standard.
  82. Do not use badly typed ImageDescription.
  83. Coherce bad ASCII string tags to bytes.
  84. Tuning of __str__ functions.
  85. Fix reading 'undefined' tag values (bug fix).
  86. Read and write ZSTD compressed data.
  87. Use hexdump to print byte strings.
  88. Determine TIFF byte order from data dtype in imsave.
  89. Add option to specify RowsPerStrip for compressed strips.
  90. Allow memory map of arrays with non-native byte order.
  91. Attempt to handle ScanImage <= 5.1 files.
  92. Restore TiffPageSeries.pages sequence interface.
  93. Use numpy.frombuffer instead of fromstring to read from binary data.
  94. Parse GeoTIFF metadata.
  95. Add option to apply horizontal differencing before compression.
  96. Towards reading PerkinElmer QPTIFF (no test files).
  97. Do not index out of bounds data in tifffile.c unpackbits and decodelzw.
  98. 2017.09.29 (tentative)
  99. Many backwards incompatible changes improving speed and resource usage:
  100. Pass 2268 tests.
  101. Add detail argument to __str__ function. Remove info functions.
  102. Fix potential issue correcting offsets of large LSM files with positions.
  103. Remove TiffFile sequence interface; use TiffFile.pages instead.
  104. Do not make tag values available as TiffPage attributes.
  105. Use str (not bytes) type for tag and metadata strings (WIP).
  106. Use documented standard tag and value names (WIP).
  107. Use enums for some documented TIFF tag values.
  108. Remove 'memmap' and 'tmpfile' options; use out='memmap' instead.
  109. Add option to specify output in asarray functions.
  110. Add option to concurrently decode image strips or tiles using threads.
  111. Add TiffPage.asrgb function (WIP).
  112. Do not apply colormap in asarray.
  113. Remove 'colormapped', 'rgbonly', and 'scale_mdgel' options from asarray.
  114. Consolidate metadata in TiffFile _metadata functions.
  115. Remove non-tag metadata properties from TiffPage.
  116. Add function to convert LSM to tiled BIN files.
  117. Align image data in file.
  118. Make TiffPage.dtype a numpy.dtype.
  119. Add 'ndim' and 'size' properties to TiffPage and TiffPageSeries.
  120. Allow imsave to write non-BigTIFF files up to ~4 GB.
  121. Only read one page for shaped series if possible.
  122. Add memmap function to create memory-mapped array stored in TIFF file.
  123. Add option to save empty arrays to TIFF files.
  124. Add option to save truncated TIFF files.
  125. Allow single tile images to be saved contiguously.
  126. Add optional movie mode for files with uniform pages.
  127. Lazy load pages.
  128. Use lightweight TiffFrame for IFDs sharing properties with key TiffPage.
  129. Move module constants to 'TIFF' namespace (speed up module import).
  130. Remove 'fastij' option from TiffFile.
  131. Remove 'pages' parameter from TiffFile.
  132. Remove TIFFfile alias.
  133. Deprecate Python 2.
  134. Require enum34 and futures packages on Python 2.7.
  135. Remove Record class and return all metadata as dict instead.
  136. Add functions to parse STK, MetaSeries, ScanImage, SVS, Pilatus metadata.
  137. Read tags from EXIF and GPS IFDs.
  138. Use pformat for tag and metadata values.
  139. Fix reading some UIC tags (bug fix).
  140. Do not modify input array in imshow (bug fix).
  141. Fix Python implementation of unpack_ints.
  142. 2017.05.23
  143. Pass 1961 tests.
  144. Write correct number of SampleFormat values (bug fix).
  145. Use Adobe deflate code to write ZIP compressed files.
  146. Add option to pass tag values as packed binary data for writing.
  147. Defer tag validation to attribute access.
  148. Use property instead of lazyattr decorator for simple expressions.
  149. 2017.03.17
  150. Write IFDs and tag values on word boundaries.
  151. Read ScanImage metadata.
  152. Remove is_rgb and is_indexed attributes from TiffFile.
  153. Create files used by doctests.
  154. 2017.01.12
  155. Read Zeiss SEM metadata.
  156. Read OME-TIFF with invalid references to external files.
  157. Rewrite C LZW decoder (5x faster).
  158. Read corrupted LSM files missing EOI code in LZW stream.
  159. 2017.01.01
  160. Add option to append images to existing TIFF files.
  161. Read files without pages.
  162. Read S-FEG and Helios NanoLab tags created by FEI software.
  163. Allow saving Color Filter Array (CFA) images.
  164. Add info functions returning more information about TiffFile and TiffPage.
  165. Add option to read specific pages only.
  166. Remove maxpages argument (backwards incompatible).
  167. Remove test_tifffile function.
  168. 2016.10.28
  169. Pass 1944 tests.
  170. Improve detection of ImageJ hyperstacks.
  171. Read TVIPS metadata created by EM-MENU (by Marco Oster).
  172. Add option to disable using OME-XML metadata.
  173. Allow non-integer range attributes in modulo tags (by Stuart Berg).
  174. 2016.06.21
  175. Do not always memmap contiguous data in page series.
  176. 2016.05.13
  177. Add option to specify resolution unit.
  178. Write grayscale images with extra samples when planarconfig is specified.
  179. Do not write RGB color images with 2 samples.
  180. Reorder TiffWriter.save keyword arguments (backwards incompatible).
  181. 2016.04.18
  182. Pass 1932 tests.
  183. TiffWriter, imread, and imsave accept open binary file streams.
  184. 2016.04.13
  185. Correctly handle reversed fill order in 2 and 4 bps images (bug fix).
  186. Implement reverse_bitorder in C.
  187. 2016.03.18
  188. Fix saving additional ImageJ metadata.
  189. 2016.02.22
  190. Pass 1920 tests.
  191. Write 8 bytes double tag values using offset if necessary (bug fix).
  192. Add option to disable writing second image description tag.
  193. Detect tags with incorrect counts.
  194. Disable color mapping for LSM.
  195. 2015.11.13
  196. Read LSM 6 mosaics.
  197. Add option to specify directory of memory-mapped files.
  198. Add command line options to specify vmin and vmax values for colormapping.
  199. 2015.10.06
  200. New helper function to apply colormaps.
  201. Renamed is_palette attributes to is_indexed (backwards incompatible).
  202. Color-mapped samples are now contiguous (backwards incompatible).
  203. Do not color-map ImageJ hyperstacks (backwards incompatible).
  204. Towards reading Leica SCN.
  205. 2015.09.25
  206. Read images with reversed bit order (FillOrder is LSB2MSB).
  207. 2015.09.21
  208. Read RGB OME-TIFF.
  209. Warn about malformed OME-XML.
  210. 2015.09.16
  211. Detect some corrupted ImageJ metadata.
  212. Better axes labels for 'shaped' files.
  213. Do not create TiffTag for default values.
  214. Chroma subsampling is not supported.
  215. Memory-map data in TiffPageSeries if possible (optional).
  216. 2015.08.17
  217. Pass 1906 tests.
  218. Write ImageJ hyperstacks (optional).
  219. Read and write LZMA compressed data.
  220. Specify datetime when saving (optional).
  221. Save tiled and color-mapped images (optional).
  222. Ignore void bytecounts and offsets if possible.
  223. Ignore bogus image_depth tag created by ISS Vista software.
  224. Decode floating point horizontal differencing (not tiled).
  225. Save image data contiguously if possible.
  226. Only read first IFD from ImageJ files if possible.
  227. Read ImageJ 'raw' format (files larger than 4 GB).
  228. TiffPageSeries class for pages with compatible shape and data type.
  229. Try to read incomplete tiles.
  230. Open file dialog if no filename is passed on command line.
  231. Ignore errors when decoding OME-XML.
  232. Rename decoder functions (backwards incompatible).
  233. 2014.08.24
  234. TiffWriter class for incremental writing images.
  235. Simplify examples.
  236. 2014.08.19
  237. Add memmap function to FileHandle.
  238. Add function to determine if image data in TiffPage is memory-mappable.
  239. Do not close files if multifile_close parameter is False.
  240. 2014.08.10
  241. Pass 1730 tests.
  242. Return all extrasamples by default (backwards incompatible).
  243. Read data from series of pages into memory-mapped array (optional).
  244. Squeeze OME dimensions (backwards incompatible).
  245. Workaround missing EOI code in strips.
  246. Support image and tile depth tags (SGI extension).
  247. Better handling of STK/UIC tags (backwards incompatible).
  248. Disable color mapping for STK.
  249. Julian to datetime converter.
  250. TIFF ASCII type may be NULL separated.
  251. Unwrap strip offsets for LSM files greater than 4 GB.
  252. Correct strip byte counts in compressed LSM files.
  253. Skip missing files in OME series.
  254. Read embedded TIFF files.
  255. 2014.02.05
  256. Save rational numbers as type 5 (bug fix).
  257. 2013.12.20
  258. Keep other files in OME multi-file series closed.
  259. FileHandle class to abstract binary file handle.
  260. Disable color mapping for bad OME-TIFF produced by bio-formats.
  261. Read bad OME-XML produced by ImageJ when cropping.
  262. 2013.11.03
  263. Allow zlib compress data in imsave function (optional).
  264. Memory-map contiguous image data (optional).
  265. 2013.10.28
  266. Read MicroManager metadata and little-endian ImageJ tag.
  267. Save extra tags in imsave function.
  268. Save tags in ascending order by code (bug fix).
  269. 2012.10.18
  270. Accept file like objects (read from OIB files).
  271. 2012.08.21
  272. Rename TIFFfile to TiffFile and TIFFpage to TiffPage.
  273. TiffSequence class for reading sequence of TIFF files.
  274. Read UltraQuant tags.
  275. Allow float numbers as resolution in imsave function.
  276. 2012.08.03
  277. Read MD GEL tags and NIH Image header.
  278. 2012.07.25
  279. Read ImageJ tags.
  280. ...
  281. Notes
  282. -----
  283. The API is not stable yet and might change between revisions.
  284. Tested on little-endian platforms only.
  285. Other Python packages and modules for reading (bio) scientific TIFF files:
  286. * `python-bioformats <https://github.com/CellProfiler/python-bioformats>`_
  287. * `Imread <https://github.com/luispedro/imread>`_
  288. * `PyLibTiff <https://github.com/pearu/pylibtiff>`_
  289. * `ITK <https://www.itk.org>`_
  290. * `PyLSM <https://launchpad.net/pylsm>`_
  291. * `PyMca.TiffIO.py <https://github.com/vasole/pymca>`_ (same as fabio.TiffIO)
  292. * `BioImageXD.Readers <http://www.bioimagexd.net/>`_
  293. * `Cellcognition.io <http://cellcognition.org/>`_
  294. * `pymimage <https://github.com/ardoi/pymimage>`_
  295. * `pytiff <https://github.com/FZJ-INM1-BDA/pytiff>`_
  296. Acknowledgements
  297. ----------------
  298. * Egor Zindy, University of Manchester, for lsm_scan_info specifics.
  299. * Wim Lewis for a bug fix and some LSM functions.
  300. * Hadrien Mary for help on reading MicroManager files.
  301. * Christian Kliche for help writing tiled and color-mapped files.
  302. References
  303. ----------
  304. 1) TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated.
  305. http://partners.adobe.com/public/developer/tiff/
  306. 2) TIFF File Format FAQ. http://www.awaresystems.be/imaging/tiff/faq.html
  307. 3) MetaMorph Stack (STK) Image File Format.
  308. http://support.meta.moleculardevices.com/docs/t10243.pdf
  309. 4) Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010).
  310. Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011
  311. 5) The OME-TIFF format.
  312. http://www.openmicroscopy.org/site/support/file-formats/ome-tiff
  313. 6) UltraQuant(r) Version 6.0 for Windows Start-Up Guide.
  314. http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf
  315. 7) Micro-Manager File Formats.
  316. http://www.micro-manager.org/wiki/Micro-Manager_File_Formats
  317. 8) Tags for TIFF and Related Specifications. Digital Preservation.
  318. http://www.digitalpreservation.gov/formats/content/tiff_tags.shtml
  319. 9) ScanImage BigTiff Specification - ScanImage 2016.
  320. http://scanimage.vidriotechnologies.com/display/SI2016/
  321. ScanImage+BigTiff+Specification
  322. 10) CIPA DC-008-2016: Exchangeable image file format for digital still cameras:
  323. Exif Version 2.31.
  324. http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
  325. Examples
  326. --------
  327. >>> # write numpy array to TIFF file
  328. >>> data = numpy.random.rand(4, 301, 219)
  329. >>> imsave('temp.tif', data, photometric='minisblack')
  330. >>> # read numpy array from TIFF file
  331. >>> image = imread('temp.tif')
  332. >>> numpy.testing.assert_array_equal(image, data)
  333. >>> # iterate over pages and tags in TIFF file
  334. >>> with TiffFile('temp.tif') as tif:
  335. ... images = tif.asarray()
  336. ... for page in tif.pages:
  337. ... for tag in page.tags.values():
  338. ... _ = tag.name, tag.value
  339. ... image = page.asarray()
  340. """
  341. from __future__ import division, print_function
  342. import sys
  343. import os
  344. import io
  345. import re
  346. import glob
  347. import math
  348. import zlib
  349. import time
  350. import json
  351. import enum
  352. import struct
  353. import pathlib
  354. import warnings
  355. import binascii
  356. import tempfile
  357. import datetime
  358. import threading
  359. import collections
  360. import multiprocessing
  361. import concurrent.futures
  362. import numpy
  363. # delay imports: mmap, pprint, fractions, xml, tkinter, matplotlib, lzma, zstd,
  364. # subprocess
  365. __version__ = "2018.06.15"
  366. __docformat__ = "restructuredtext en"
  367. __all__ = (
  368. "imsave",
  369. "imread",
  370. "imshow",
  371. "memmap",
  372. "TiffFile",
  373. "TiffWriter",
  374. "TiffSequence",
  375. # utility functions used by oiffile or czifile
  376. "FileHandle",
  377. "lazyattr",
  378. "natural_sorted",
  379. "decode_lzw",
  380. "stripnull",
  381. "create_output",
  382. "repeat_nd",
  383. "format_size",
  384. "product",
  385. "xml2dict",
  386. )
  387. def imread(files, **kwargs):
  388. """Return image data from TIFF file(s) as numpy array.
  389. Refer to the TiffFile class and member functions for documentation.
  390. Parameters
  391. ----------
  392. files : str, binary stream, or sequence
  393. File name, seekable binary stream, glob pattern, or sequence of
  394. file names.
  395. kwargs : dict
  396. Parameters 'multifile' and 'is_ome' are passed to the TiffFile class.
  397. The 'pattern' parameter is passed to the TiffSequence class.
  398. Other parameters are passed to the asarray functions.
  399. The first image series is returned if no arguments are provided.
  400. Examples
  401. --------
  402. >>> # get image from first page
  403. >>> imsave('temp.tif', numpy.random.rand(3, 4, 301, 219))
  404. >>> im = imread('temp.tif', key=0)
  405. >>> im.shape
  406. (4, 301, 219)
  407. >>> # get images from sequence of files
  408. >>> ims = imread(['temp.tif', 'temp.tif'])
  409. >>> ims.shape
  410. (2, 3, 4, 301, 219)
  411. """
  412. kwargs_file = parse_kwargs(kwargs, "multifile", "is_ome")
  413. kwargs_seq = parse_kwargs(kwargs, "pattern")
  414. if isinstance(files, basestring) and any(i in files for i in "?*"):
  415. files = glob.glob(files)
  416. if not files:
  417. raise ValueError("no files found")
  418. if not hasattr(files, "seek") and len(files) == 1:
  419. files = files[0]
  420. if isinstance(files, basestring) or hasattr(files, "seek"):
  421. with TiffFile(files, **kwargs_file) as tif:
  422. return tif.asarray(**kwargs)
  423. else:
  424. with TiffSequence(files, **kwargs_seq) as imseq:
  425. return imseq.asarray(**kwargs)
  426. def imsave(file, data=None, shape=None, dtype=None, bigsize=2**32 - 2**25, **kwargs):
  427. """Write numpy array to TIFF file.
  428. Refer to the TiffWriter class and member functions for documentation.
  429. Parameters
  430. ----------
  431. file : str or binary stream
  432. File name or writable binary stream, such as an open file or BytesIO.
  433. data : array_like
  434. Input image. The last dimensions are assumed to be image depth,
  435. height, width, and samples.
  436. If None, an empty array of the specified shape and dtype is
  437. saved to file.
  438. Unless 'byteorder' is specified in 'kwargs', the TIFF file byte order
  439. is determined from the data's dtype or the dtype argument.
  440. shape : tuple
  441. If 'data' is None, shape of an empty array to save to the file.
  442. dtype : numpy.dtype
  443. If 'data' is None, data-type of an empty array to save to the file.
  444. bigsize : int
  445. Create a BigTIFF file if the size of data in bytes is larger than
  446. this threshold and 'imagej' or 'truncate' are not enabled.
  447. By default, the threshold is 4 GB minus 32 MB reserved for metadata.
  448. Use the 'bigtiff' parameter to explicitly specify the type of
  449. file created.
  450. kwargs : dict
  451. Parameters 'append', 'byteorder', 'bigtiff', and 'imagej', are passed
  452. to TiffWriter(). Other parameters are passed to TiffWriter.save().
  453. Returns
  454. -------
  455. If the image data are written contiguously, return offset and bytecount
  456. of image data in the file.
  457. Examples
  458. --------
  459. >>> # save a RGB image
  460. >>> data = numpy.random.randint(0, 255, (256, 256, 3), 'uint8')
  461. >>> imsave('temp.tif', data, photometric='rgb')
  462. >>> # save a random array and metadata, using compression
  463. >>> data = numpy.random.rand(2, 5, 3, 301, 219)
  464. >>> imsave('temp.tif', data, compress=6, metadata={'axes': 'TZCYX'})
  465. """
  466. tifargs = parse_kwargs(kwargs, "append", "bigtiff", "byteorder", "imagej")
  467. if data is None:
  468. size = product(shape) * numpy.dtype(dtype).itemsize
  469. byteorder = numpy.dtype(dtype).byteorder
  470. else:
  471. try:
  472. size = data.nbytes
  473. byteorder = data.dtype.byteorder
  474. except Exception:
  475. size = 0
  476. byteorder = None
  477. if (
  478. size > bigsize
  479. and "bigtiff" not in tifargs
  480. and not (tifargs.get("imagej", False) or tifargs.get("truncate", False))
  481. ):
  482. tifargs["bigtiff"] = True
  483. if "byteorder" not in tifargs:
  484. tifargs["byteorder"] = byteorder
  485. with TiffWriter(file, **tifargs) as tif:
  486. return tif.save(data, shape, dtype, **kwargs)
  487. def memmap(filename, shape=None, dtype=None, page=None, series=0, mode="r+", **kwargs):
  488. """Return memory-mapped numpy array stored in TIFF file.
  489. Memory-mapping requires data stored in native byte order, without tiling,
  490. compression, predictors, etc.
  491. If 'shape' and 'dtype' are provided, existing files will be overwritten or
  492. appended to depending on the 'append' parameter.
  493. Otherwise the image data of a specified page or series in an existing
  494. file will be memory-mapped. By default, the image data of the first page
  495. series is memory-mapped.
  496. Call flush() to write any changes in the array to the file.
  497. Raise ValueError if the image data in the file is not memory-mappable.
  498. Parameters
  499. ----------
  500. filename : str
  501. Name of the TIFF file which stores the array.
  502. shape : tuple
  503. Shape of the empty array.
  504. dtype : numpy.dtype
  505. Data-type of the empty array.
  506. page : int
  507. Index of the page which image data to memory-map.
  508. series : int
  509. Index of the page series which image data to memory-map.
  510. mode : {'r+', 'r', 'c'}, optional
  511. The file open mode. Default is to open existing file for reading and
  512. writing ('r+').
  513. kwargs : dict
  514. Additional parameters passed to imsave() or TiffFile().
  515. Examples
  516. --------
  517. >>> # create an empty TIFF file and write to memory-mapped image
  518. >>> im = memmap('temp.tif', shape=(256, 256), dtype='float32')
  519. >>> im[255, 255] = 1.0
  520. >>> im.flush()
  521. >>> im.shape, im.dtype
  522. ((256, 256), dtype('float32'))
  523. >>> del im
  524. >>> # memory-map image data in a TIFF file
  525. >>> im = memmap('temp.tif', page=0)
  526. >>> im[255, 255]
  527. 1.0
  528. """
  529. if shape is not None and dtype is not None:
  530. # create a new, empty array
  531. kwargs.update(
  532. data=None,
  533. shape=shape,
  534. dtype=dtype,
  535. returnoffset=True,
  536. align=TIFF.ALLOCATIONGRANULARITY,
  537. )
  538. result = imsave(filename, **kwargs)
  539. if result is None:
  540. # TODO: fail before creating file or writing data
  541. raise ValueError("image data are not memory-mappable")
  542. offset = result[0]
  543. else:
  544. # use existing file
  545. with TiffFile(filename, **kwargs) as tif:
  546. if page is not None:
  547. page = tif.pages[page]
  548. if not page.is_memmappable:
  549. raise ValueError("image data are not memory-mappable")
  550. offset, _ = page.is_contiguous
  551. shape = page.shape
  552. dtype = page.dtype
  553. else:
  554. series = tif.series[series]
  555. if series.offset is None:
  556. raise ValueError("image data are not memory-mappable")
  557. shape = series.shape
  558. dtype = series.dtype
  559. offset = series.offset
  560. dtype = tif.byteorder + dtype.char
  561. return numpy.memmap(filename, dtype, mode, offset, shape, "C")
  562. class lazyattr(object):
  563. """Attribute whose value is computed on first access."""
  564. # TODO: help() doesn't work
  565. __slots__ = ("func",)
  566. def __init__(self, func):
  567. self.func = func
  568. # self.__name__ = func.__name__
  569. # self.__doc__ = func.__doc__
  570. # self.lock = threading.RLock()
  571. def __get__(self, instance, owner):
  572. # with self.lock:
  573. if instance is None:
  574. return self
  575. try:
  576. value = self.func(instance)
  577. except AttributeError as e:
  578. raise RuntimeError(e)
  579. if value is NotImplemented:
  580. return getattr(super(owner, instance), self.func.__name__)
  581. setattr(instance, self.func.__name__, value)
  582. return value
  583. class TiffWriter(object):
  584. """Write numpy arrays to TIFF file.
  585. TiffWriter instances must be closed using the 'close' method, which is
  586. automatically called when using the 'with' context manager.
  587. TiffWriter's main purpose is saving nD numpy array's as TIFF,
  588. not to create any possible TIFF format. Specifically, JPEG compression,
  589. SubIFDs, ExifIFD, or GPSIFD tags are not supported.
  590. Examples
  591. --------
  592. >>> # successively append images to BigTIFF file
  593. >>> data = numpy.random.rand(2, 5, 3, 301, 219)
  594. >>> with TiffWriter('temp.tif', bigtiff=True) as tif:
  595. ... for i in range(data.shape[0]):
  596. ... tif.save(data[i], compress=6, photometric='minisblack')
  597. """
  598. def __init__(self, file, bigtiff=False, byteorder=None, append=False, imagej=False):
  599. """Open a TIFF file for writing.
  600. An empty TIFF file is created if the file does not exist, else the
  601. file is overwritten with an empty TIFF file unless 'append'
  602. is true. Use bigtiff=True when creating files larger than 4 GB.
  603. Parameters
  604. ----------
  605. file : str, binary stream, or FileHandle
  606. File name or writable binary stream, such as an open file
  607. or BytesIO.
  608. bigtiff : bool
  609. If True, the BigTIFF format is used.
  610. byteorder : {'<', '>', '=', '|'}
  611. The endianness of the data in the file.
  612. By default, this is the system's native byte order.
  613. append : bool
  614. If True and 'file' is an existing standard TIFF file, image data
  615. and tags are appended to the file.
  616. Appending data may corrupt specifically formatted TIFF files
  617. such as LSM, STK, ImageJ, NIH, or FluoView.
  618. imagej : bool
  619. If True, write an ImageJ hyperstack compatible file.
  620. This format can handle data types uint8, uint16, or float32 and
  621. data shapes up to 6 dimensions in TZCYXS order.
  622. RGB images (S=3 or S=4) must be uint8.
  623. ImageJ's default byte order is big-endian but this implementation
  624. uses the system's native byte order by default.
  625. ImageJ does not support BigTIFF format or LZMA compression.
  626. The ImageJ file format is undocumented.
  627. """
  628. if append:
  629. # determine if file is an existing TIFF file that can be extended
  630. try:
  631. with FileHandle(file, mode="rb", size=0) as fh:
  632. pos = fh.tell()
  633. try:
  634. with TiffFile(fh) as tif:
  635. if append != "force" and any(
  636. getattr(tif, "is_" + a)
  637. for a in (
  638. "lsm",
  639. "stk",
  640. "imagej",
  641. "nih",
  642. "fluoview",
  643. "micromanager",
  644. )
  645. ):
  646. raise ValueError("file contains metadata")
  647. byteorder = tif.byteorder
  648. bigtiff = tif.is_bigtiff
  649. self._ifdoffset = tif.pages.next_page_offset
  650. except Exception as e:
  651. raise ValueError("cannot append to file: %s" % str(e))
  652. finally:
  653. fh.seek(pos)
  654. except (IOError, FileNotFoundError):
  655. append = False
  656. if byteorder in (None, "=", "|"):
  657. byteorder = "<" if sys.byteorder == "little" else ">"
  658. elif byteorder not in ("<", ">"):
  659. raise ValueError("invalid byteorder %s" % byteorder)
  660. if imagej and bigtiff:
  661. warnings.warn("writing incompatible BigTIFF ImageJ")
  662. self._byteorder = byteorder
  663. self._imagej = bool(imagej)
  664. self._truncate = False
  665. self._metadata = None
  666. self._colormap = None
  667. self._descriptionoffset = 0
  668. self._descriptionlen = 0
  669. self._descriptionlenoffset = 0
  670. self._tags = None
  671. self._shape = None # normalized shape of data in consecutive pages
  672. self._datashape = None # shape of data in consecutive pages
  673. self._datadtype = None # data type
  674. self._dataoffset = None # offset to data
  675. self._databytecounts = None # byte counts per plane
  676. self._tagoffsets = None # strip or tile offset tag code
  677. if bigtiff:
  678. self._bigtiff = True
  679. self._offsetsize = 8
  680. self._tagsize = 20
  681. self._tagnoformat = "Q"
  682. self._offsetformat = "Q"
  683. self._valueformat = "8s"
  684. else:
  685. self._bigtiff = False
  686. self._offsetsize = 4
  687. self._tagsize = 12
  688. self._tagnoformat = "H"
  689. self._offsetformat = "I"
  690. self._valueformat = "4s"
  691. if append:
  692. self._fh = FileHandle(file, mode="r+b", size=0)
  693. self._fh.seek(0, 2)
  694. else:
  695. self._fh = FileHandle(file, mode="wb", size=0)
  696. self._fh.write({"<": b"II", ">": b"MM"}[byteorder])
  697. if bigtiff:
  698. self._fh.write(struct.pack(byteorder + "HHH", 43, 8, 0))
  699. else:
  700. self._fh.write(struct.pack(byteorder + "H", 42))
  701. # first IFD
  702. self._ifdoffset = self._fh.tell()
  703. self._fh.write(struct.pack(byteorder + self._offsetformat, 0))
  704. def save(
  705. self,
  706. data=None,
  707. shape=None,
  708. dtype=None,
  709. returnoffset=False,
  710. photometric=None,
  711. planarconfig=None,
  712. tile=None,
  713. contiguous=True,
  714. align=16,
  715. truncate=False,
  716. compress=0,
  717. rowsperstrip=None,
  718. predictor=False,
  719. colormap=None,
  720. description=None,
  721. datetime=None,
  722. resolution=None,
  723. software="tifffile.py",
  724. metadata={},
  725. ijmetadata=None,
  726. extratags=(),
  727. ):
  728. """Write numpy array and tags to TIFF file.
  729. The data shape's last dimensions are assumed to be image depth,
  730. height (length), width, and samples.
  731. If a colormap is provided, the data's dtype must be uint8 or uint16
  732. and the data values are indices into the last dimension of the
  733. colormap.
  734. If 'shape' and 'dtype' are specified, an empty array is saved.
  735. This option cannot be used with compression or multiple tiles.
  736. Image data are written uncompressed in one strip per plane by default.
  737. Dimensions larger than 2 to 4 (depending on photometric mode, planar
  738. configuration, and SGI mode) are flattened and saved as separate pages.
  739. The SampleFormat and BitsPerSample tags are derived from the data type.
  740. Parameters
  741. ----------
  742. data : numpy.ndarray or None
  743. Input image array.
  744. shape : tuple or None
  745. Shape of the empty array to save. Used only if 'data' is None.
  746. dtype : numpy.dtype or None
  747. Data-type of the empty array to save. Used only if 'data' is None.
  748. returnoffset : bool
  749. If True and the image data in the file is memory-mappable, return
  750. the offset and number of bytes of the image data in the file.
  751. photometric : {'MINISBLACK', 'MINISWHITE', 'RGB', 'PALETTE', 'CFA'}
  752. The color space of the image data.
  753. By default, this setting is inferred from the data shape and the
  754. value of colormap.
  755. For CFA images, DNG tags must be specified in 'extratags'.
  756. planarconfig : {'CONTIG', 'SEPARATE'}
  757. Specifies if samples are stored contiguous or in separate planes.
  758. By default, this setting is inferred from the data shape.
  759. If this parameter is set, extra samples are used to store grayscale
  760. images.
  761. 'CONTIG': last dimension contains samples.
  762. 'SEPARATE': third last dimension contains samples.
  763. tile : tuple of int
  764. The shape (depth, length, width) of image tiles to write.
  765. If None (default), image data are written in strips.
  766. The tile length and width must be a multiple of 16.
  767. If the tile depth is provided, the SGI ImageDepth and TileDepth
  768. tags are used to save volume data.
  769. Unless a single tile is used, tiles cannot be used to write
  770. contiguous files.
  771. Few software can read the SGI format, e.g. MeVisLab.
  772. contiguous : bool
  773. If True (default) and the data and parameters are compatible with
  774. previous ones, if any, the image data are stored contiguously after
  775. the previous one. Parameters 'photometric' and 'planarconfig'
  776. are ignored. Parameters 'description', datetime', and 'extratags'
  777. are written to the first page of a contiguous series only.
  778. align : int
  779. Byte boundary on which to align the image data in the file.
  780. Default 16. Use mmap.ALLOCATIONGRANULARITY for memory-mapped data.
  781. Following contiguous writes are not aligned.
  782. truncate : bool
  783. If True, only write the first page including shape metadata if
  784. possible (uncompressed, contiguous, not tiled).
  785. Other TIFF readers will only be able to read part of the data.
  786. compress : int or 'LZMA', 'ZSTD'
  787. Values from 0 to 9 controlling the level of zlib compression.
  788. If 0 (default), data are written uncompressed.
  789. Compression cannot be used to write contiguous files.
  790. If 'LZMA' or 'ZSTD', LZMA or ZSTD compression is used, which is
  791. not available on all platforms.
  792. rowsperstrip : int
  793. The number of rows per strip used for compression.
  794. Uncompressed data are written in one strip per plane.
  795. predictor : bool
  796. If True, apply horizontal differencing to integer type images
  797. before compression.
  798. colormap : numpy.ndarray
  799. RGB color values for the corresponding data value.
  800. Must be of shape (3, 2**(data.itemsize*8)) and dtype uint16.
  801. description : str
  802. The subject of the image. Must be 7-bit ASCII. Cannot be used with
  803. the ImageJ format. Saved with the first page only.
  804. datetime : datetime
  805. Date and time of image creation in '%Y:%m:%d %H:%M:%S' format.
  806. If None (default), the current date and time is used.
  807. Saved with the first page only.
  808. resolution : (float, float[, str]) or ((int, int), (int, int)[, str])
  809. X and Y resolutions in pixels per resolution unit as float or
  810. rational numbers. A third, optional parameter specifies the
  811. resolution unit, which must be None (default for ImageJ),
  812. 'INCH' (default), or 'CENTIMETER'.
  813. software : str
  814. Name of the software used to create the file. Must be 7-bit ASCII.
  815. Saved with the first page only.
  816. metadata : dict
  817. Additional meta data to be saved along with shape information
  818. in JSON or ImageJ formats in an ImageDescription tag.
  819. If None, do not write a second ImageDescription tag.
  820. Strings must be 7-bit ASCII. Saved with the first page only.
  821. ijmetadata : dict
  822. Additional meta data to be saved in application specific
  823. IJMetadata and IJMetadataByteCounts tags. Refer to the
  824. imagej_metadata_tags function for valid keys and values.
  825. Saved with the first page only.
  826. extratags : sequence of tuples
  827. Additional tags as [(code, dtype, count, value, writeonce)].
  828. code : int
  829. The TIFF tag Id.
  830. dtype : str
  831. Data type of items in 'value' in Python struct format.
  832. One of B, s, H, I, 2I, b, h, i, 2i, f, d, Q, or q.
  833. count : int
  834. Number of data values. Not used for string or byte string
  835. values.
  836. value : sequence
  837. 'Count' values compatible with 'dtype'.
  838. Byte strings must contain count values of dtype packed as
  839. binary data.
  840. writeonce : bool
  841. If True, the tag is written to the first page only.
  842. """
  843. # TODO: refactor this function
  844. fh = self._fh
  845. byteorder = self._byteorder
  846. if data is None:
  847. if compress:
  848. raise ValueError("cannot save compressed empty file")
  849. datashape = shape
  850. datadtype = numpy.dtype(dtype).newbyteorder(byteorder)
  851. datadtypechar = datadtype.char
  852. else:
  853. data = numpy.asarray(data, byteorder + data.dtype.char, "C")
  854. if data.size == 0:
  855. raise ValueError("cannot save empty array")
  856. datashape = data.shape
  857. datadtype = data.dtype
  858. datadtypechar = data.dtype.char
  859. returnoffset = returnoffset and datadtype.isnative
  860. bilevel = datadtypechar == "?"
  861. if bilevel:
  862. index = -1 if datashape[-1] > 1 else -2
  863. datasize = product(datashape[:index])
  864. if datashape[index] % 8:
  865. datasize *= datashape[index] // 8 + 1
  866. else:
  867. datasize *= datashape[index] // 8
  868. else:
  869. datasize = product(datashape) * datadtype.itemsize
  870. # just append contiguous data if possible
  871. self._truncate = bool(truncate)
  872. if self._datashape:
  873. if (
  874. not contiguous
  875. or self._datashape[1:] != datashape
  876. or self._datadtype != datadtype
  877. or (compress and self._tags)
  878. or tile
  879. or not numpy.array_equal(colormap, self._colormap)
  880. ):
  881. # incompatible shape, dtype, compression mode, or colormap
  882. self._write_remaining_pages()
  883. self._write_image_description()
  884. self._truncate = False
  885. self._descriptionoffset = 0
  886. self._descriptionlenoffset = 0
  887. self._datashape = None
  888. self._colormap = None
  889. if self._imagej:
  890. raise ValueError("ImageJ does not support non-contiguous data")
  891. else:
  892. # consecutive mode
  893. self._datashape = (self._datashape[0] + 1,) + datashape
  894. if not compress:
  895. # write contiguous data, write IFDs/tags later
  896. offset = fh.tell()
  897. if data is None:
  898. fh.write_empty(datasize)
  899. else:
  900. fh.write_array(data)
  901. if returnoffset:
  902. return offset, datasize
  903. return
  904. input_shape = datashape
  905. tagnoformat = self._tagnoformat
  906. valueformat = self._valueformat
  907. offsetformat = self._offsetformat
  908. offsetsize = self._offsetsize
  909. tagsize = self._tagsize
  910. MINISBLACK = TIFF.PHOTOMETRIC.MINISBLACK
  911. RGB = TIFF.PHOTOMETRIC.RGB
  912. CFA = TIFF.PHOTOMETRIC.CFA
  913. PALETTE = TIFF.PHOTOMETRIC.PALETTE
  914. CONTIG = TIFF.PLANARCONFIG.CONTIG
  915. SEPARATE = TIFF.PLANARCONFIG.SEPARATE
  916. # parse input
  917. if photometric is not None:
  918. photometric = enumarg(TIFF.PHOTOMETRIC, photometric)
  919. if planarconfig:
  920. planarconfig = enumarg(TIFF.PLANARCONFIG, planarconfig)
  921. if not compress:
  922. compress = False
  923. compresstag = 1
  924. predictor = False
  925. else:
  926. if isinstance(compress, (tuple, list)):
  927. compress, compresslevel = compress
  928. elif isinstance(compress, int):
  929. compress, compresslevel = "ADOBE_DEFLATE", int(compress)
  930. if not 0 <= compresslevel <= 9:
  931. raise ValueError("invalid compression level %s" % compress)
  932. else:
  933. compresslevel = None
  934. compress = compress.upper()
  935. compresstag = enumarg(TIFF.COMPRESSION, compress)
  936. # prepare ImageJ format
  937. if self._imagej:
  938. if compress in ("LZMA", "ZSTD"):
  939. raise ValueError("ImageJ cannot handle LZMA or ZSTD compression")
  940. if description:
  941. warnings.warn("not writing description to ImageJ file")
  942. description = None
  943. volume = False
  944. if datadtypechar not in "BHhf":
  945. raise ValueError("ImageJ does not support data type %s" % datadtypechar)
  946. ijrgb = photometric == RGB if photometric else None
  947. if datadtypechar not in "B":
  948. ijrgb = False
  949. ijshape = imagej_shape(datashape, ijrgb)
  950. if ijshape[-1] in (3, 4):
  951. photometric = RGB
  952. if datadtypechar not in "B":
  953. raise ValueError(
  954. "ImageJ does not support data type %s "
  955. "for RGB" % datadtypechar
  956. )
  957. elif photometric is None:
  958. photometric = MINISBLACK
  959. planarconfig = None
  960. if planarconfig == SEPARATE:
  961. raise ValueError("ImageJ does not support planar images")
  962. else:
  963. planarconfig = CONTIG if ijrgb else None
  964. # define compress function
  965. if compress:
  966. if compresslevel is None:
  967. compressor, compresslevel = TIFF.COMPESSORS[compresstag]
  968. else:
  969. compressor, _ = TIFF.COMPESSORS[compresstag]
  970. compresslevel = int(compresslevel)
  971. if predictor:
  972. if datadtype.kind not in "iu":
  973. raise ValueError("prediction not implemented for %s" % datadtype)
  974. def compress(data, level=compresslevel):
  975. # horizontal differencing
  976. diff = numpy.diff(data, axis=-2)
  977. data = numpy.insert(diff, 0, data[..., 0, :], axis=-2)
  978. return compressor(data, level)
  979. else:
  980. def compress(data, level=compresslevel):
  981. return compressor(data, level)
  982. # verify colormap and indices
  983. if colormap is not None:
  984. if datadtypechar not in "BH":
  985. raise ValueError("invalid data dtype for palette mode")
  986. colormap = numpy.asarray(colormap, dtype=byteorder + "H")
  987. if colormap.shape != (3, 2 ** (datadtype.itemsize * 8)):
  988. raise ValueError("invalid color map shape")
  989. self._colormap = colormap
  990. # verify tile shape
  991. if tile:
  992. tile = tuple(int(i) for i in tile[:3])
  993. volume = len(tile) == 3
  994. if (
  995. len(tile) < 2
  996. or tile[-1] % 16
  997. or tile[-2] % 16
  998. or any(i < 1 for i in tile)
  999. ):
  1000. raise ValueError("invalid tile shape")
  1001. else:
  1002. tile = ()
  1003. volume = False
  1004. # normalize data shape to 5D or 6D, depending on volume:
  1005. # (pages, planar_samples, [depth,] height, width, contig_samples)
  1006. datashape = reshape_nd(datashape, 3 if photometric == RGB else 2)
  1007. shape = datashape
  1008. ndim = len(datashape)
  1009. samplesperpixel = 1
  1010. extrasamples = 0
  1011. if volume and ndim < 3:
  1012. volume = False
  1013. if colormap is not None:
  1014. photometric = PALETTE
  1015. planarconfig = None
  1016. if photometric is None:
  1017. photometric = MINISBLACK
  1018. if bilevel:
  1019. photometric = TIFF.PHOTOMETRIC.MINISWHITE
  1020. elif planarconfig == CONTIG:
  1021. if ndim > 2 and shape[-1] in (3, 4):
  1022. photometric = RGB
  1023. elif planarconfig == SEPARATE:
  1024. if volume and ndim > 3 and shape[-4] in (3, 4):
  1025. photometric = RGB
  1026. elif ndim > 2 and shape[-3] in (3, 4):
  1027. photometric = RGB
  1028. elif ndim > 2 and shape[-1] in (3, 4):
  1029. photometric = RGB
  1030. elif self._imagej:
  1031. photometric = MINISBLACK
  1032. elif volume and ndim > 3 and shape[-4] in (3, 4):
  1033. photometric = RGB
  1034. elif ndim > 2 and shape[-3] in (3, 4):
  1035. photometric = RGB
  1036. if planarconfig and len(shape) <= (3 if volume else 2):
  1037. planarconfig = None
  1038. photometric = MINISBLACK
  1039. if photometric == RGB:
  1040. if len(shape) < 3:
  1041. raise ValueError("not a RGB(A) image")
  1042. if len(shape) < 4:
  1043. volume = False
  1044. if planarconfig is None:
  1045. if shape[-1] in (3, 4):
  1046. planarconfig = CONTIG
  1047. elif shape[-4 if volume else -3] in (3, 4):
  1048. planarconfig = SEPARATE
  1049. elif shape[-1] > shape[-4 if volume else -3]:
  1050. planarconfig = SEPARATE
  1051. else:
  1052. planarconfig = CONTIG
  1053. if planarconfig == CONTIG:
  1054. datashape = (-1, 1) + shape[(-4 if volume else -3) :]
  1055. samplesperpixel = datashape[-1]
  1056. else:
  1057. datashape = (-1,) + shape[(-4 if volume else -3) :] + (1,)
  1058. samplesperpixel = datashape[1]
  1059. if samplesperpixel > 3:
  1060. extrasamples = samplesperpixel - 3
  1061. elif photometric == CFA:
  1062. if len(shape) != 2:
  1063. raise ValueError("invalid CFA image")
  1064. volume = False
  1065. planarconfig = None
  1066. datashape = (-1, 1) + shape[-2:] + (1,)
  1067. if 50706 not in (et[0] for et in extratags):
  1068. raise ValueError("must specify DNG tags for CFA image")
  1069. elif planarconfig and len(shape) > (3 if volume else 2):
  1070. if planarconfig == CONTIG:
  1071. datashape = (-1, 1) + shape[(-4 if volume else -3) :]
  1072. samplesperpixel = datashape[-1]
  1073. else:
  1074. datashape = (-1,) + shape[(-4 if volume else -3) :] + (1,)
  1075. samplesperpixel = datashape[1]
  1076. extrasamples = samplesperpixel - 1
  1077. else:
  1078. planarconfig = None
  1079. # remove trailing 1s
  1080. while len(shape) > 2 and shape[-1] == 1:
  1081. shape = shape[:-1]
  1082. if len(shape) < 3:
  1083. volume = False
  1084. datashape = (-1, 1) + shape[(-3 if volume else -2) :] + (1,)
  1085. # normalize shape to 6D
  1086. assert len(datashape) in (5, 6)
  1087. if len(datashape) == 5:
  1088. datashape = datashape[:2] + (1,) + datashape[2:]
  1089. if datashape[0] == -1:
  1090. s0 = product(input_shape) // product(datashape[1:])
  1091. datashape = (s0,) + datashape[1:]
  1092. shape = datashape
  1093. if data is not None:
  1094. data = data.reshape(shape)
  1095. if tile and not volume:
  1096. tile = (1, tile[-2], tile[-1])
  1097. if photometric == PALETTE:
  1098. if samplesperpixel != 1 or extrasamples or shape[1] != 1 or shape[-1] != 1:
  1099. raise ValueError("invalid data shape for palette mode")
  1100. if photometric == RGB and samplesperpixel == 2:
  1101. raise ValueError("not a RGB image (samplesperpixel=2)")
  1102. if bilevel:
  1103. if compress:
  1104. raise ValueError("cannot save compressed bilevel image")
  1105. if tile:
  1106. raise ValueError("cannot save tiled bilevel image")
  1107. if photometric not in (0, 1):
  1108. raise ValueError("cannot save bilevel image as %s" % str(photometric))
  1109. datashape = list(datashape)
  1110. if datashape[-2] % 8:
  1111. datashape[-2] = datashape[-2] // 8 + 1
  1112. else:
  1113. datashape[-2] = datashape[-2] // 8
  1114. datashape = tuple(datashape)
  1115. assert datasize == product(datashape)
  1116. if data is not None:
  1117. data = numpy.packbits(data, axis=-2)
  1118. assert datashape[-2] == data.shape[-2]
  1119. bytestr = (
  1120. bytes
  1121. if sys.version[0] == "2"
  1122. else (lambda x: bytes(x, "ascii") if isinstance(x, str) else x)
  1123. )
  1124. tags = [] # list of (code, ifdentry, ifdvalue, writeonce)
  1125. strip_or_tile = "Tile" if tile else "Strip"
  1126. tagbytecounts = TIFF.TAG_NAMES[strip_or_tile + "ByteCounts"]
  1127. tag_offsets = TIFF.TAG_NAMES[strip_or_tile + "Offsets"]
  1128. self._tagoffsets = tag_offsets
  1129. def pack(fmt, *val):
  1130. return struct.pack(byteorder + fmt, *val)
  1131. def addtag(code, dtype, count, value, writeonce=False):
  1132. # Compute ifdentry & ifdvalue bytes from code, dtype, count, value
  1133. # Append (code, ifdentry, ifdvalue, writeonce) to tags list
  1134. code = int(TIFF.TAG_NAMES.get(code, code))
  1135. try:
  1136. tifftype = TIFF.DATA_DTYPES[dtype]
  1137. except KeyError:
  1138. raise ValueError("unknown dtype %s" % dtype)
  1139. rawcount = count
  1140. if dtype == "s":
  1141. # strings
  1142. value = bytestr(value) + b"\0"
  1143. count = rawcount = len(value)
  1144. rawcount = value.find(b"\0\0")
  1145. if rawcount < 0:
  1146. rawcount = count
  1147. else:
  1148. rawcount += 1 # length of string without buffer
  1149. value = (value,)
  1150. elif isinstance(value, bytes):
  1151. # packed binary data
  1152. dtsize = struct.calcsize(dtype)
  1153. if len(value) % dtsize:
  1154. raise ValueError("invalid packed binary data")
  1155. count = len(value) // dtsize
  1156. if len(dtype) > 1:
  1157. count *= int(dtype[:-1])
  1158. dtype = dtype[-1]
  1159. ifdentry = [pack("HH", code, tifftype), pack(offsetformat, rawcount)]
  1160. ifdvalue = None
  1161. if struct.calcsize(dtype) * count <= offsetsize:
  1162. # value(s) can be written directly
  1163. if isinstance(value, bytes):
  1164. ifdentry.append(pack(valueformat, value))
  1165. elif count == 1:
  1166. if isinstance(value, (tuple, list, numpy.ndarray)):
  1167. value = value[0]
  1168. ifdentry.append(pack(valueformat, pack(dtype, value)))
  1169. else:
  1170. ifdentry.append(pack(valueformat, pack(str(count) + dtype, *value)))
  1171. else:
  1172. # use offset to value(s)
  1173. ifdentry.append(pack(offsetformat, 0))
  1174. if isinstance(value, bytes):
  1175. ifdvalue = value
  1176. elif isinstance(value, numpy.ndarray):
  1177. assert value.size == count
  1178. assert value.dtype.char == dtype
  1179. ifdvalue = value.tostring()
  1180. elif isinstance(value, (tuple, list)):
  1181. ifdvalue = pack(str(count) + dtype, *value)
  1182. else:
  1183. ifdvalue = pack(dtype, value)
  1184. tags.append((code, b"".join(ifdentry), ifdvalue, writeonce))
  1185. def rational(arg, max_denominator=1000000):
  1186. """ "Return nominator and denominator from float or two integers."""
  1187. from fractions import Fraction # delayed import
  1188. try:
  1189. f = Fraction.from_float(arg)
  1190. except TypeError:
  1191. f = Fraction(arg[0], arg[1])
  1192. f = f.limit_denominator(max_denominator)
  1193. return f.numerator, f.denominator
  1194. if description:
  1195. # user provided description
  1196. addtag("ImageDescription", "s", 0, description, writeonce=True)
  1197. # write shape and metadata to ImageDescription
  1198. self._metadata = {} if not metadata else metadata.copy()
  1199. if self._imagej:
  1200. description = imagej_description(
  1201. input_shape,
  1202. shape[-1] in (3, 4),
  1203. self._colormap is not None,
  1204. **self._metadata,
  1205. )
  1206. elif metadata or metadata == {}:
  1207. if self._truncate:
  1208. self._metadata.update(truncated=True)
  1209. description = json_description(input_shape, **self._metadata)
  1210. else:
  1211. description = None
  1212. if description:
  1213. # add 64 bytes buffer
  1214. # the image description might be updated later with the final shape
  1215. description = str2bytes(description, "ascii")
  1216. description += b"\0" * 64
  1217. self._descriptionlen = len(description)
  1218. addtag("ImageDescription", "s", 0, description, writeonce=True)
  1219. if software:
  1220. addtag("Software", "s", 0, software, writeonce=True)
  1221. if datetime is None:
  1222. datetime = self._now()
  1223. addtag(
  1224. "DateTime", "s", 0, datetime.strftime("%Y:%m:%d %H:%M:%S"), writeonce=True
  1225. )
  1226. addtag("Compression", "H", 1, compresstag)
  1227. if predictor:
  1228. addtag("Predictor", "H", 1, 2)
  1229. addtag("ImageWidth", "I", 1, shape[-2])
  1230. addtag("ImageLength", "I", 1, shape[-3])
  1231. if tile:
  1232. addtag("TileWidth", "I", 1, tile[-1])
  1233. addtag("TileLength", "I", 1, tile[-2])
  1234. if tile[0] > 1:
  1235. addtag("ImageDepth", "I", 1, shape[-4])
  1236. addtag("TileDepth", "I", 1, tile[0])
  1237. addtag("NewSubfileType", "I", 1, 0)
  1238. if not bilevel:
  1239. sampleformat = {"u": 1, "i": 2, "f": 3, "c": 6}[datadtype.kind]
  1240. addtag(
  1241. "SampleFormat", "H", samplesperpixel, (sampleformat,) * samplesperpixel
  1242. )
  1243. addtag("PhotometricInterpretation", "H", 1, photometric.value)
  1244. if colormap is not None:
  1245. addtag("ColorMap", "H", colormap.size, colormap)
  1246. addtag("SamplesPerPixel", "H", 1, samplesperpixel)
  1247. if bilevel:
  1248. pass
  1249. elif planarconfig and samplesperpixel > 1:
  1250. addtag("PlanarConfiguration", "H", 1, planarconfig.value)
  1251. addtag(
  1252. "BitsPerSample",
  1253. "H",
  1254. samplesperpixel,
  1255. (datadtype.itemsize * 8,) * samplesperpixel,
  1256. )
  1257. else:
  1258. addtag("BitsPerSample", "H", 1, datadtype.itemsize * 8)
  1259. if extrasamples:
  1260. if photometric == RGB and extrasamples == 1:
  1261. addtag("ExtraSamples", "H", 1, 1) # associated alpha channel
  1262. else:
  1263. addtag("ExtraSamples", "H", extrasamples, (0,) * extrasamples)
  1264. if resolution is not None:
  1265. addtag("XResolution", "2I", 1, rational(resolution[0]))
  1266. addtag("YResolution", "2I", 1, rational(resolution[1]))
  1267. if len(resolution) > 2:
  1268. unit = resolution[2]
  1269. unit = 1 if unit is None else enumarg(TIFF.RESUNIT, unit)
  1270. elif self._imagej:
  1271. unit = 1
  1272. else:
  1273. unit = 2
  1274. addtag("ResolutionUnit", "H", 1, unit)
  1275. elif not self._imagej:
  1276. addtag("XResolution", "2I", 1, (1, 1))
  1277. addtag("YResolution", "2I", 1, (1, 1))
  1278. addtag("ResolutionUnit", "H", 1, 1)
  1279. if ijmetadata:
  1280. for t in imagej_metadata_tags(ijmetadata, byteorder):
  1281. addtag(*t)
  1282. contiguous = not compress
  1283. if tile:
  1284. # one chunk per tile per plane
  1285. tiles = (
  1286. (shape[2] + tile[0] - 1) // tile[0],
  1287. (shape[3] + tile[1] - 1) // tile[1],
  1288. (shape[4] + tile[2] - 1) // tile[2],
  1289. )
  1290. numtiles = product(tiles) * shape[1]
  1291. stripbytecounts = [
  1292. product(tile) * shape[-1] * datadtype.itemsize
  1293. ] * numtiles
  1294. addtag(tagbytecounts, offsetformat, numtiles, stripbytecounts)
  1295. addtag(tag_offsets, offsetformat, numtiles, [0] * numtiles)
  1296. contiguous = contiguous and product(tiles) == 1
  1297. if not contiguous:
  1298. # allocate tile buffer
  1299. chunk = numpy.empty(tile + (shape[-1],), dtype=datadtype)
  1300. elif contiguous:
  1301. # one strip per plane
  1302. if bilevel:
  1303. stripbytecounts = [product(datashape[2:])] * shape[1]
  1304. else:
  1305. stripbytecounts = [product(datashape[2:]) * datadtype.itemsize] * shape[
  1306. 1
  1307. ]
  1308. addtag(tagbytecounts, offsetformat, shape[1], stripbytecounts)
  1309. addtag(tag_offsets, offsetformat, shape[1], [0] * shape[1])
  1310. addtag("RowsPerStrip", "I", 1, shape[-3])
  1311. else:
  1312. # compress rowsperstrip or ~64 KB chunks
  1313. rowsize = product(shape[-2:]) * datadtype.itemsize
  1314. if rowsperstrip is None:
  1315. rowsperstrip = 65536 // rowsize
  1316. if rowsperstrip < 1:
  1317. rowsperstrip = 1
  1318. elif rowsperstrip > shape[-3]:
  1319. rowsperstrip = shape[-3]
  1320. addtag("RowsPerStrip", "I", 1, rowsperstrip)
  1321. numstrips = (shape[-3] + rowsperstrip - 1) // rowsperstrip
  1322. numstrips *= shape[1]
  1323. stripbytecounts = [0] * numstrips
  1324. addtag(tagbytecounts, offsetformat, numstrips, [0] * numstrips)
  1325. addtag(tag_offsets, offsetformat, numstrips, [0] * numstrips)
  1326. if data is None and not contiguous:
  1327. raise ValueError("cannot write non-contiguous empty file")
  1328. # add extra tags from user
  1329. for t in extratags:
  1330. addtag(*t)
  1331. # TODO: check TIFFReadDirectoryCheckOrder warning in files containing
  1332. # multiple tags of same code
  1333. # the entries in an IFD must be sorted in ascending order by tag code
  1334. tags = sorted(tags, key=lambda x: x[0])
  1335. if not (self._bigtiff or self._imagej) and (fh.tell() + datasize > 2**31 - 1):
  1336. raise ValueError("data too large for standard TIFF file")
  1337. # if not compressed or multi-tiled, write the first IFD and then
  1338. # all data contiguously; else, write all IFDs and data interleaved
  1339. for pageindex in range(1 if contiguous else shape[0]):
  1340. # update pointer at ifd_offset
  1341. pos = fh.tell()
  1342. if pos % 2:
  1343. # location of IFD must begin on a word boundary
  1344. fh.write(b"\0")
  1345. pos += 1
  1346. fh.seek(self._ifdoffset)
  1347. fh.write(pack(offsetformat, pos))
  1348. fh.seek(pos)
  1349. # write ifdentries
  1350. fh.write(pack(tagnoformat, len(tags)))
  1351. tag_offset = fh.tell()
  1352. fh.write(b"".join(t[1] for t in tags))
  1353. self._ifdoffset = fh.tell()
  1354. fh.write(pack(offsetformat, 0)) # offset to next IFD
  1355. # write tag values and patch offsets in ifdentries, if necessary
  1356. for tagindex, tag in enumerate(tags):
  1357. if tag[2]:
  1358. pos = fh.tell()
  1359. if pos % 2:
  1360. # tag value is expected to begin on word boundary
  1361. fh.write(b"\0")
  1362. pos += 1
  1363. fh.seek(tag_offset + tagindex * tagsize + offsetsize + 4)
  1364. fh.write(pack(offsetformat, pos))
  1365. fh.seek(pos)
  1366. if tag[0] == tag_offsets:
  1367. stripoffsetsoffset = pos
  1368. elif tag[0] == tagbytecounts:
  1369. strip_bytecounts_offset = pos
  1370. elif tag[0] == 270 and tag[2].endswith(b"\0\0\0\0"):
  1371. # image description buffer
  1372. self._descriptionoffset = pos
  1373. self._descriptionlenoffset = tag_offset + tagindex * tagsize + 4
  1374. fh.write(tag[2])
  1375. # write image data
  1376. data_offset = fh.tell()
  1377. skip = align - data_offset % align
  1378. fh.seek(skip, 1)
  1379. data_offset += skip
  1380. if contiguous:
  1381. if data is None:
  1382. fh.write_empty(datasize)
  1383. else:
  1384. fh.write_array(data)
  1385. elif tile:
  1386. if data is None:
  1387. fh.write_empty(numtiles * stripbytecounts[0])
  1388. else:
  1389. stripindex = 0
  1390. for plane in data[pageindex]:
  1391. for tz in range(tiles[0]):
  1392. for ty in range(tiles[1]):
  1393. for tx in range(tiles[2]):
  1394. c0 = min(tile[0], shape[2] - tz * tile[0])
  1395. c1 = min(tile[1], shape[3] - ty * tile[1])
  1396. c2 = min(tile[2], shape[4] - tx * tile[2])
  1397. chunk[c0:, c1:, c2:] = 0
  1398. chunk[:c0, :c1, :c2] = plane[
  1399. tz * tile[0] : tz * tile[0] + c0,
  1400. ty * tile[1] : ty * tile[1] + c1,
  1401. tx * tile[2] : tx * tile[2] + c2,
  1402. ]
  1403. if compress:
  1404. t = compress(chunk)
  1405. fh.write(t)
  1406. stripbytecounts[stripindex] = len(t)
  1407. stripindex += 1
  1408. else:
  1409. fh.write_array(chunk)
  1410. fh.flush()
  1411. elif compress:
  1412. # write one strip per rowsperstrip
  1413. assert data.shape[2] == 1 # not handling depth
  1414. numstrips = (shape[-3] + rowsperstrip - 1) // rowsperstrip
  1415. stripindex = 0
  1416. for plane in data[pageindex]:
  1417. for i in range(numstrips):
  1418. strip = plane[0, i * rowsperstrip : (i + 1) * rowsperstrip]
  1419. strip = compress(strip)
  1420. fh.write(strip)
  1421. stripbytecounts[stripindex] = len(strip)
  1422. stripindex += 1
  1423. # update strip/tile offsets and bytecounts if necessary
  1424. pos = fh.tell()
  1425. for tagindex, tag in enumerate(tags):
  1426. if tag[0] == tag_offsets: # strip/tile offsets
  1427. if tag[2]:
  1428. fh.seek(stripoffsetsoffset)
  1429. strip_offset = data_offset
  1430. for size in stripbytecounts:
  1431. fh.write(pack(offsetformat, strip_offset))
  1432. strip_offset += size
  1433. else:
  1434. fh.seek(tag_offset + tagindex * tagsize + offsetsize + 4)
  1435. fh.write(pack(offsetformat, data_offset))
  1436. elif tag[0] == tagbytecounts: # strip/tile bytecounts
  1437. if compress:
  1438. if tag[2]:
  1439. fh.seek(strip_bytecounts_offset)
  1440. for size in stripbytecounts:
  1441. fh.write(pack(offsetformat, size))
  1442. else:
  1443. fh.seek(tag_offset + tagindex * tagsize + offsetsize + 4)
  1444. fh.write(pack(offsetformat, stripbytecounts[0]))
  1445. break
  1446. fh.seek(pos)
  1447. fh.flush()
  1448. # remove tags that should be written only once
  1449. if pageindex == 0:
  1450. tags = [tag for tag in tags if not tag[-1]]
  1451. self._shape = shape
  1452. self._datashape = (1,) + input_shape
  1453. self._datadtype = datadtype
  1454. self._dataoffset = data_offset
  1455. self._databytecounts = stripbytecounts
  1456. if contiguous:
  1457. # write remaining IFDs/tags later
  1458. self._tags = tags
  1459. # return offset and size of image data
  1460. if returnoffset:
  1461. return data_offset, sum(stripbytecounts)
  1462. def _write_remaining_pages(self):
  1463. """Write outstanding IFDs and tags to file."""
  1464. if not self._tags or self._truncate:
  1465. return
  1466. fh = self._fh
  1467. fhpos = fh.tell()
  1468. if fhpos % 2:
  1469. fh.write(b"\0")
  1470. fhpos += 1
  1471. byteorder = self._byteorder
  1472. offsetformat = self._offsetformat
  1473. offsetsize = self._offsetsize
  1474. tagnoformat = self._tagnoformat
  1475. tagsize = self._tagsize
  1476. dataoffset = self._dataoffset
  1477. pagedatasize = sum(self._databytecounts)
  1478. pageno = self._shape[0] * self._datashape[0] - 1
  1479. def pack(fmt, *val):
  1480. return struct.pack(byteorder + fmt, *val)
  1481. # construct template IFD in memory
  1482. # need to patch offsets to next IFD and data before writing to disk
  1483. ifd = io.BytesIO()
  1484. ifd.write(pack(tagnoformat, len(self._tags)))
  1485. tagoffset = ifd.tell()
  1486. ifd.write(b"".join(t[1] for t in self._tags))
  1487. ifdoffset = ifd.tell()
  1488. ifd.write(pack(offsetformat, 0)) # offset to next IFD
  1489. # tag values
  1490. for tagindex, tag in enumerate(self._tags):
  1491. offset2value = tagoffset + tagindex * tagsize + offsetsize + 4
  1492. if tag[2]:
  1493. pos = ifd.tell()
  1494. if pos % 2: # tag value is expected to begin on word boundary
  1495. ifd.write(b"\0")
  1496. pos += 1
  1497. ifd.seek(offset2value)
  1498. try:
  1499. ifd.write(pack(offsetformat, pos + fhpos))
  1500. except Exception: # struct.error
  1501. if self._imagej:
  1502. warnings.warn("truncating ImageJ file")
  1503. self._truncate = True
  1504. return
  1505. raise ValueError("data too large for non-BigTIFF file")
  1506. ifd.seek(pos)
  1507. ifd.write(tag[2])
  1508. if tag[0] == self._tagoffsets:
  1509. # save strip/tile offsets for later updates
  1510. stripoffset2offset = offset2value
  1511. stripoffset2value = pos
  1512. elif tag[0] == self._tagoffsets:
  1513. # save strip/tile offsets for later updates
  1514. stripoffset2offset = None
  1515. stripoffset2value = offset2value
  1516. # size to word boundary
  1517. if ifd.tell() % 2:
  1518. ifd.write(b"\0")
  1519. # check if all IFDs fit in file
  1520. pos = fh.tell()
  1521. if not self._bigtiff and pos + ifd.tell() * pageno > 2**32 - 256:
  1522. if self._imagej:
  1523. warnings.warn("truncating ImageJ file")
  1524. self._truncate = True
  1525. return
  1526. raise ValueError("data too large for non-BigTIFF file")
  1527. # TODO: assemble IFD chain in memory
  1528. for _ in range(pageno):
  1529. # update pointer at IFD offset
  1530. pos = fh.tell()
  1531. fh.seek(self._ifdoffset)
  1532. fh.write(pack(offsetformat, pos))
  1533. fh.seek(pos)
  1534. self._ifdoffset = pos + ifdoffset
  1535. # update strip/tile offsets in IFD
  1536. dataoffset += pagedatasize # offset to image data
  1537. if stripoffset2offset is None:
  1538. ifd.seek(stripoffset2value)
  1539. ifd.write(pack(offsetformat, dataoffset))
  1540. else:
  1541. ifd.seek(stripoffset2offset)
  1542. ifd.write(pack(offsetformat, pos + stripoffset2value))
  1543. ifd.seek(stripoffset2value)
  1544. stripoffset = dataoffset
  1545. for size in self._databytecounts:
  1546. ifd.write(pack(offsetformat, stripoffset))
  1547. stripoffset += size
  1548. # write IFD entry
  1549. fh.write(ifd.getvalue())
  1550. self._tags = None
  1551. self._datadtype = None
  1552. self._dataoffset = None
  1553. self._databytecounts = None
  1554. # do not reset _shape or _data_shape
  1555. def _write_image_description(self):
  1556. """Write meta data to ImageDescription tag."""
  1557. if (
  1558. not self._datashape
  1559. or self._datashape[0] == 1
  1560. or self._descriptionoffset <= 0
  1561. ):
  1562. return
  1563. colormapped = self._colormap is not None
  1564. if self._imagej:
  1565. isrgb = self._shape[-1] in (3, 4)
  1566. description = imagej_description(
  1567. self._datashape, isrgb, colormapped, **self._metadata
  1568. )
  1569. else:
  1570. description = json_description(self._datashape, **self._metadata)
  1571. # rewrite description and its length to file
  1572. description = description.encode("utf-8")
  1573. description = description[: self._descriptionlen - 1]
  1574. pos = self._fh.tell()
  1575. self._fh.seek(self._descriptionoffset)
  1576. self._fh.write(description)
  1577. self._fh.seek(self._descriptionlenoffset)
  1578. self._fh.write(
  1579. struct.pack(self._byteorder + self._offsetformat, len(description) + 1)
  1580. )
  1581. self._fh.seek(pos)
  1582. self._descriptionoffset = 0
  1583. self._descriptionlenoffset = 0
  1584. self._descriptionlen = 0
  1585. def _now(self):
  1586. """Return current date and time."""
  1587. return datetime.datetime.now()
  1588. def close(self):
  1589. """Write remaining pages and close file handle."""
  1590. if not self._truncate:
  1591. self._write_remaining_pages()
  1592. self._write_image_description()
  1593. self._fh.close()
  1594. def __enter__(self):
  1595. return self
  1596. def __exit__(self, exc_type, exc_value, traceback):
  1597. self.close()
  1598. class TiffFile(object):
  1599. """Read image and metadata from TIFF file.
  1600. TiffFile instances must be closed using the 'close' method, which is
  1601. automatically called when using the 'with' context manager.
  1602. Attributes
  1603. ----------
  1604. pages : TiffPages
  1605. Sequence of TIFF pages in file.
  1606. series : list of TiffPageSeries
  1607. Sequences of closely related TIFF pages. These are computed
  1608. from OME, LSM, ImageJ, etc. metadata or based on similarity
  1609. of page properties such as shape, dtype, and compression.
  1610. byteorder : '>', '<'
  1611. The endianness of data in the file.
  1612. '>': big-endian (Motorola).
  1613. '>': little-endian (Intel).
  1614. is_flag : bool
  1615. If True, file is of a certain format.
  1616. Flags are: bigtiff, movie, shaped, ome, imagej, stk, lsm, fluoview,
  1617. nih, vista, 'micromanager, metaseries, mdgel, mediacy, tvips, fei,
  1618. sem, scn, svs, scanimage, andor, epics, pilatus, qptiff.
  1619. All attributes are read-only.
  1620. Examples
  1621. --------
  1622. >>> # read image array from TIFF file
  1623. >>> imsave('temp.tif', numpy.random.rand(5, 301, 219))
  1624. >>> with TiffFile('temp.tif') as tif:
  1625. ... data = tif.asarray()
  1626. >>> data.shape
  1627. (5, 301, 219)
  1628. """
  1629. def __init__(
  1630. self,
  1631. arg,
  1632. name=None,
  1633. offset=None,
  1634. size=None,
  1635. multifile=True,
  1636. movie=None,
  1637. **kwargs,
  1638. ):
  1639. """Initialize instance from file.
  1640. Parameters
  1641. ----------
  1642. arg : str or open file
  1643. Name of file or open file object.
  1644. The file objects are closed in TiffFile.close().
  1645. name : str
  1646. Optional name of file in case 'arg' is a file handle.
  1647. offset : int
  1648. Optional start position of embedded file. By default, this is
  1649. the current file position.
  1650. size : int
  1651. Optional size of embedded file. By default, this is the number
  1652. of bytes from the 'offset' to the end of the file.
  1653. multifile : bool
  1654. If True (default), series may include pages from multiple files.
  1655. Currently applies to OME-TIFF only.
  1656. movie : bool
  1657. If True, assume that later pages differ from first page only by
  1658. data offsets and byte counts. Significantly increases speed and
  1659. reduces memory usage when reading movies with thousands of pages.
  1660. Enabling this for non-movie files will result in data corruption
  1661. or crashes. Python 3 only.
  1662. kwargs : bool
  1663. 'is_ome': If False, disable processing of OME-XML metadata.
  1664. """
  1665. if "fastij" in kwargs:
  1666. del kwargs["fastij"]
  1667. raise DeprecationWarning("the fastij option will be removed")
  1668. for key, value in kwargs.items():
  1669. if key[:3] == "is_" and key[3:] in TIFF.FILE_FLAGS:
  1670. if value is not None and not value:
  1671. setattr(self, key, bool(value))
  1672. else:
  1673. raise TypeError("unexpected keyword argument: %s" % key)
  1674. fh = FileHandle(arg, mode="rb", name=name, offset=offset, size=size)
  1675. self._fh = fh
  1676. self._multifile = bool(multifile)
  1677. self._files = {fh.name: self} # cache of TiffFiles
  1678. try:
  1679. fh.seek(0)
  1680. try:
  1681. byteorder = {b"II": "<", b"MM": ">"}[fh.read(2)]
  1682. except KeyError:
  1683. raise ValueError("not a TIFF file")
  1684. sys_byteorder = {"big": ">", "little": "<"}[sys.byteorder]
  1685. self.isnative = byteorder == sys_byteorder
  1686. version = struct.unpack(byteorder + "H", fh.read(2))[0]
  1687. if version == 43:
  1688. # BigTiff
  1689. self.is_bigtiff = True
  1690. offsetsize, zero = struct.unpack(byteorder + "HH", fh.read(4))
  1691. if zero or offsetsize != 8:
  1692. raise ValueError("invalid BigTIFF file")
  1693. self.byteorder = byteorder
  1694. self.offsetsize = 8
  1695. self.offsetformat = byteorder + "Q"
  1696. self.tagnosize = 8
  1697. self.tagnoformat = byteorder + "Q"
  1698. self.tagsize = 20
  1699. self.tagformat1 = byteorder + "HH"
  1700. self.tagformat2 = byteorder + "Q8s"
  1701. elif version == 42:
  1702. self.is_bigtiff = False
  1703. self.byteorder = byteorder
  1704. self.offsetsize = 4
  1705. self.offsetformat = byteorder + "I"
  1706. self.tagnosize = 2
  1707. self.tagnoformat = byteorder + "H"
  1708. self.tagsize = 12
  1709. self.tagformat1 = byteorder + "HH"
  1710. self.tagformat2 = byteorder + "I4s"
  1711. else:
  1712. raise ValueError("invalid TIFF file")
  1713. # file handle is at offset to offset to first page
  1714. self.pages = TiffPages(self)
  1715. if self.is_lsm and (
  1716. self.filehandle.size >= 2**32
  1717. or self.pages[0].compression != 1
  1718. or self.pages[1].compression != 1
  1719. ):
  1720. self._lsm_load_pages()
  1721. self._lsm_fix_strip_offsets()
  1722. self._lsm_fix_strip_bytecounts()
  1723. elif movie:
  1724. self.pages.useframes = True
  1725. except Exception:
  1726. fh.close()
  1727. raise
  1728. @property
  1729. def filehandle(self):
  1730. """Return file handle."""
  1731. return self._fh
  1732. @property
  1733. def filename(self):
  1734. """Return name of file handle."""
  1735. return self._fh.name
  1736. @lazyattr
  1737. def fstat(self):
  1738. """Return status of file handle as stat_result object."""
  1739. try:
  1740. return os.fstat(self._fh.fileno())
  1741. except Exception: # io.UnsupportedOperation
  1742. return None
  1743. def close(self):
  1744. """Close open file handle(s)."""
  1745. for tif in self._files.values():
  1746. tif.filehandle.close()
  1747. self._files = {}
  1748. def asarray(self, key=None, series=None, out=None, validate=True, maxworkers=1):
  1749. """Return image data from multiple TIFF pages as numpy array.
  1750. By default, the data from the first series is returned.
  1751. Parameters
  1752. ----------
  1753. key : int, slice, or sequence of page indices
  1754. Defines which pages to return as array.
  1755. series : int or TiffPageSeries
  1756. Defines which series of pages to return as array.
  1757. out : numpy.ndarray, str, or file-like object; optional
  1758. Buffer where image data will be saved.
  1759. If None (default), a new array will be created.
  1760. If numpy.ndarray, a writable array of compatible dtype and shape.
  1761. If 'memmap', directly memory-map the image data in the TIFF file
  1762. if possible; else create a memory-mapped array in a temporary file.
  1763. If str or open file, the file name or file object used to
  1764. create a memory-map to an array stored in a binary file on disk.
  1765. validate : bool
  1766. If True (default), validate various tags.
  1767. Passed to TiffPage.asarray().
  1768. maxworkers : int
  1769. Maximum number of threads to concurrently get data from pages.
  1770. Default is 1. If None, up to half the CPU cores are used.
  1771. Reading data from file is limited to a single thread.
  1772. Using multiple threads can significantly speed up this function
  1773. if the bottleneck is decoding compressed data, e.g. in case of
  1774. large LZW compressed LSM files.
  1775. If the bottleneck is I/O or pure Python code, using multiple
  1776. threads might be detrimental.
  1777. """
  1778. if not self.pages:
  1779. return numpy.array([])
  1780. if key is None and series is None:
  1781. series = 0
  1782. if series is not None:
  1783. try:
  1784. series = self.series[series]
  1785. except (KeyError, TypeError):
  1786. pass
  1787. pages = series._pages
  1788. else:
  1789. pages = self.pages
  1790. if key is None:
  1791. pass
  1792. elif isinstance(key, inttypes):
  1793. pages = [pages[key]]
  1794. elif isinstance(key, slice):
  1795. pages = pages[key]
  1796. elif isinstance(key, collections.Iterable):
  1797. pages = [pages[k] for k in key]
  1798. else:
  1799. raise TypeError("key must be an int, slice, or sequence")
  1800. if not pages:
  1801. raise ValueError("no pages selected")
  1802. if self.is_nih:
  1803. result = stack_pages(pages, out=out, maxworkers=maxworkers, squeeze=False)
  1804. elif key is None and series and series.offset:
  1805. typecode = self.byteorder + series.dtype.char
  1806. if out == "memmap" and pages[0].is_memmappable:
  1807. result = self.filehandle.memmap_array(
  1808. typecode, series.shape, series.offset
  1809. )
  1810. else:
  1811. if out is not None:
  1812. out = create_output(out, series.shape, series.dtype)
  1813. self.filehandle.seek(series.offset)
  1814. result = self.filehandle.read_array(
  1815. typecode, product(series.shape), out=out, native=True
  1816. )
  1817. elif len(pages) == 1:
  1818. result = pages[0].asarray(out=out, validate=validate)
  1819. else:
  1820. result = stack_pages(pages, out=out, maxworkers=maxworkers)
  1821. if result is None:
  1822. return
  1823. if key is None:
  1824. try:
  1825. result.shape = series.shape
  1826. except ValueError:
  1827. try:
  1828. warnings.warn(
  1829. "failed to reshape %s to %s" % (result.shape, series.shape)
  1830. )
  1831. # try series of expected shapes
  1832. result.shape = (-1,) + series.shape
  1833. except ValueError:
  1834. # revert to generic shape
  1835. result.shape = (-1,) + pages[0].shape
  1836. elif len(pages) == 1:
  1837. result.shape = pages[0].shape
  1838. else:
  1839. result.shape = (-1,) + pages[0].shape
  1840. return result
  1841. @lazyattr
  1842. def series(self):
  1843. """Return related pages as TiffPageSeries.
  1844. Side effect: after calling this function, TiffFile.pages might contain
  1845. TiffPage and TiffFrame instances.
  1846. """
  1847. if not self.pages:
  1848. return []
  1849. useframes = self.pages.useframes
  1850. keyframe = self.pages.keyframe
  1851. series = []
  1852. for name in "ome imagej lsm fluoview nih mdgel shaped".split():
  1853. if getattr(self, "is_" + name, False):
  1854. series = getattr(self, "_%s_series" % name)()
  1855. break
  1856. self.pages.useframes = useframes
  1857. self.pages.keyframe = keyframe
  1858. if not series:
  1859. series = self._generic_series()
  1860. # remove empty series, e.g. in MD Gel files
  1861. series = [s for s in series if sum(s.shape) > 0]
  1862. for i, s in enumerate(series):
  1863. s.index = i
  1864. return series
  1865. def _generic_series(self):
  1866. """Return image series in file."""
  1867. if self.pages.useframes:
  1868. # movie mode
  1869. page = self.pages[0]
  1870. shape = page.shape
  1871. axes = page.axes
  1872. if len(self.pages) > 1:
  1873. shape = (len(self.pages),) + shape
  1874. axes = "I" + axes
  1875. return [
  1876. TiffPageSeries(self.pages[:], shape, page.dtype, axes, stype="movie")
  1877. ]
  1878. self.pages.clear(False)
  1879. self.pages.load()
  1880. result = []
  1881. keys = []
  1882. series = {}
  1883. compressions = TIFF.DECOMPESSORS
  1884. for page in self.pages:
  1885. if not page.shape:
  1886. continue
  1887. key = page.shape + (page.axes, page.compression in compressions)
  1888. if key in series:
  1889. series[key].append(page)
  1890. else:
  1891. keys.append(key)
  1892. series[key] = [page]
  1893. for key in keys:
  1894. pages = series[key]
  1895. page = pages[0]
  1896. shape = page.shape
  1897. axes = page.axes
  1898. if len(pages) > 1:
  1899. shape = (len(pages),) + shape
  1900. axes = "I" + axes
  1901. result.append(
  1902. TiffPageSeries(pages, shape, page.dtype, axes, stype="Generic")
  1903. )
  1904. return result
  1905. def _shaped_series(self):
  1906. """Return image series in "shaped" file."""
  1907. pages = self.pages
  1908. pages.useframes = True
  1909. lenpages = len(pages)
  1910. def append_series(series, pages, axes, shape, reshape, name, truncated):
  1911. page = pages[0]
  1912. if not axes:
  1913. shape = page.shape
  1914. axes = page.axes
  1915. if len(pages) > 1:
  1916. shape = (len(pages),) + shape
  1917. axes = "Q" + axes
  1918. size = product(shape)
  1919. resize = product(reshape)
  1920. if page.is_contiguous and resize > size and resize % size == 0:
  1921. if truncated is None:
  1922. truncated = True
  1923. axes = "Q" + axes
  1924. shape = (resize // size,) + shape
  1925. try:
  1926. axes = reshape_axes(axes, shape, reshape)
  1927. shape = reshape
  1928. except ValueError as e:
  1929. warnings.warn(str(e))
  1930. series.append(
  1931. TiffPageSeries(
  1932. pages,
  1933. shape,
  1934. page.dtype,
  1935. axes,
  1936. name=name,
  1937. stype="Shaped",
  1938. truncated=truncated,
  1939. )
  1940. )
  1941. keyframe = axes = shape = reshape = name = None
  1942. series = []
  1943. index = 0
  1944. while True:
  1945. if index >= lenpages:
  1946. break
  1947. # new keyframe; start of new series
  1948. pages.keyframe = index
  1949. keyframe = pages[index]
  1950. if not keyframe.is_shaped:
  1951. warnings.warn("invalid shape metadata or corrupted file")
  1952. return
  1953. # read metadata
  1954. axes = None
  1955. shape = None
  1956. metadata = json_description_metadata(keyframe.is_shaped)
  1957. name = metadata.get("name", "")
  1958. reshape = metadata["shape"]
  1959. truncated = metadata.get("truncated", None)
  1960. if "axes" in metadata:
  1961. axes = metadata["axes"]
  1962. if len(axes) == len(reshape):
  1963. shape = reshape
  1964. else:
  1965. axes = ""
  1966. warnings.warn("axes do not match shape")
  1967. # skip pages if possible
  1968. spages = [keyframe]
  1969. size = product(reshape)
  1970. npages, mod = divmod(size, product(keyframe.shape))
  1971. if mod:
  1972. warnings.warn("series shape does not match page shape")
  1973. return
  1974. if 1 < npages <= lenpages - index:
  1975. size *= keyframe._dtype.itemsize
  1976. if truncated:
  1977. npages = 1
  1978. elif (
  1979. keyframe.is_final
  1980. and keyframe.offset + size < pages[index + 1].offset
  1981. ):
  1982. truncated = False
  1983. else:
  1984. # need to read all pages for series
  1985. truncated = False
  1986. for j in range(index + 1, index + npages):
  1987. page = pages[j]
  1988. page.keyframe = keyframe
  1989. spages.append(page)
  1990. append_series(series, spages, axes, shape, reshape, name, truncated)
  1991. index += npages
  1992. return series
  1993. def _imagej_series(self):
  1994. """Return image series in ImageJ file."""
  1995. # ImageJ's dimension order is always TZCYXS
  1996. # TODO: fix loading of color, composite, or palette images
  1997. self.pages.useframes = True
  1998. self.pages.keyframe = 0
  1999. ij = self.imagej_metadata
  2000. pages = self.pages
  2001. page = pages[0]
  2002. def is_hyperstack():
  2003. # ImageJ hyperstack store all image metadata in the first page and
  2004. # image data are stored contiguously before the second page, if any
  2005. if not page.is_final:
  2006. return False
  2007. images = ij.get("images", 0)
  2008. if images <= 1:
  2009. return False
  2010. offset, count = page.is_contiguous
  2011. if (
  2012. count != product(page.shape) * page.bitspersample // 8
  2013. or offset + count * images > self.filehandle.size
  2014. ):
  2015. raise ValueError()
  2016. # check that next page is stored after data
  2017. if len(pages) > 1 and offset + count * images > pages[1].offset:
  2018. return False
  2019. return True
  2020. try:
  2021. hyperstack = is_hyperstack()
  2022. except ValueError:
  2023. warnings.warn("invalid ImageJ metadata or corrupted file")
  2024. return
  2025. if hyperstack:
  2026. # no need to read other pages
  2027. pages = [page]
  2028. else:
  2029. self.pages.load()
  2030. shape = []
  2031. axes = []
  2032. if "frames" in ij:
  2033. shape.append(ij["frames"])
  2034. axes.append("T")
  2035. if "slices" in ij:
  2036. shape.append(ij["slices"])
  2037. axes.append("Z")
  2038. if "channels" in ij and not (
  2039. page.photometric == 2 and not ij.get("hyperstack", False)
  2040. ):
  2041. shape.append(ij["channels"])
  2042. axes.append("C")
  2043. remain = ij.get("images", len(pages)) // (product(shape) if shape else 1)
  2044. if remain > 1:
  2045. shape.append(remain)
  2046. axes.append("I")
  2047. if page.axes[0] == "I":
  2048. # contiguous multiple images
  2049. shape.extend(page.shape[1:])
  2050. axes.extend(page.axes[1:])
  2051. elif page.axes[:2] == "SI":
  2052. # color-mapped contiguous multiple images
  2053. shape = page.shape[0:1] + tuple(shape) + page.shape[2:]
  2054. axes = list(page.axes[0]) + axes + list(page.axes[2:])
  2055. else:
  2056. shape.extend(page.shape)
  2057. axes.extend(page.axes)
  2058. truncated = (
  2059. hyperstack
  2060. and len(self.pages) == 1
  2061. and page.is_contiguous[1] != product(shape) * page.bitspersample // 8
  2062. )
  2063. return [
  2064. TiffPageSeries(
  2065. pages, shape, page.dtype, axes, stype="ImageJ", truncated=truncated
  2066. )
  2067. ]
  2068. def _fluoview_series(self):
  2069. """Return image series in FluoView file."""
  2070. self.pages.useframes = True
  2071. self.pages.keyframe = 0
  2072. self.pages.load()
  2073. mm = self.fluoview_metadata
  2074. mmhd = list(reversed(mm["Dimensions"]))
  2075. axes = "".join(
  2076. TIFF.MM_DIMENSIONS.get(i[0].upper(), "Q") for i in mmhd if i[1] > 1
  2077. )
  2078. shape = tuple(int(i[1]) for i in mmhd if i[1] > 1)
  2079. return [
  2080. TiffPageSeries(
  2081. self.pages,
  2082. shape,
  2083. self.pages[0].dtype,
  2084. axes,
  2085. name=mm["ImageName"],
  2086. stype="FluoView",
  2087. )
  2088. ]
  2089. def _mdgel_series(self):
  2090. """Return image series in MD Gel file."""
  2091. # only a single page, scaled according to metadata in second page
  2092. self.pages.useframes = False
  2093. self.pages.keyframe = 0
  2094. self.pages.load()
  2095. md = self.mdgel_metadata
  2096. if md["FileTag"] in (2, 128):
  2097. dtype = numpy.dtype("float32")
  2098. scale = md["ScalePixel"]
  2099. scale = scale[0] / scale[1] # rational
  2100. if md["FileTag"] == 2:
  2101. # squary root data format
  2102. def transform(a):
  2103. return a.astype("float32") ** 2 * scale
  2104. else:
  2105. def transform(a):
  2106. return a.astype("float32") * scale
  2107. else:
  2108. transform = None
  2109. page = self.pages[0]
  2110. return [
  2111. TiffPageSeries(
  2112. [page], page.shape, dtype, page.axes, transform=transform, stype="MDGel"
  2113. )
  2114. ]
  2115. def _nih_series(self):
  2116. """Return image series in NIH file."""
  2117. self.pages.useframes = True
  2118. self.pages.keyframe = 0
  2119. self.pages.load()
  2120. page0 = self.pages[0]
  2121. if len(self.pages) == 1:
  2122. shape = page0.shape
  2123. axes = page0.axes
  2124. else:
  2125. shape = (len(self.pages),) + page0.shape
  2126. axes = "I" + page0.axes
  2127. return [TiffPageSeries(self.pages, shape, page0.dtype, axes, stype="NIH")]
  2128. def _ome_series(self):
  2129. """Return image series in OME-TIFF file(s)."""
  2130. from xml.etree import cElementTree as etree # delayed import
  2131. omexml = self.pages[0].description
  2132. try:
  2133. root = etree.fromstring(omexml)
  2134. except etree.ParseError as e:
  2135. # TODO: test badly encoded OME-XML
  2136. warnings.warn("ome-xml: %s" % e)
  2137. try:
  2138. # might work on Python 2
  2139. omexml = omexml.decode("utf-8", "ignore").encode("utf-8")
  2140. root = etree.fromstring(omexml)
  2141. except Exception:
  2142. return
  2143. self.pages.useframes = True
  2144. self.pages.keyframe = 0
  2145. self.pages.load()
  2146. uuid = root.attrib.get("UUID", None)
  2147. self._files = {uuid: self}
  2148. dirname = self._fh.dirname
  2149. modulo = {}
  2150. series = []
  2151. for element in root:
  2152. if element.tag.endswith("BinaryOnly"):
  2153. # TODO: load OME-XML from master or companion file
  2154. warnings.warn("ome-xml: not an ome-tiff master file")
  2155. break
  2156. if element.tag.endswith("StructuredAnnotations"):
  2157. for annot in element:
  2158. if not annot.attrib.get("Namespace", "").endswith("modulo"):
  2159. continue
  2160. for value in annot:
  2161. for modul in value:
  2162. for along in modul:
  2163. if not along.tag[:-1].endswith("Along"):
  2164. continue
  2165. axis = along.tag[-1]
  2166. newaxis = along.attrib.get("Type", "other")
  2167. newaxis = TIFF.AXES_LABELS[newaxis]
  2168. if "Start" in along.attrib:
  2169. step = float(along.attrib.get("Step", 1))
  2170. start = float(along.attrib["Start"])
  2171. stop = float(along.attrib["End"]) + step
  2172. labels = numpy.arange(start, stop, step)
  2173. else:
  2174. labels = [
  2175. label.text
  2176. for label in along
  2177. if label.tag.endswith("Label")
  2178. ]
  2179. modulo[axis] = (newaxis, labels)
  2180. if not element.tag.endswith("Image"):
  2181. continue
  2182. attr = element.attrib
  2183. name = attr.get("Name", None)
  2184. for pixels in element:
  2185. if not pixels.tag.endswith("Pixels"):
  2186. continue
  2187. attr = pixels.attrib
  2188. dtype = attr.get("PixelType", None)
  2189. axes = "".join(reversed(attr["DimensionOrder"]))
  2190. shape = list(int(attr["Size" + ax]) for ax in axes)
  2191. size = product(shape[:-2])
  2192. ifds = None
  2193. spp = 1 # samples per pixel
  2194. # FIXME: this implementation assumes the last two
  2195. # dimensions are stored in tiff pages (shape[:-2]).
  2196. # Apparently that is not always the case.
  2197. for data in pixels:
  2198. if data.tag.endswith("Channel"):
  2199. attr = data.attrib
  2200. if ifds is None:
  2201. spp = int(attr.get("SamplesPerPixel", spp))
  2202. ifds = [None] * (size // spp)
  2203. elif int(attr.get("SamplesPerPixel", 1)) != spp:
  2204. raise ValueError("cannot handle differing SamplesPerPixel")
  2205. continue
  2206. if ifds is None:
  2207. ifds = [None] * (size // spp)
  2208. if not data.tag.endswith("TiffData"):
  2209. continue
  2210. attr = data.attrib
  2211. ifd = int(attr.get("IFD", 0))
  2212. num = int(attr.get("NumPlanes", 1 if "IFD" in attr else 0))
  2213. num = int(attr.get("PlaneCount", num))
  2214. idx = [int(attr.get("First" + ax, 0)) for ax in axes[:-2]]
  2215. try:
  2216. idx = numpy.ravel_multi_index(idx, shape[:-2])
  2217. except ValueError:
  2218. # ImageJ produces invalid ome-xml when cropping
  2219. warnings.warn("ome-xml: invalid TiffData index")
  2220. continue
  2221. for uuid in data:
  2222. if not uuid.tag.endswith("UUID"):
  2223. continue
  2224. if uuid.text not in self._files:
  2225. if not self._multifile:
  2226. # abort reading multifile OME series
  2227. # and fall back to generic series
  2228. return []
  2229. fname = uuid.attrib["FileName"]
  2230. try:
  2231. tif = TiffFile(os.path.join(dirname, fname))
  2232. tif.pages.useframes = True
  2233. tif.pages.keyframe = 0
  2234. tif.pages.load()
  2235. except (IOError, FileNotFoundError, ValueError):
  2236. warnings.warn("ome-xml: failed to read '%s'" % fname)
  2237. break
  2238. self._files[uuid.text] = tif
  2239. tif.close()
  2240. pages = self._files[uuid.text].pages
  2241. try:
  2242. for i in range(num if num else len(pages)):
  2243. ifds[idx + i] = pages[ifd + i]
  2244. except IndexError:
  2245. warnings.warn("ome-xml: index out of range")
  2246. # only process first UUID
  2247. break
  2248. else:
  2249. pages = self.pages
  2250. try:
  2251. for i in range(num if num else len(pages)):
  2252. ifds[idx + i] = pages[ifd + i]
  2253. except IndexError:
  2254. warnings.warn("ome-xml: index out of range")
  2255. if all(i is None for i in ifds):
  2256. # skip images without data
  2257. continue
  2258. # set a keyframe on all IFDs
  2259. keyframe = None
  2260. for i in ifds:
  2261. # try find a TiffPage
  2262. if i and i == i.keyframe:
  2263. keyframe = i
  2264. break
  2265. if not keyframe:
  2266. # reload a TiffPage from file
  2267. for i, keyframe in enumerate(ifds):
  2268. if keyframe:
  2269. keyframe.parent.pages.keyframe = keyframe.index
  2270. keyframe = keyframe.parent.pages[keyframe.index]
  2271. ifds[i] = keyframe
  2272. break
  2273. for i in ifds:
  2274. if i is not None:
  2275. i.keyframe = keyframe
  2276. dtype = keyframe.dtype
  2277. series.append(
  2278. TiffPageSeries(
  2279. ifds, shape, dtype, axes, parent=self, name=name, stype="OME"
  2280. )
  2281. )
  2282. for serie in series:
  2283. shape = list(serie.shape)
  2284. for axis, (newaxis, labels) in modulo.items():
  2285. i = serie.axes.index(axis)
  2286. size = len(labels)
  2287. if shape[i] == size:
  2288. serie.axes = serie.axes.replace(axis, newaxis, 1)
  2289. else:
  2290. shape[i] //= size
  2291. shape.insert(i + 1, size)
  2292. serie.axes = serie.axes.replace(axis, axis + newaxis, 1)
  2293. serie.shape = tuple(shape)
  2294. # squeeze dimensions
  2295. for serie in series:
  2296. serie.shape, serie.axes = squeeze_axes(serie.shape, serie.axes)
  2297. return series
  2298. def _lsm_series(self):
  2299. """Return main image series in LSM file. Skip thumbnails."""
  2300. lsmi = self.lsm_metadata
  2301. axes = TIFF.CZ_LSMINFO_SCANTYPE[lsmi["ScanType"]]
  2302. if self.pages[0].photometric == 2: # RGB; more than one channel
  2303. axes = axes.replace("C", "").replace("XY", "XYC")
  2304. if lsmi.get("DimensionP", 0) > 1:
  2305. axes += "P"
  2306. if lsmi.get("DimensionM", 0) > 1:
  2307. axes += "M"
  2308. axes = axes[::-1]
  2309. shape = tuple(int(lsmi[TIFF.CZ_LSMINFO_DIMENSIONS[i]]) for i in axes)
  2310. name = lsmi.get("Name", "")
  2311. self.pages.keyframe = 0
  2312. pages = self.pages[::2]
  2313. dtype = pages[0].dtype
  2314. series = [TiffPageSeries(pages, shape, dtype, axes, name=name, stype="LSM")]
  2315. if self.pages[1].is_reduced:
  2316. self.pages.keyframe = 1
  2317. pages = self.pages[1::2]
  2318. dtype = pages[0].dtype
  2319. cp, i = 1, 0
  2320. while cp < len(pages) and i < len(shape) - 2:
  2321. cp *= shape[i]
  2322. i += 1
  2323. shape = shape[:i] + pages[0].shape
  2324. axes = axes[:i] + "CYX"
  2325. series.append(
  2326. TiffPageSeries(pages, shape, dtype, axes, name=name, stype="LSMreduced")
  2327. )
  2328. return series
  2329. def _lsm_load_pages(self):
  2330. """Load all pages from LSM file."""
  2331. self.pages.cache = True
  2332. self.pages.useframes = True
  2333. # second series: thumbnails
  2334. self.pages.keyframe = 1
  2335. keyframe = self.pages[1]
  2336. for page in self.pages[1::2]:
  2337. page.keyframe = keyframe
  2338. # first series: data
  2339. self.pages.keyframe = 0
  2340. keyframe = self.pages[0]
  2341. for page in self.pages[::2]:
  2342. page.keyframe = keyframe
  2343. def _lsm_fix_strip_offsets(self):
  2344. """Unwrap strip offsets for LSM files greater than 4 GB.
  2345. Each series and position require separate unwrapping (undocumented).
  2346. """
  2347. if self.filehandle.size < 2**32:
  2348. return
  2349. pages = self.pages
  2350. npages = len(pages)
  2351. series = self.series[0]
  2352. axes = series.axes
  2353. # find positions
  2354. positions = 1
  2355. for i in 0, 1:
  2356. if series.axes[i] in "PM":
  2357. positions *= series.shape[i]
  2358. # make time axis first
  2359. if positions > 1:
  2360. ntimes = 0
  2361. for i in 1, 2:
  2362. if axes[i] == "T":
  2363. ntimes = series.shape[i]
  2364. break
  2365. if ntimes:
  2366. div, mod = divmod(npages, 2 * positions * ntimes)
  2367. assert mod == 0
  2368. shape = (positions, ntimes, div, 2)
  2369. indices = numpy.arange(product(shape)).reshape(shape)
  2370. indices = numpy.moveaxis(indices, 1, 0)
  2371. else:
  2372. indices = numpy.arange(npages).reshape(-1, 2)
  2373. # images of reduced page might be stored first
  2374. if pages[0].dataoffsets[0] > pages[1].dataoffsets[0]:
  2375. indices = indices[..., ::-1]
  2376. # unwrap offsets
  2377. wrap = 0
  2378. previousoffset = 0
  2379. for i in indices.flat:
  2380. page = pages[i]
  2381. dataoffsets = []
  2382. for currentoffset in page.dataoffsets:
  2383. if currentoffset < previousoffset:
  2384. wrap += 2**32
  2385. dataoffsets.append(currentoffset + wrap)
  2386. previousoffset = currentoffset
  2387. page.dataoffsets = tuple(dataoffsets)
  2388. def _lsm_fix_strip_bytecounts(self):
  2389. """Set databytecounts to size of compressed data.
  2390. The StripByteCounts tag in LSM files contains the number of bytes
  2391. for the uncompressed data.
  2392. """
  2393. pages = self.pages
  2394. if pages[0].compression == 1:
  2395. return
  2396. # sort pages by first strip offset
  2397. pages = sorted(pages, key=lambda p: p.dataoffsets[0])
  2398. npages = len(pages) - 1
  2399. for i, page in enumerate(pages):
  2400. if page.index % 2:
  2401. continue
  2402. offsets = page.dataoffsets
  2403. bytecounts = page.databytecounts
  2404. if i < npages:
  2405. lastoffset = pages[i + 1].dataoffsets[0]
  2406. else:
  2407. # LZW compressed strips might be longer than uncompressed
  2408. lastoffset = min(offsets[-1] + 2 * bytecounts[-1], self._fh.size)
  2409. offsets = offsets + (lastoffset,)
  2410. page.databytecounts = tuple(
  2411. offsets[j + 1] - offsets[j] for j in range(len(bytecounts))
  2412. )
  2413. def __getattr__(self, name):
  2414. """Return 'is_flag' attributes from first page."""
  2415. if name[3:] in TIFF.FILE_FLAGS:
  2416. if not self.pages:
  2417. return False
  2418. value = bool(getattr(self.pages[0], name))
  2419. setattr(self, name, value)
  2420. return value
  2421. raise AttributeError(
  2422. "'%s' object has no attribute '%s'" % (self.__class__.__name__, name)
  2423. )
  2424. def __enter__(self):
  2425. return self
  2426. def __exit__(self, exc_type, exc_value, traceback):
  2427. self.close()
  2428. def __str__(self, detail=0, width=79):
  2429. """Return string containing information about file.
  2430. The detail parameter specifies the level of detail returned:
  2431. 0: file only.
  2432. 1: all series, first page of series and its tags.
  2433. 2: large tag values and file metadata.
  2434. 3: all pages.
  2435. """
  2436. info = [
  2437. "TiffFile '%s'",
  2438. format_size(self._fh.size),
  2439. {"<": "LittleEndian", ">": "BigEndian"}[self.byteorder],
  2440. ]
  2441. if self.is_bigtiff:
  2442. info.append("BigTiff")
  2443. info.append("|".join(f.upper() for f in self.flags))
  2444. if len(self.pages) > 1:
  2445. info.append("%i Pages" % len(self.pages))
  2446. if len(self.series) > 1:
  2447. info.append("%i Series" % len(self.series))
  2448. if len(self._files) > 1:
  2449. info.append("%i Files" % (len(self._files)))
  2450. info = " ".join(info)
  2451. info = info.replace(" ", " ").replace(" ", " ")
  2452. info = info % snipstr(self._fh.name, max(12, width + 2 - len(info)))
  2453. if detail <= 0:
  2454. return info
  2455. info = [info]
  2456. info.append("\n".join(str(s) for s in self.series))
  2457. if detail >= 3:
  2458. info.extend(
  2459. (
  2460. TiffPage.__str__(p, detail=detail, width=width)
  2461. for p in self.pages
  2462. if p is not None
  2463. )
  2464. )
  2465. else:
  2466. info.extend(
  2467. (
  2468. TiffPage.__str__(s.pages[0], detail=detail, width=width)
  2469. for s in self.series
  2470. if s.pages[0] is not None
  2471. )
  2472. )
  2473. if detail >= 2:
  2474. for name in sorted(self.flags):
  2475. if hasattr(self, name + "_metadata"):
  2476. m = getattr(self, name + "_metadata")
  2477. if m:
  2478. info.append(
  2479. "%s_METADATA\n%s"
  2480. % (
  2481. name.upper(),
  2482. pformat(m, width=width, height=detail * 12),
  2483. )
  2484. )
  2485. return "\n\n".join(info).replace("\n\n\n", "\n\n")
  2486. @lazyattr
  2487. def flags(self):
  2488. """Return set of file flags."""
  2489. return set(
  2490. name.lower()
  2491. for name in sorted(TIFF.FILE_FLAGS)
  2492. if getattr(self, "is_" + name)
  2493. )
  2494. @lazyattr
  2495. def is_mdgel(self):
  2496. """File has MD Gel format."""
  2497. try:
  2498. return self.pages[0].is_mdgel or self.pages[1].is_mdgel
  2499. except IndexError:
  2500. return False
  2501. @property
  2502. def is_movie(self):
  2503. """Return if file is a movie."""
  2504. return self.pages.useframes
  2505. @lazyattr
  2506. def shaped_metadata(self):
  2507. """Return Tifffile metadata from JSON descriptions as dicts."""
  2508. if not self.is_shaped:
  2509. return
  2510. return tuple(
  2511. json_description_metadata(s.pages[0].is_shaped)
  2512. for s in self.series
  2513. if s.stype.lower() == "shaped"
  2514. )
  2515. @lazyattr
  2516. def ome_metadata(self):
  2517. """Return OME XML as dict."""
  2518. # TODO: remove this or return XML?
  2519. if not self.is_ome:
  2520. return
  2521. return xml2dict(self.pages[0].description)["OME"]
  2522. @lazyattr
  2523. def qptiff_metadata(self):
  2524. """Return PerkinElmer-QPI-ImageDescription XML element as dict."""
  2525. if not self.is_qptiff:
  2526. return
  2527. root = "PerkinElmer-QPI-ImageDescription"
  2528. xml = self.pages[0].description.replace(" " + root + " ", root)
  2529. return xml2dict(xml)[root]
  2530. @lazyattr
  2531. def lsm_metadata(self):
  2532. """Return LSM metadata from CZ_LSMINFO tag as dict."""
  2533. if not self.is_lsm:
  2534. return
  2535. return self.pages[0].tags["CZ_LSMINFO"].value
  2536. @lazyattr
  2537. def stk_metadata(self):
  2538. """Return STK metadata from UIC tags as dict."""
  2539. if not self.is_stk:
  2540. return
  2541. page = self.pages[0]
  2542. tags = page.tags
  2543. result = {}
  2544. result["NumberPlanes"] = tags["UIC2tag"].count
  2545. if page.description:
  2546. result["PlaneDescriptions"] = page.description.split("\0")
  2547. # result['plane_descriptions'] = stk_description_metadata(
  2548. # page.image_description)
  2549. if "UIC1tag" in tags:
  2550. result.update(tags["UIC1tag"].value)
  2551. if "UIC3tag" in tags:
  2552. result.update(tags["UIC3tag"].value) # wavelengths
  2553. if "UIC4tag" in tags:
  2554. result.update(tags["UIC4tag"].value) # override uic1 tags
  2555. uic2tag = tags["UIC2tag"].value
  2556. result["ZDistance"] = uic2tag["ZDistance"]
  2557. result["TimeCreated"] = uic2tag["TimeCreated"]
  2558. result["TimeModified"] = uic2tag["TimeModified"]
  2559. try:
  2560. result["DatetimeCreated"] = numpy.array(
  2561. [
  2562. julian_datetime(*dt)
  2563. for dt in zip(uic2tag["DateCreated"], uic2tag["TimeCreated"])
  2564. ],
  2565. dtype="datetime64[ns]",
  2566. )
  2567. result["DatetimeModified"] = numpy.array(
  2568. [
  2569. julian_datetime(*dt)
  2570. for dt in zip(uic2tag["DateModified"], uic2tag["TimeModified"])
  2571. ],
  2572. dtype="datetime64[ns]",
  2573. )
  2574. except ValueError as e:
  2575. warnings.warn("stk_metadata: %s" % e)
  2576. return result
  2577. @lazyattr
  2578. def imagej_metadata(self):
  2579. """Return consolidated ImageJ metadata as dict."""
  2580. if not self.is_imagej:
  2581. return
  2582. page = self.pages[0]
  2583. result = imagej_description_metadata(page.is_imagej)
  2584. if "IJMetadata" in page.tags:
  2585. try:
  2586. result.update(page.tags["IJMetadata"].value)
  2587. except Exception:
  2588. pass
  2589. return result
  2590. @lazyattr
  2591. def fluoview_metadata(self):
  2592. """Return consolidated FluoView metadata as dict."""
  2593. if not self.is_fluoview:
  2594. return
  2595. result = {}
  2596. page = self.pages[0]
  2597. result.update(page.tags["MM_Header"].value)
  2598. # TODO: read stamps from all pages
  2599. result["Stamp"] = page.tags["MM_Stamp"].value
  2600. # skip parsing image description; not reliable
  2601. # try:
  2602. # t = fluoview_description_metadata(page.image_description)
  2603. # if t is not None:
  2604. # result['ImageDescription'] = t
  2605. # except Exception as e:
  2606. # warnings.warn(
  2607. # "failed to read FluoView image description: %s" % e)
  2608. return result
  2609. @lazyattr
  2610. def nih_metadata(self):
  2611. """Return NIH Image metadata from NIHImageHeader tag as dict."""
  2612. if not self.is_nih:
  2613. return
  2614. return self.pages[0].tags["NIHImageHeader"].value
  2615. @lazyattr
  2616. def fei_metadata(self):
  2617. """Return FEI metadata from SFEG or HELIOS tags as dict."""
  2618. if not self.is_fei:
  2619. return
  2620. tags = self.pages[0].tags
  2621. if "FEI_SFEG" in tags:
  2622. return tags["FEI_SFEG"].value
  2623. if "FEI_HELIOS" in tags:
  2624. return tags["FEI_HELIOS"].value
  2625. @lazyattr
  2626. def sem_metadata(self):
  2627. """Return SEM metadata from CZ_SEM tag as dict."""
  2628. if not self.is_sem:
  2629. return
  2630. return self.pages[0].tags["CZ_SEM"].value
  2631. @lazyattr
  2632. def mdgel_metadata(self):
  2633. """Return consolidated metadata from MD GEL tags as dict."""
  2634. for page in self.pages[:2]:
  2635. if "MDFileTag" in page.tags:
  2636. tags = page.tags
  2637. break
  2638. else:
  2639. return
  2640. result = {}
  2641. for code in range(33445, 33453):
  2642. name = TIFF.TAGS[code]
  2643. if name not in tags:
  2644. continue
  2645. result[name[2:]] = tags[name].value
  2646. return result
  2647. @lazyattr
  2648. def andor_metadata(self):
  2649. """Return Andor tags as dict."""
  2650. return self.pages[0].andor_tags
  2651. @lazyattr
  2652. def epics_metadata(self):
  2653. """Return EPICS areaDetector tags as dict."""
  2654. return self.pages[0].epics_tags
  2655. @lazyattr
  2656. def tvips_metadata(self):
  2657. """Return TVIPS tag as dict."""
  2658. if not self.is_tvips:
  2659. return
  2660. return self.pages[0].tags["TVIPS"].value
  2661. @lazyattr
  2662. def metaseries_metadata(self):
  2663. """Return MetaSeries metadata from image description as dict."""
  2664. if not self.is_metaseries:
  2665. return
  2666. return metaseries_description_metadata(self.pages[0].description)
  2667. @lazyattr
  2668. def pilatus_metadata(self):
  2669. """Return Pilatus metadata from image description as dict."""
  2670. if not self.is_pilatus:
  2671. return
  2672. return pilatus_description_metadata(self.pages[0].description)
  2673. @lazyattr
  2674. def micromanager_metadata(self):
  2675. """Return consolidated MicroManager metadata as dict."""
  2676. if not self.is_micromanager:
  2677. return
  2678. # from file header
  2679. result = read_micromanager_metadata(self._fh)
  2680. # from tag
  2681. result.update(self.pages[0].tags["MicroManagerMetadata"].value)
  2682. return result
  2683. @lazyattr
  2684. def scanimage_metadata(self):
  2685. """Return ScanImage non-varying frame and ROI metadata as dict."""
  2686. if not self.is_scanimage:
  2687. return
  2688. result = {}
  2689. try:
  2690. framedata, roidata = read_scanimage_metadata(self._fh)
  2691. result["FrameData"] = framedata
  2692. result.update(roidata)
  2693. except ValueError:
  2694. pass
  2695. # TODO: scanimage_artist_metadata
  2696. try:
  2697. result["Description"] = scanimage_description_metadata(
  2698. self.pages[0].description
  2699. )
  2700. except Exception as e:
  2701. warnings.warn("scanimage_description_metadata failed: %s" % e)
  2702. return result
  2703. @property
  2704. def geotiff_metadata(self):
  2705. """Return GeoTIFF metadata from first page as dict."""
  2706. if not self.is_geotiff:
  2707. return
  2708. return self.pages[0].geotiff_tags
  2709. class TiffPages(object):
  2710. """Sequence of TIFF image file directories."""
  2711. def __init__(self, parent):
  2712. """Initialize instance from file. Read first TiffPage from file.
  2713. The file position must be at an offset to an offset to a TiffPage.
  2714. """
  2715. self.parent = parent
  2716. self.pages = [] # cache of TiffPages, TiffFrames, or their offsets
  2717. self.complete = False # True if offsets to all pages were read
  2718. self._tiffpage = TiffPage # class for reading tiff pages
  2719. self._keyframe = None
  2720. self._cache = True
  2721. # read offset to first page
  2722. fh = parent.filehandle
  2723. self._nextpageoffset = fh.tell()
  2724. offset = struct.unpack(parent.offsetformat, fh.read(parent.offsetsize))[0]
  2725. if offset == 0:
  2726. # warnings.warn('file contains no pages')
  2727. self.complete = True
  2728. return
  2729. if offset >= fh.size:
  2730. warnings.warn("invalid page offset (%i)" % offset)
  2731. self.complete = True
  2732. return
  2733. # always read and cache first page
  2734. fh.seek(offset)
  2735. page = TiffPage(parent, index=0)
  2736. self.pages.append(page)
  2737. self._keyframe = page
  2738. @property
  2739. def cache(self):
  2740. """Return if pages/frames are currently being cached."""
  2741. return self._cache
  2742. @cache.setter
  2743. def cache(self, value):
  2744. """Enable or disable caching of pages/frames. Clear cache if False."""
  2745. value = bool(value)
  2746. if self._cache and not value:
  2747. self.clear()
  2748. self._cache = value
  2749. @property
  2750. def useframes(self):
  2751. """Return if currently using TiffFrame (True) or TiffPage (False)."""
  2752. return self._tiffpage == TiffFrame and TiffFrame is not TiffPage
  2753. @useframes.setter
  2754. def useframes(self, value):
  2755. """Set to use TiffFrame (True) or TiffPage (False)."""
  2756. self._tiffpage = TiffFrame if value else TiffPage
  2757. @property
  2758. def keyframe(self):
  2759. """Return index of current keyframe."""
  2760. return self._keyframe.index
  2761. @keyframe.setter
  2762. def keyframe(self, index):
  2763. """Set current keyframe. Load TiffPage from file if necessary."""
  2764. if self._keyframe.index == index:
  2765. return
  2766. if self.complete or 0 <= index < len(self.pages):
  2767. page = self.pages[index]
  2768. if isinstance(page, TiffPage):
  2769. self._keyframe = page
  2770. return
  2771. elif isinstance(page, TiffFrame):
  2772. # remove existing frame
  2773. self.pages[index] = page.offset
  2774. # load TiffPage from file
  2775. useframes = self.useframes
  2776. self._tiffpage = TiffPage
  2777. self._keyframe = self[index]
  2778. self.useframes = useframes
  2779. @property
  2780. def next_page_offset(self):
  2781. """Return offset where offset to a new page can be stored."""
  2782. if not self.complete:
  2783. self._seek(-1)
  2784. return self._nextpageoffset
  2785. def load(self):
  2786. """Read all remaining pages from file."""
  2787. fh = self.parent.filehandle
  2788. keyframe = self._keyframe
  2789. pages = self.pages
  2790. if not self.complete:
  2791. self._seek(-1)
  2792. for i, page in enumerate(pages):
  2793. if isinstance(page, inttypes):
  2794. fh.seek(page)
  2795. page = self._tiffpage(self.parent, index=i, keyframe=keyframe)
  2796. pages[i] = page
  2797. def clear(self, fully=True):
  2798. """Delete all but first page from cache. Set keyframe to first page."""
  2799. pages = self.pages
  2800. if not self._cache or len(pages) < 1:
  2801. return
  2802. self._keyframe = pages[0]
  2803. if fully:
  2804. # delete all but first TiffPage/TiffFrame
  2805. for i, page in enumerate(pages[1:]):
  2806. if not isinstance(page, inttypes):
  2807. pages[i + 1] = page.offset
  2808. elif TiffFrame is not TiffPage:
  2809. # delete only TiffFrames
  2810. for i, page in enumerate(pages):
  2811. if isinstance(page, TiffFrame):
  2812. pages[i] = page.offset
  2813. def _seek(self, index, maxpages=2**22):
  2814. """Seek file to offset of specified page."""
  2815. pages = self.pages
  2816. if not pages:
  2817. return
  2818. fh = self.parent.filehandle
  2819. if fh.closed:
  2820. raise RuntimeError("FileHandle is closed")
  2821. if self.complete or 0 <= index < len(pages):
  2822. page = pages[index]
  2823. offset = page if isinstance(page, inttypes) else page.offset
  2824. fh.seek(offset)
  2825. return
  2826. offsetformat = self.parent.offsetformat
  2827. offsetsize = self.parent.offsetsize
  2828. tagnoformat = self.parent.tagnoformat
  2829. tagnosize = self.parent.tagnosize
  2830. tagsize = self.parent.tagsize
  2831. unpack = struct.unpack
  2832. page = pages[-1]
  2833. offset = page if isinstance(page, inttypes) else page.offset
  2834. while len(pages) < maxpages:
  2835. # read offsets to pages from file until index is reached
  2836. fh.seek(offset)
  2837. # skip tags
  2838. try:
  2839. tagno = unpack(tagnoformat, fh.read(tagnosize))[0]
  2840. if tagno > 4096:
  2841. raise ValueError("suspicious number of tags")
  2842. except Exception:
  2843. warnings.warn("corrupted tag list at offset %i" % offset)
  2844. del pages[-1]
  2845. self.complete = True
  2846. break
  2847. self._nextpageoffset = offset + tagnosize + tagno * tagsize
  2848. fh.seek(self._nextpageoffset)
  2849. # read offset to next page
  2850. offset = unpack(offsetformat, fh.read(offsetsize))[0]
  2851. if offset == 0:
  2852. self.complete = True
  2853. break
  2854. if offset >= fh.size:
  2855. warnings.warn("invalid page offset (%i)" % offset)
  2856. self.complete = True
  2857. break
  2858. pages.append(offset)
  2859. if 0 <= index < len(pages):
  2860. break
  2861. if index >= len(pages):
  2862. raise IndexError("list index out of range")
  2863. page = pages[index]
  2864. fh.seek(page if isinstance(page, inttypes) else page.offset)
  2865. def __bool__(self):
  2866. """Return True if file contains any pages."""
  2867. return len(self.pages) > 0
  2868. def __len__(self):
  2869. """Return number of pages in file."""
  2870. if not self.complete:
  2871. self._seek(-1)
  2872. return len(self.pages)
  2873. def __getitem__(self, key):
  2874. """Return specified page(s) from cache or file."""
  2875. pages = self.pages
  2876. if not pages:
  2877. raise IndexError("list index out of range")
  2878. if key == 0:
  2879. return pages[key]
  2880. if isinstance(key, slice):
  2881. start, stop, _ = key.indices(2**31 - 1)
  2882. if not self.complete and max(stop, start) > len(pages):
  2883. self._seek(-1)
  2884. return [self[i] for i in range(*key.indices(len(pages)))]
  2885. if self.complete and key >= len(pages):
  2886. raise IndexError("list index out of range")
  2887. try:
  2888. page = pages[key]
  2889. except IndexError:
  2890. page = 0
  2891. if not isinstance(page, inttypes):
  2892. return page
  2893. self._seek(key)
  2894. page = self._tiffpage(self.parent, index=key, keyframe=self._keyframe)
  2895. if self._cache:
  2896. pages[key] = page
  2897. return page
  2898. def __iter__(self):
  2899. """Return iterator over all pages."""
  2900. i = 0
  2901. while True:
  2902. try:
  2903. yield self[i]
  2904. i += 1
  2905. except IndexError:
  2906. break
  2907. class TiffPage(object):
  2908. """TIFF image file directory (IFD).
  2909. Attributes
  2910. ----------
  2911. index : int
  2912. Index of page in file.
  2913. dtype : numpy.dtype or None
  2914. Data type (native byte order) of the image in IFD.
  2915. shape : tuple
  2916. Dimensions of the image in IFD.
  2917. axes : str
  2918. Axes label codes:
  2919. 'X' width, 'Y' height, 'S' sample, 'I' image series|page|plane,
  2920. 'Z' depth, 'C' color|em-wavelength|channel, 'E' ex-wavelength|lambda,
  2921. 'T' time, 'R' region|tile, 'A' angle, 'P' phase, 'H' lifetime,
  2922. 'L' exposure, 'V' event, 'Q' unknown, '_' missing
  2923. tags : dict
  2924. Dictionary of tags in IFD. {tag.name: TiffTag}
  2925. colormap : numpy.ndarray
  2926. Color look up table, if exists.
  2927. All attributes are read-only.
  2928. Notes
  2929. -----
  2930. The internal, normalized '_shape' attribute is 6 dimensional:
  2931. 0 : number planes/images (stk, ij).
  2932. 1 : planar samplesperpixel.
  2933. 2 : imagedepth Z (sgi).
  2934. 3 : imagelength Y.
  2935. 4 : imagewidth X.
  2936. 5 : contig samplesperpixel.
  2937. """
  2938. # default properties; will be updated from tags
  2939. imagewidth = 0
  2940. imagelength = 0
  2941. imagedepth = 1
  2942. tilewidth = 0
  2943. tilelength = 0
  2944. tiledepth = 1
  2945. bitspersample = 1
  2946. samplesperpixel = 1
  2947. sampleformat = 1
  2948. rowsperstrip = 2**32 - 1
  2949. compression = 1
  2950. planarconfig = 1
  2951. fillorder = 1
  2952. photometric = 0
  2953. predictor = 1
  2954. extrasamples = 1
  2955. colormap = None
  2956. software = ""
  2957. description = ""
  2958. description1 = ""
  2959. def __init__(self, parent, index, keyframe=None):
  2960. """Initialize instance from file.
  2961. The file handle position must be at offset to a valid IFD.
  2962. """
  2963. self.parent = parent
  2964. self.index = index
  2965. self.shape = ()
  2966. self._shape = ()
  2967. self.dtype = None
  2968. self._dtype = None
  2969. self.axes = ""
  2970. self.tags = {}
  2971. self.dataoffsets = ()
  2972. self.databytecounts = ()
  2973. # read TIFF IFD structure and its tags from file
  2974. fh = parent.filehandle
  2975. self.offset = fh.tell() # offset to this IFD
  2976. try:
  2977. tagno = struct.unpack(parent.tagnoformat, fh.read(parent.tagnosize))[0]
  2978. if tagno > 4096:
  2979. raise ValueError("suspicious number of tags")
  2980. except Exception:
  2981. raise ValueError("corrupted tag list at offset %i" % self.offset)
  2982. tagsize = parent.tagsize
  2983. data = fh.read(tagsize * tagno)
  2984. tags = self.tags
  2985. index = -tagsize
  2986. for _ in range(tagno):
  2987. index += tagsize
  2988. try:
  2989. tag = TiffTag(self.parent, data[index : index + tagsize])
  2990. except TiffTag.Error as e:
  2991. warnings.warn(str(e))
  2992. continue
  2993. tagname = tag.name
  2994. if tagname not in tags:
  2995. name = tagname
  2996. tags[name] = tag
  2997. else:
  2998. # some files contain multiple tags with same code
  2999. # e.g. MicroManager files contain two ImageDescription tags
  3000. i = 1
  3001. while True:
  3002. name = "%s%i" % (tagname, i)
  3003. if name not in tags:
  3004. tags[name] = tag
  3005. break
  3006. name = TIFF.TAG_ATTRIBUTES.get(name, "")
  3007. if name:
  3008. if name[:3] in "sof des" and not isinstance(tag.value, str):
  3009. pass # wrong string type for software, description
  3010. else:
  3011. setattr(self, name, tag.value)
  3012. if not tags:
  3013. return # found in FIBICS
  3014. # consolidate private tags; remove them from self.tags
  3015. if self.is_andor:
  3016. self.andor_tags
  3017. elif self.is_epics:
  3018. self.epics_tags
  3019. if self.is_lsm or (self.index and self.parent.is_lsm):
  3020. # correct non standard LSM bitspersample tags
  3021. self.tags["BitsPerSample"]._fix_lsm_bitspersample(self)
  3022. if self.is_vista or (self.index and self.parent.is_vista):
  3023. # ISS Vista writes wrong ImageDepth tag
  3024. self.imagedepth = 1
  3025. if self.is_stk and "UIC1tag" in tags and not tags["UIC1tag"].value:
  3026. # read UIC1tag now that plane count is known
  3027. uic1tag = tags["UIC1tag"]
  3028. fh.seek(uic1tag.valueoffset)
  3029. tags["UIC1tag"].value = read_uic1tag(
  3030. fh,
  3031. self.parent.byteorder,
  3032. uic1tag.dtype,
  3033. uic1tag.count,
  3034. None,
  3035. tags["UIC2tag"].count,
  3036. )
  3037. if "IJMetadata" in tags:
  3038. # decode IJMetadata tag
  3039. try:
  3040. tags["IJMetadata"].value = imagej_metadata(
  3041. tags["IJMetadata"].value,
  3042. tags["IJMetadataByteCounts"].value,
  3043. self.parent.byteorder,
  3044. )
  3045. except Exception as e:
  3046. warnings.warn(str(e))
  3047. if "BitsPerSample" in tags:
  3048. tag = tags["BitsPerSample"]
  3049. if tag.count == 1:
  3050. self.bitspersample = tag.value
  3051. else:
  3052. # LSM might list more items than samplesperpixel
  3053. value = tag.value[: self.samplesperpixel]
  3054. if any((v - value[0] for v in value)):
  3055. self.bitspersample = value
  3056. else:
  3057. self.bitspersample = value[0]
  3058. if "SampleFormat" in tags:
  3059. tag = tags["SampleFormat"]
  3060. if tag.count == 1:
  3061. self.sampleformat = tag.value
  3062. else:
  3063. value = tag.value[: self.samplesperpixel]
  3064. if any((v - value[0] for v in value)):
  3065. self.sampleformat = value
  3066. else:
  3067. self.sampleformat = value[0]
  3068. if "ImageLength" in tags:
  3069. if "RowsPerStrip" not in tags or tags["RowsPerStrip"].count > 1:
  3070. self.rowsperstrip = self.imagelength
  3071. # self.stripsperimage = int(math.floor(
  3072. # float(self.imagelength + self.rowsperstrip - 1) /
  3073. # self.rowsperstrip))
  3074. # determine dtype
  3075. dtype = self.sampleformat, self.bitspersample
  3076. dtype = TIFF.SAMPLE_DTYPES.get(dtype, None)
  3077. if dtype is not None:
  3078. dtype = numpy.dtype(dtype)
  3079. self.dtype = self._dtype = dtype
  3080. # determine shape of data
  3081. imagelength = self.imagelength
  3082. imagewidth = self.imagewidth
  3083. imagedepth = self.imagedepth
  3084. samplesperpixel = self.samplesperpixel
  3085. if self.is_stk:
  3086. assert self.imagedepth == 1
  3087. uictag = tags["UIC2tag"].value
  3088. planes = tags["UIC2tag"].count
  3089. if self.planarconfig == 1:
  3090. self._shape = (planes, 1, 1, imagelength, imagewidth, samplesperpixel)
  3091. if samplesperpixel == 1:
  3092. self.shape = (planes, imagelength, imagewidth)
  3093. self.axes = "YX"
  3094. else:
  3095. self.shape = (planes, imagelength, imagewidth, samplesperpixel)
  3096. self.axes = "YXS"
  3097. else:
  3098. self._shape = (planes, samplesperpixel, 1, imagelength, imagewidth, 1)
  3099. if samplesperpixel == 1:
  3100. self.shape = (planes, imagelength, imagewidth)
  3101. self.axes = "YX"
  3102. else:
  3103. self.shape = (planes, samplesperpixel, imagelength, imagewidth)
  3104. self.axes = "SYX"
  3105. # detect type of series
  3106. if planes == 1:
  3107. self.shape = self.shape[1:]
  3108. elif numpy.all(uictag["ZDistance"] != 0):
  3109. self.axes = "Z" + self.axes
  3110. elif numpy.all(numpy.diff(uictag["TimeCreated"]) != 0):
  3111. self.axes = "T" + self.axes
  3112. else:
  3113. self.axes = "I" + self.axes
  3114. elif self.photometric == 2 or samplesperpixel > 1: # PHOTOMETRIC.RGB
  3115. if self.planarconfig == 1:
  3116. self._shape = (
  3117. 1,
  3118. 1,
  3119. imagedepth,
  3120. imagelength,
  3121. imagewidth,
  3122. samplesperpixel,
  3123. )
  3124. if imagedepth == 1:
  3125. self.shape = (imagelength, imagewidth, samplesperpixel)
  3126. self.axes = "YXS"
  3127. else:
  3128. self.shape = (imagedepth, imagelength, imagewidth, samplesperpixel)
  3129. self.axes = "ZYXS"
  3130. else:
  3131. self._shape = (
  3132. 1,
  3133. samplesperpixel,
  3134. imagedepth,
  3135. imagelength,
  3136. imagewidth,
  3137. 1,
  3138. )
  3139. if imagedepth == 1:
  3140. self.shape = (samplesperpixel, imagelength, imagewidth)
  3141. self.axes = "SYX"
  3142. else:
  3143. self.shape = (samplesperpixel, imagedepth, imagelength, imagewidth)
  3144. self.axes = "SZYX"
  3145. else:
  3146. self._shape = (1, 1, imagedepth, imagelength, imagewidth, 1)
  3147. if imagedepth == 1:
  3148. self.shape = (imagelength, imagewidth)
  3149. self.axes = "YX"
  3150. else:
  3151. self.shape = (imagedepth, imagelength, imagewidth)
  3152. self.axes = "ZYX"
  3153. # dataoffsets and databytecounts
  3154. if "TileOffsets" in tags:
  3155. self.dataoffsets = tags["TileOffsets"].value
  3156. elif "StripOffsets" in tags:
  3157. self.dataoffsets = tags["StripOffsets"].value
  3158. else:
  3159. self.dataoffsets = (0,)
  3160. if "TileByteCounts" in tags:
  3161. self.databytecounts = tags["TileByteCounts"].value
  3162. elif "StripByteCounts" in tags:
  3163. self.databytecounts = tags["StripByteCounts"].value
  3164. else:
  3165. self.databytecounts = (product(self.shape) * (self.bitspersample // 8),)
  3166. if self.compression != 1:
  3167. warnings.warn("required ByteCounts tag is missing")
  3168. assert len(self.shape) == len(self.axes)
  3169. def asarray(
  3170. self,
  3171. out=None,
  3172. squeeze=True,
  3173. lock=None,
  3174. reopen=True,
  3175. maxsize=2**44,
  3176. validate=True,
  3177. ):
  3178. """Read image data from file and return as numpy array.
  3179. Raise ValueError if format is unsupported.
  3180. Parameters
  3181. ----------
  3182. out : numpy.ndarray, str, or file-like object; optional
  3183. Buffer where image data will be saved.
  3184. If None (default), a new array will be created.
  3185. If numpy.ndarray, a writable array of compatible dtype and shape.
  3186. If 'memmap', directly memory-map the image data in the TIFF file
  3187. if possible; else create a memory-mapped array in a temporary file.
  3188. If str or open file, the file name or file object used to
  3189. create a memory-map to an array stored in a binary file on disk.
  3190. squeeze : bool
  3191. If True, all length-1 dimensions (except X and Y) are
  3192. squeezed out from the array.
  3193. If False, the shape of the returned array might be different from
  3194. the page.shape.
  3195. lock : {RLock, NullContext}
  3196. A reentrant lock used to synchronize reads from file.
  3197. If None (default), the lock of the parent's filehandle is used.
  3198. reopen : bool
  3199. If True (default) and the parent file handle is closed, the file
  3200. is temporarily re-opened and closed if no exception occurs.
  3201. maxsize: int or None
  3202. Maximum size of data before a ValueError is raised.
  3203. Can be used to catch DOS. Default: 16 TB.
  3204. validate : bool
  3205. If True (default), validate various parameters.
  3206. If None, only validate parameters and return None.
  3207. """
  3208. self_ = self
  3209. self = self.keyframe # self or keyframe
  3210. if not self._shape or product(self._shape) == 0:
  3211. return
  3212. tags = self.tags
  3213. if validate or validate is None:
  3214. if maxsize and product(self._shape) > maxsize:
  3215. raise ValueError("data are too large %s" % str(self._shape))
  3216. if self.dtype is None:
  3217. raise ValueError(
  3218. "data type not supported: %s%i"
  3219. % (self.sampleformat, self.bitspersample)
  3220. )
  3221. if self.compression not in TIFF.DECOMPESSORS:
  3222. raise ValueError("cannot decompress %s" % self.compression.name)
  3223. if "SampleFormat" in tags:
  3224. tag = tags["SampleFormat"]
  3225. if tag.count != 1 and any((i - tag.value[0] for i in tag.value)):
  3226. raise ValueError("sample formats do not match %s" % tag.value)
  3227. if self.is_chroma_subsampled and (
  3228. self.compression != 7 or self.planarconfig == 2
  3229. ):
  3230. raise NotImplementedError("chroma subsampling not supported")
  3231. if validate is None:
  3232. return
  3233. fh = self_.parent.filehandle
  3234. lock = fh.lock if lock is None else lock
  3235. with lock:
  3236. closed = fh.closed
  3237. if closed:
  3238. if reopen:
  3239. fh.open()
  3240. else:
  3241. raise IOError("file handle is closed")
  3242. dtype = self._dtype
  3243. shape = self._shape
  3244. imagewidth = self.imagewidth
  3245. imagelength = self.imagelength
  3246. imagedepth = self.imagedepth
  3247. bitspersample = self.bitspersample
  3248. typecode = self.parent.byteorder + dtype.char
  3249. lsb2msb = self.fillorder == 2
  3250. offsets, bytecounts = self_.offsets_bytecounts
  3251. istiled = self.is_tiled
  3252. if istiled:
  3253. tilewidth = self.tilewidth
  3254. tilelength = self.tilelength
  3255. tiledepth = self.tiledepth
  3256. tw = (imagewidth + tilewidth - 1) // tilewidth
  3257. tl = (imagelength + tilelength - 1) // tilelength
  3258. td = (imagedepth + tiledepth - 1) // tiledepth
  3259. shape = (
  3260. shape[0],
  3261. shape[1],
  3262. td * tiledepth,
  3263. tl * tilelength,
  3264. tw * tilewidth,
  3265. shape[-1],
  3266. )
  3267. tileshape = (tiledepth, tilelength, tilewidth, shape[-1])
  3268. runlen = tilewidth
  3269. else:
  3270. runlen = imagewidth
  3271. if self.planarconfig == 1:
  3272. runlen *= self.samplesperpixel
  3273. if out == "memmap" and self.is_memmappable:
  3274. with lock:
  3275. result = fh.memmap_array(typecode, shape, offset=offsets[0])
  3276. elif self.is_contiguous:
  3277. if out is not None:
  3278. out = create_output(out, shape, dtype)
  3279. with lock:
  3280. fh.seek(offsets[0])
  3281. result = fh.read_array(typecode, product(shape), out=out)
  3282. if out is None and not result.dtype.isnative:
  3283. # swap byte order and dtype without copy
  3284. result.byteswap(True)
  3285. result = result.view(result.dtype.newbyteorder())
  3286. if lsb2msb:
  3287. reverse_bitorder(result)
  3288. else:
  3289. result = create_output(out, shape, dtype)
  3290. decompress = TIFF.DECOMPESSORS[self.compression]
  3291. if self.compression == 7: # COMPRESSION.JPEG
  3292. if bitspersample not in (8, 12):
  3293. raise ValueError("unsupported JPEG precision %i" % bitspersample)
  3294. if "JPEGTables" in tags:
  3295. table = tags["JPEGTables"].value
  3296. else:
  3297. table = b""
  3298. unpack = identityfunc
  3299. colorspace = TIFF.PHOTOMETRIC(self.photometric).name
  3300. def decompress(
  3301. x,
  3302. func=decompress,
  3303. table=table,
  3304. bitspersample=bitspersample,
  3305. colorspace=colorspace,
  3306. ):
  3307. return func(x, table, bitspersample, colorspace).reshape(-1)
  3308. elif bitspersample in (8, 16, 32, 64, 128):
  3309. if (bitspersample * runlen) % 8:
  3310. raise ValueError("data and sample size mismatch")
  3311. def unpack(x, typecode=typecode):
  3312. if self.predictor == 3: # PREDICTOR.FLOATINGPOINT
  3313. # the floating point horizontal differencing decoder
  3314. # needs the raw byte order
  3315. typecode = dtype.char
  3316. try:
  3317. # read only numpy array
  3318. return numpy.frombuffer(x, typecode)
  3319. except ValueError:
  3320. # strips may be missing EOI
  3321. # warnings.warn('unpack: %s' % e)
  3322. xlen = (len(x) // (bitspersample // 8)) * (bitspersample // 8)
  3323. return numpy.frombuffer(x[:xlen], typecode)
  3324. elif isinstance(bitspersample, tuple):
  3325. def unpack(x, typecode=typecode, bitspersample=bitspersample):
  3326. return unpack_rgb(x, typecode, bitspersample)
  3327. else:
  3328. def unpack(
  3329. x, typecode=typecode, bitspersample=bitspersample, runlen=runlen
  3330. ):
  3331. return unpack_ints(x, typecode, bitspersample, runlen)
  3332. if istiled:
  3333. writable = None
  3334. tw, tl, td, pl = 0, 0, 0, 0
  3335. for tile in buffered_read(fh, lock, offsets, bytecounts):
  3336. if lsb2msb:
  3337. tile = reverse_bitorder(tile)
  3338. tile = decompress(tile)
  3339. tile = unpack(tile)
  3340. try:
  3341. tile.shape = tileshape
  3342. except ValueError:
  3343. # incomplete tiles; see gdal issue #1179
  3344. warnings.warn("invalid tile data")
  3345. t = numpy.zeros(tileshape, dtype).reshape(-1)
  3346. s = min(tile.size, t.size)
  3347. t[:s] = tile[:s]
  3348. tile = t.reshape(tileshape)
  3349. if self.predictor == 2: # PREDICTOR.HORIZONTAL
  3350. if writable is None:
  3351. writable = tile.flags["WRITEABLE"]
  3352. if writable:
  3353. numpy.cumsum(tile, axis=-2, dtype=dtype, out=tile)
  3354. else:
  3355. tile = numpy.cumsum(tile, axis=-2, dtype=dtype)
  3356. elif self.predictor == 3: # PREDICTOR.FLOATINGPOINT
  3357. raise NotImplementedError()
  3358. result[
  3359. 0,
  3360. pl,
  3361. td : td + tiledepth,
  3362. tl : tl + tilelength,
  3363. tw : tw + tilewidth,
  3364. :,
  3365. ] = tile
  3366. del tile
  3367. tw += tilewidth
  3368. if tw >= shape[4]:
  3369. tw, tl = 0, tl + tilelength
  3370. if tl >= shape[3]:
  3371. tl, td = 0, td + tiledepth
  3372. if td >= shape[2]:
  3373. td, pl = 0, pl + 1
  3374. result = result[..., :imagedepth, :imagelength, :imagewidth, :]
  3375. else:
  3376. strip_size = self.rowsperstrip * self.imagewidth
  3377. if self.planarconfig == 1:
  3378. strip_size *= self.samplesperpixel
  3379. result = result.reshape(-1)
  3380. index = 0
  3381. for strip in buffered_read(fh, lock, offsets, bytecounts):
  3382. if lsb2msb:
  3383. strip = reverse_bitorder(strip)
  3384. strip = decompress(strip)
  3385. strip = unpack(strip)
  3386. size = min(result.size, strip.size, strip_size, result.size - index)
  3387. result[index : index + size] = strip[:size]
  3388. del strip
  3389. index += size
  3390. result.shape = self._shape
  3391. if self.predictor != 1 and not (istiled and not self.is_contiguous):
  3392. if self.parent.is_lsm and self.compression == 1:
  3393. pass # work around bug in LSM510 software
  3394. elif self.predictor == 2: # PREDICTOR.HORIZONTAL
  3395. numpy.cumsum(result, axis=-2, dtype=dtype, out=result)
  3396. elif self.predictor == 3: # PREDICTOR.FLOATINGPOINT
  3397. result = decode_floats(result)
  3398. if squeeze:
  3399. try:
  3400. result.shape = self.shape
  3401. except ValueError:
  3402. warnings.warn(
  3403. "failed to reshape from %s to %s"
  3404. % (str(result.shape), str(self.shape))
  3405. )
  3406. if closed:
  3407. # TODO: file should remain open if an exception occurred above
  3408. fh.close()
  3409. return result
  3410. def asrgb(
  3411. self,
  3412. uint8=False,
  3413. alpha=None,
  3414. colormap=None,
  3415. dmin=None,
  3416. dmax=None,
  3417. *args,
  3418. **kwargs,
  3419. ):
  3420. """Return image data as RGB(A).
  3421. Work in progress.
  3422. """
  3423. data = self.asarray(*args, **kwargs)
  3424. self = self.keyframe # self or keyframe
  3425. photometric = self.photometric
  3426. PHOTOMETRIC = TIFF.PHOTOMETRIC
  3427. if photometric == PHOTOMETRIC.PALETTE:
  3428. colormap = self.colormap
  3429. if colormap.shape[1] < 2**self.bitspersample or self.dtype.char not in "BH":
  3430. raise ValueError("cannot apply colormap")
  3431. if uint8:
  3432. if colormap.max() > 255:
  3433. colormap >>= 8
  3434. colormap = colormap.astype("uint8")
  3435. if "S" in self.axes:
  3436. data = data[..., 0] if self.planarconfig == 1 else data[0]
  3437. data = apply_colormap(data, colormap)
  3438. elif photometric == PHOTOMETRIC.RGB:
  3439. if "ExtraSamples" in self.tags:
  3440. if alpha is None:
  3441. alpha = TIFF.EXTRASAMPLE
  3442. extrasamples = self.extrasamples
  3443. if self.tags["ExtraSamples"].count == 1:
  3444. extrasamples = (extrasamples,)
  3445. for i, exs in enumerate(extrasamples):
  3446. if exs in alpha:
  3447. if self.planarconfig == 1:
  3448. data = data[..., [0, 1, 2, 3 + i]]
  3449. else:
  3450. data = data[:, [0, 1, 2, 3 + i]]
  3451. break
  3452. else:
  3453. if self.planarconfig == 1:
  3454. data = data[..., :3]
  3455. else:
  3456. data = data[:, :3]
  3457. # TODO: convert to uint8?
  3458. elif photometric == PHOTOMETRIC.MINISBLACK:
  3459. raise NotImplementedError()
  3460. elif photometric == PHOTOMETRIC.MINISWHITE:
  3461. raise NotImplementedError()
  3462. elif photometric == PHOTOMETRIC.SEPARATED:
  3463. raise NotImplementedError()
  3464. else:
  3465. raise NotImplementedError()
  3466. return data
  3467. def aspage(self):
  3468. return self
  3469. @property
  3470. def keyframe(self):
  3471. return self
  3472. @keyframe.setter
  3473. def keyframe(self, index):
  3474. return
  3475. @lazyattr
  3476. def offsets_bytecounts(self):
  3477. """Return simplified offsets and bytecounts."""
  3478. if self.is_contiguous:
  3479. offset, byte_count = self.is_contiguous
  3480. return [offset], [byte_count]
  3481. return clean_offsets_counts(self.dataoffsets, self.databytecounts)
  3482. @lazyattr
  3483. def is_contiguous(self):
  3484. """Return offset and size of contiguous data, else None.
  3485. Excludes prediction and fill_order.
  3486. """
  3487. if self.compression != 1 or self.bitspersample not in (8, 16, 32, 64):
  3488. return
  3489. if "TileWidth" in self.tags:
  3490. if (
  3491. self.imagewidth != self.tilewidth
  3492. or self.imagelength % self.tilelength
  3493. or self.tilewidth % 16
  3494. or self.tilelength % 16
  3495. ):
  3496. return
  3497. if (
  3498. "ImageDepth" in self.tags
  3499. and "TileDepth" in self.tags
  3500. and (
  3501. self.imagelength != self.tilelength
  3502. or self.imagedepth % self.tiledepth
  3503. )
  3504. ):
  3505. return
  3506. offsets = self.dataoffsets
  3507. bytecounts = self.databytecounts
  3508. if len(offsets) == 1:
  3509. return offsets[0], bytecounts[0]
  3510. if self.is_stk or all(
  3511. (
  3512. offsets[i] + bytecounts[i] == offsets[i + 1] or bytecounts[i + 1] == 0
  3513. ) # no data/ignore offset
  3514. for i in range(len(offsets) - 1)
  3515. ):
  3516. return offsets[0], sum(bytecounts)
  3517. @lazyattr
  3518. def is_final(self):
  3519. """Return if page's image data are stored in final form.
  3520. Excludes byte-swapping.
  3521. """
  3522. return (
  3523. self.is_contiguous
  3524. and self.fillorder == 1
  3525. and self.predictor == 1
  3526. and not self.is_chroma_subsampled
  3527. )
  3528. @lazyattr
  3529. def is_memmappable(self):
  3530. """Return if page's image data in file can be memory-mapped."""
  3531. return (
  3532. self.parent.filehandle.is_file
  3533. and self.is_final
  3534. and
  3535. # (self.bitspersample == 8 or self.parent.isnative) and
  3536. self.is_contiguous[0] % self.dtype.itemsize == 0
  3537. ) # aligned?
  3538. def __str__(self, detail=0, width=79):
  3539. """Return string containing information about page."""
  3540. if self.keyframe != self:
  3541. return TiffFrame.__str__(self, detail)
  3542. attr = ""
  3543. for name in ("memmappable", "final", "contiguous"):
  3544. attr = getattr(self, "is_" + name)
  3545. if attr:
  3546. attr = name.upper()
  3547. break
  3548. info = " ".join(
  3549. s
  3550. for s in (
  3551. "x".join(str(i) for i in self.shape),
  3552. "%s%s"
  3553. % (TIFF.SAMPLEFORMAT(self.sampleformat).name, self.bitspersample),
  3554. "|".join(
  3555. i
  3556. for i in (
  3557. TIFF.PHOTOMETRIC(self.photometric).name,
  3558. "TILED" if self.is_tiled else "",
  3559. self.compression.name if self.compression != 1 else "",
  3560. self.planarconfig.name if self.planarconfig != 1 else "",
  3561. self.predictor.name if self.predictor != 1 else "",
  3562. self.fillorder.name if self.fillorder != 1 else "",
  3563. )
  3564. if i
  3565. ),
  3566. attr,
  3567. "|".join((f.upper() for f in self.flags)),
  3568. )
  3569. if s
  3570. )
  3571. info = "TiffPage %i @%i %s" % (self.index, self.offset, info)
  3572. if detail <= 0:
  3573. return info
  3574. info = [info]
  3575. tags = self.tags
  3576. tlines = []
  3577. vlines = []
  3578. for tag in sorted(tags.values(), key=lambda x: x.code):
  3579. value = tag.__str__(width=width + 1)
  3580. tlines.append(value[:width].strip())
  3581. if detail > 1 and len(value) > width:
  3582. name = tag.name.upper()
  3583. if detail <= 2 and ("COUNTS" in name or "OFFSETS" in name):
  3584. value = pformat(tag.value, width=width, height=detail * 4)
  3585. else:
  3586. value = pformat(tag.value, width=width, height=detail * 12)
  3587. vlines.append("%s\n%s" % (tag.name, value))
  3588. info.append("\n".join(tlines))
  3589. if detail > 1:
  3590. info.append("\n\n".join(vlines))
  3591. if detail > 3:
  3592. try:
  3593. info.append(
  3594. "DATA\n%s" % pformat(self.asarray(), width=width, height=detail * 8)
  3595. )
  3596. except Exception:
  3597. pass
  3598. return "\n\n".join(info)
  3599. @lazyattr
  3600. def flags(self):
  3601. """Return set of flags."""
  3602. return set(
  3603. (
  3604. name.lower()
  3605. for name in sorted(TIFF.FILE_FLAGS)
  3606. if getattr(self, "is_" + name)
  3607. )
  3608. )
  3609. @property
  3610. def ndim(self):
  3611. """Return number of array dimensions."""
  3612. return len(self.shape)
  3613. @property
  3614. def size(self):
  3615. """Return number of elements in array."""
  3616. return product(self.shape)
  3617. @lazyattr
  3618. def andor_tags(self):
  3619. """Return consolidated metadata from Andor tags as dict.
  3620. Remove Andor tags from self.tags.
  3621. """
  3622. if not self.is_andor:
  3623. return
  3624. tags = self.tags
  3625. result = {"Id": tags["AndorId"].value}
  3626. for tag in list(self.tags.values()):
  3627. code = tag.code
  3628. if not 4864 < code < 5031:
  3629. continue
  3630. value = tag.value
  3631. name = tag.name[5:] if len(tag.name) > 5 else tag.name
  3632. result[name] = value
  3633. del tags[tag.name]
  3634. return result
  3635. @lazyattr
  3636. def epics_tags(self):
  3637. """Return consolidated metadata from EPICS areaDetector tags as dict.
  3638. Remove areaDetector tags from self.tags.
  3639. """
  3640. if not self.is_epics:
  3641. return
  3642. result = {}
  3643. tags = self.tags
  3644. for tag in list(self.tags.values()):
  3645. code = tag.code
  3646. if not 65000 <= code < 65500:
  3647. continue
  3648. value = tag.value
  3649. if code == 65000:
  3650. result["timeStamp"] = datetime.datetime.fromtimestamp(float(value))
  3651. elif code == 65001:
  3652. result["uniqueID"] = int(value)
  3653. elif code == 65002:
  3654. result["epicsTSSec"] = int(value)
  3655. elif code == 65003:
  3656. result["epicsTSNsec"] = int(value)
  3657. else:
  3658. key, value = value.split(":", 1)
  3659. result[key] = astype(value)
  3660. del tags[tag.name]
  3661. return result
  3662. @lazyattr
  3663. def geotiff_tags(self):
  3664. """Return consolidated metadata from GeoTIFF tags as dict."""
  3665. if not self.is_geotiff:
  3666. return
  3667. tags = self.tags
  3668. gkd = tags["GeoKeyDirectoryTag"].value
  3669. if gkd[0] != 1:
  3670. warnings.warn("invalid GeoKeyDirectoryTag")
  3671. return {}
  3672. result = {
  3673. "KeyDirectoryVersion": gkd[0],
  3674. "KeyRevision": gkd[1],
  3675. "KeyRevisionMinor": gkd[2],
  3676. # 'NumberOfKeys': gkd[3],
  3677. }
  3678. # deltags = ['GeoKeyDirectoryTag']
  3679. geokeys = TIFF.GEO_KEYS
  3680. geocodes = TIFF.GEO_CODES
  3681. for index in range(gkd[3]):
  3682. keyid, tagid, count, offset = gkd[4 + index * 4 : index * 4 + 8]
  3683. keyid = geokeys.get(keyid, keyid)
  3684. if tagid == 0:
  3685. value = offset
  3686. else:
  3687. tagname = TIFF.TAGS[tagid]
  3688. # deltags.append(tagname)
  3689. value = tags[tagname].value[offset : offset + count]
  3690. if tagid == 34737 and count > 1 and value[-1] == "|":
  3691. value = value[:-1]
  3692. value = value if count > 1 else value[0]
  3693. if keyid in geocodes:
  3694. try:
  3695. value = geocodes[keyid](value)
  3696. except Exception:
  3697. pass
  3698. result[keyid] = value
  3699. if "IntergraphMatrixTag" in tags:
  3700. value = tags["IntergraphMatrixTag"].value
  3701. value = numpy.array(value)
  3702. if len(value) == 16:
  3703. value = value.reshape((4, 4)).tolist()
  3704. result["IntergraphMatrix"] = value
  3705. if "ModelPixelScaleTag" in tags:
  3706. value = numpy.array(tags["ModelPixelScaleTag"].value).tolist()
  3707. result["ModelPixelScale"] = value
  3708. if "ModelTiepointTag" in tags:
  3709. value = tags["ModelTiepointTag"].value
  3710. value = numpy.array(value).reshape((-1, 6)).squeeze().tolist()
  3711. result["ModelTiepoint"] = value
  3712. if "ModelTransformationTag" in tags:
  3713. value = tags["ModelTransformationTag"].value
  3714. value = numpy.array(value).reshape((4, 4)).tolist()
  3715. result["ModelTransformation"] = value
  3716. elif False:
  3717. # if 'ModelPixelScaleTag' in tags and 'ModelTiepointTag' in tags:
  3718. sx, sy, sz = tags["ModelPixelScaleTag"].value
  3719. tiepoints = tags["ModelTiepointTag"].value
  3720. transforms = []
  3721. for tp in range(0, len(tiepoints), 6):
  3722. i, j, k, x, y, z = tiepoints[tp : tp + 6]
  3723. transforms.append(
  3724. [
  3725. [sx, 0.0, 0.0, x - i * sx],
  3726. [0.0, -sy, 0.0, y + j * sy],
  3727. [0.0, 0.0, sz, z - k * sz],
  3728. [0.0, 0.0, 0.0, 1.0],
  3729. ]
  3730. )
  3731. if len(tiepoints) == 6:
  3732. transforms = transforms[0]
  3733. result["ModelTransformation"] = transforms
  3734. if "RPCCoefficientTag" in tags:
  3735. rpcc = tags["RPCCoefficientTag"].value
  3736. result["RPCCoefficient"] = {
  3737. "ERR_BIAS": rpcc[0],
  3738. "ERR_RAND": rpcc[1],
  3739. "LINE_OFF": rpcc[2],
  3740. "SAMP_OFF": rpcc[3],
  3741. "LAT_OFF": rpcc[4],
  3742. "LONG_OFF": rpcc[5],
  3743. "HEIGHT_OFF": rpcc[6],
  3744. "LINE_SCALE": rpcc[7],
  3745. "SAMP_SCALE": rpcc[8],
  3746. "LAT_SCALE": rpcc[9],
  3747. "LONG_SCALE": rpcc[10],
  3748. "HEIGHT_SCALE": rpcc[11],
  3749. "LINE_NUM_COEFF": rpcc[12:33],
  3750. "LINE_DEN_COEFF ": rpcc[33:53],
  3751. "SAMP_NUM_COEFF": rpcc[53:73],
  3752. "SAMP_DEN_COEFF": rpcc[73:],
  3753. }
  3754. return result
  3755. @property
  3756. def is_tiled(self):
  3757. """Page contains tiled image."""
  3758. return "TileWidth" in self.tags
  3759. @property
  3760. def is_reduced(self):
  3761. """Page is reduced image of another image."""
  3762. return "NewSubfileType" in self.tags and self.tags["NewSubfileType"].value & 1
  3763. @property
  3764. def is_chroma_subsampled(self):
  3765. """Page contains chroma subsampled image."""
  3766. return "YCbCrSubSampling" in self.tags and self.tags[
  3767. "YCbCrSubSampling"
  3768. ].value != (1, 1)
  3769. @lazyattr
  3770. def is_imagej(self):
  3771. """Return ImageJ description if exists, else None."""
  3772. for description in (self.description, self.description1):
  3773. if not description:
  3774. return
  3775. if description[:7] == "ImageJ=":
  3776. return description
  3777. @lazyattr
  3778. def is_shaped(self):
  3779. """Return description containing array shape if exists, else None."""
  3780. for description in (self.description, self.description1):
  3781. if not description:
  3782. return
  3783. if description[:1] == "{" and '"shape":' in description:
  3784. return description
  3785. if description[:6] == "shape=":
  3786. return description
  3787. @property
  3788. def is_mdgel(self):
  3789. """Page contains MDFileTag tag."""
  3790. return "MDFileTag" in self.tags
  3791. @property
  3792. def is_mediacy(self):
  3793. """Page contains Media Cybernetics Id tag."""
  3794. return "MC_Id" in self.tags and self.tags["MC_Id"].value[:7] == b"MC TIFF"
  3795. @property
  3796. def is_stk(self):
  3797. """Page contains UIC2Tag tag."""
  3798. return "UIC2tag" in self.tags
  3799. @property
  3800. def is_lsm(self):
  3801. """Page contains CZ_LSMINFO tag."""
  3802. return "CZ_LSMINFO" in self.tags
  3803. @property
  3804. def is_fluoview(self):
  3805. """Page contains FluoView MM_STAMP tag."""
  3806. return "MM_Stamp" in self.tags
  3807. @property
  3808. def is_nih(self):
  3809. """Page contains NIH image header."""
  3810. return "NIHImageHeader" in self.tags
  3811. @property
  3812. def is_sgi(self):
  3813. """Page contains SGI image and tile depth tags."""
  3814. return "ImageDepth" in self.tags and "TileDepth" in self.tags
  3815. @property
  3816. def is_vista(self):
  3817. """Software tag is 'ISS Vista'."""
  3818. return self.software == "ISS Vista"
  3819. @property
  3820. def is_metaseries(self):
  3821. """Page contains MDS MetaSeries metadata in ImageDescription tag."""
  3822. if self.index > 1 or self.software != "MetaSeries":
  3823. return False
  3824. d = self.description
  3825. return d.startswith("<MetaData>") and d.endswith("</MetaData>")
  3826. @property
  3827. def is_ome(self):
  3828. """Page contains OME-XML in ImageDescription tag."""
  3829. if self.index > 1 or not self.description:
  3830. return False
  3831. d = self.description
  3832. return d[:14] == "<?xml version=" and d[-6:] == "</OME>"
  3833. @property
  3834. def is_scn(self):
  3835. """Page contains Leica SCN XML in ImageDescription tag."""
  3836. if self.index > 1 or not self.description:
  3837. return False
  3838. d = self.description
  3839. return d[:14] == "<?xml version=" and d[-6:] == "</scn>"
  3840. @property
  3841. def is_micromanager(self):
  3842. """Page contains Micro-Manager metadata."""
  3843. return "MicroManagerMetadata" in self.tags
  3844. @property
  3845. def is_andor(self):
  3846. """Page contains Andor Technology tags."""
  3847. return "AndorId" in self.tags
  3848. @property
  3849. def is_pilatus(self):
  3850. """Page contains Pilatus tags."""
  3851. return self.software[:8] == "TVX TIFF" and self.description[:2] == "# "
  3852. @property
  3853. def is_epics(self):
  3854. """Page contains EPICS areaDetector tags."""
  3855. return (
  3856. self.description == "EPICS areaDetector"
  3857. or self.software == "EPICS areaDetector"
  3858. )
  3859. @property
  3860. def is_tvips(self):
  3861. """Page contains TVIPS metadata."""
  3862. return "TVIPS" in self.tags
  3863. @property
  3864. def is_fei(self):
  3865. """Page contains SFEG or HELIOS metadata."""
  3866. return "FEI_SFEG" in self.tags or "FEI_HELIOS" in self.tags
  3867. @property
  3868. def is_sem(self):
  3869. """Page contains Zeiss SEM metadata."""
  3870. return "CZ_SEM" in self.tags
  3871. @property
  3872. def is_svs(self):
  3873. """Page contains Aperio metadata."""
  3874. return self.description[:20] == "Aperio Image Library"
  3875. @property
  3876. def is_scanimage(self):
  3877. """Page contains ScanImage metadata."""
  3878. return (
  3879. self.description[:12] == "state.config"
  3880. or self.software[:22] == "SI.LINE_FORMAT_VERSION"
  3881. or "scanimage.SI." in self.description[-256:]
  3882. )
  3883. @property
  3884. def is_qptiff(self):
  3885. """Page contains PerkinElmer tissue images metadata."""
  3886. # The ImageDescription tag contains XML with a top-level
  3887. # <PerkinElmer-QPI-ImageDescription> element
  3888. return self.software[:15] == "PerkinElmer-QPI"
  3889. @property
  3890. def is_geotiff(self):
  3891. """Page contains GeoTIFF metadata."""
  3892. return "GeoKeyDirectoryTag" in self.tags
  3893. class TiffFrame(object):
  3894. """Lightweight TIFF image file directory (IFD).
  3895. Only a limited number of tag values are read from file, e.g. StripOffsets,
  3896. and StripByteCounts. Other tag values are assumed to be identical with a
  3897. specified TiffPage instance, the keyframe.
  3898. TiffFrame is intended to reduce resource usage and speed up reading data
  3899. from file, not for introspection of metadata.
  3900. Not compatible with Python 2.
  3901. """
  3902. __slots__ = (
  3903. "keyframe",
  3904. "parent",
  3905. "index",
  3906. "offset",
  3907. "dataoffsets",
  3908. "databytecounts",
  3909. )
  3910. is_mdgel = False
  3911. tags = {}
  3912. def __init__(self, parent, index, keyframe):
  3913. """Read specified tags from file.
  3914. The file handle position must be at the offset to a valid IFD.
  3915. """
  3916. self.keyframe = keyframe
  3917. self.parent = parent
  3918. self.index = index
  3919. self.dataoffsets = None
  3920. self.databytecounts = None
  3921. unpack = struct.unpack
  3922. fh = parent.filehandle
  3923. self.offset = fh.tell()
  3924. try:
  3925. tagno = unpack(parent.tagnoformat, fh.read(parent.tagnosize))[0]
  3926. if tagno > 4096:
  3927. raise ValueError("suspicious number of tags")
  3928. except Exception:
  3929. raise ValueError("corrupted page list at offset %i" % self.offset)
  3930. # tags = {}
  3931. tagcodes = {273, 279, 324, 325} # TIFF.FRAME_TAGS
  3932. tagsize = parent.tagsize
  3933. codeformat = parent.tagformat1[:2]
  3934. data = fh.read(tagsize * tagno)
  3935. index = -tagsize
  3936. for _ in range(tagno):
  3937. index += tagsize
  3938. code = unpack(codeformat, data[index : index + 2])[0]
  3939. if code not in tagcodes:
  3940. continue
  3941. try:
  3942. tag = TiffTag(parent, data[index : index + tagsize])
  3943. except TiffTag.Error as e:
  3944. warnings.warn(str(e))
  3945. continue
  3946. if code == 273 or code == 324:
  3947. setattr(self, "dataoffsets", tag.value)
  3948. elif code == 279 or code == 325:
  3949. setattr(self, "databytecounts", tag.value)
  3950. # elif code == 270:
  3951. # tagname = tag.name
  3952. # if tagname not in tags:
  3953. # tags[tagname] = bytes2str(tag.value)
  3954. # elif 'ImageDescription1' not in tags:
  3955. # tags['ImageDescription1'] = bytes2str(tag.value)
  3956. # else:
  3957. # tags[tag.name] = tag.value
  3958. def aspage(self):
  3959. """Return TiffPage from file."""
  3960. self.parent.filehandle.seek(self.offset)
  3961. return TiffPage(self.parent, index=self.index, keyframe=None)
  3962. def asarray(self, *args, **kwargs):
  3963. """Read image data from file and return as numpy array."""
  3964. # TODO: fix TypeError on Python 2
  3965. # "TypeError: unbound method asarray() must be called with TiffPage
  3966. # instance as first argument (got TiffFrame instance instead)"
  3967. kwargs["validate"] = False
  3968. return TiffPage.asarray(self, *args, **kwargs)
  3969. def asrgb(self, *args, **kwargs):
  3970. """Read image data from file and return RGB image as numpy array."""
  3971. kwargs["validate"] = False
  3972. return TiffPage.asrgb(self, *args, **kwargs)
  3973. @property
  3974. def offsets_bytecounts(self):
  3975. """Return simplified offsets and bytecounts."""
  3976. if self.keyframe.is_contiguous:
  3977. return self.dataoffsets[:1], self.keyframe.is_contiguous[1:]
  3978. return clean_offsets_counts(self.dataoffsets, self.databytecounts)
  3979. @property
  3980. def is_contiguous(self):
  3981. """Return offset and size of contiguous data, else None."""
  3982. if self.keyframe.is_contiguous:
  3983. return self.dataoffsets[0], self.keyframe.is_contiguous[1]
  3984. @property
  3985. def is_memmappable(self):
  3986. """Return if page's image data in file can be memory-mapped."""
  3987. return self.keyframe.is_memmappable
  3988. def __getattr__(self, name):
  3989. """Return attribute from keyframe."""
  3990. if name in TIFF.FRAME_ATTRS:
  3991. return getattr(self.keyframe, name)
  3992. # this error could be raised because an AttributeError was
  3993. # raised inside a @property function
  3994. raise AttributeError(
  3995. "'%s' object has no attribute '%s'" % (self.__class__.__name__, name)
  3996. )
  3997. def __str__(self, detail=0):
  3998. """Return string containing information about frame."""
  3999. info = " ".join(
  4000. s for s in ("x".join(str(i) for i in self.shape), str(self.dtype))
  4001. )
  4002. return "TiffFrame %i @%i %s" % (self.index, self.offset, info)
  4003. class TiffTag(object):
  4004. """TIFF tag structure.
  4005. Attributes
  4006. ----------
  4007. name : string
  4008. Name of tag.
  4009. code : int
  4010. Decimal code of tag.
  4011. dtype : str
  4012. Datatype of tag data. One of TIFF DATA_FORMATS.
  4013. count : int
  4014. Number of values.
  4015. value : various types
  4016. Tag data as Python object.
  4017. ImageSourceData : int
  4018. Location of value in file.
  4019. All attributes are read-only.
  4020. """
  4021. __slots__ = ("code", "count", "dtype", "value", "valueoffset")
  4022. class Error(Exception):
  4023. pass
  4024. def __init__(self, parent, tagheader, **kwargs):
  4025. """Initialize instance from tag header."""
  4026. fh = parent.filehandle
  4027. byteorder = parent.byteorder
  4028. unpack = struct.unpack
  4029. offsetsize = parent.offsetsize
  4030. self.valueoffset = fh.tell() + offsetsize + 4
  4031. code, type_ = unpack(parent.tagformat1, tagheader[:4])
  4032. count, value = unpack(parent.tagformat2, tagheader[4:])
  4033. try:
  4034. dtype = TIFF.DATA_FORMATS[type_]
  4035. except KeyError:
  4036. raise TiffTag.Error("unknown tag data type %i" % type_)
  4037. fmt = "%s%i%s" % (byteorder, count * int(dtype[0]), dtype[1])
  4038. size = struct.calcsize(fmt)
  4039. if size > offsetsize or code in TIFF.TAG_READERS:
  4040. self.valueoffset = offset = unpack(parent.offsetformat, value)[0]
  4041. if offset < 8 or offset > fh.size - size:
  4042. raise TiffTag.Error("invalid tag value offset")
  4043. # if offset % 2:
  4044. # warnings.warn('tag value does not begin on word boundary')
  4045. fh.seek(offset)
  4046. if code in TIFF.TAG_READERS:
  4047. readfunc = TIFF.TAG_READERS[code]
  4048. value = readfunc(fh, byteorder, dtype, count, offsetsize)
  4049. elif type_ == 7 or (count > 1 and dtype[-1] == "B"):
  4050. value = read_bytes(fh, byteorder, dtype, count, offsetsize)
  4051. elif code in TIFF.TAGS or dtype[-1] == "s":
  4052. value = unpack(fmt, fh.read(size))
  4053. else:
  4054. value = read_numpy(fh, byteorder, dtype, count, offsetsize)
  4055. elif dtype[-1] == "B" or type_ == 7:
  4056. value = value[:size]
  4057. else:
  4058. value = unpack(fmt, value[:size])
  4059. process = (
  4060. code not in TIFF.TAG_READERS and code not in TIFF.TAG_TUPLE and type_ != 7
  4061. )
  4062. if process and dtype[-1] == "s" and isinstance(value[0], bytes):
  4063. # TIFF ASCII fields can contain multiple strings,
  4064. # each terminated with a NUL
  4065. value = value[0]
  4066. try:
  4067. value = bytes2str(stripascii(value).strip())
  4068. except UnicodeDecodeError:
  4069. warnings.warn("tag %i: coercing invalid ASCII to bytes" % code)
  4070. dtype = "1B"
  4071. else:
  4072. if code in TIFF.TAG_ENUM:
  4073. t = TIFF.TAG_ENUM[code]
  4074. try:
  4075. value = tuple(t(v) for v in value)
  4076. except ValueError as e:
  4077. warnings.warn(str(e))
  4078. if process:
  4079. if len(value) == 1:
  4080. value = value[0]
  4081. self.code = code
  4082. self.dtype = dtype
  4083. self.count = count
  4084. self.value = value
  4085. @property
  4086. def name(self):
  4087. return TIFF.TAGS.get(self.code, str(self.code))
  4088. def _fix_lsm_bitspersample(self, parent):
  4089. """Correct LSM bitspersample tag.
  4090. Old LSM writers may use a separate region for two 16-bit values,
  4091. although they fit into the tag value element of the tag.
  4092. """
  4093. if self.code == 258 and self.count == 2:
  4094. # TODO: test this case; need example file
  4095. warnings.warn("correcting LSM bitspersample tag")
  4096. tof = parent.offsetformat[parent.offsetsize]
  4097. self.valueoffset = struct.unpack(tof, self._value)[0]
  4098. parent.filehandle.seek(self.valueoffset)
  4099. self.value = struct.unpack("<HH", parent.filehandle.read(4))
  4100. def __str__(self, detail=0, width=79):
  4101. """Return string containing information about tag."""
  4102. height = 1 if detail <= 0 else 8 * detail
  4103. tcode = "%i%s" % (self.count * int(self.dtype[0]), self.dtype[1])
  4104. line = (
  4105. "TiffTag %i %s %s @%i "
  4106. % (self.code, self.name, tcode, self.valueoffset)[:width]
  4107. )
  4108. if self.code in TIFF.TAG_ENUM:
  4109. if self.count == 1:
  4110. value = TIFF.TAG_ENUM[self.code](self.value).name
  4111. else:
  4112. value = pformat(tuple(v.name for v in self.value))
  4113. else:
  4114. value = pformat(self.value, width=width, height=height)
  4115. if detail <= 0:
  4116. line += value
  4117. line = line[:width]
  4118. else:
  4119. line += "\n" + value
  4120. return line
  4121. # Added to produce cleaner exceptions if tifffile unexpectedly fails to open the
  4122. # file. See this comment (and the following) for details:
  4123. # https://github.com/imageio/imageio/commit/bdbe699bbcda4223b0b6bd4d7474f84bbe34af09#r64068747
  4124. class TiffFileError(ValueError):
  4125. pass
  4126. class TiffPageSeries(object):
  4127. """Series of TIFF pages with compatible shape and data type.
  4128. Attributes
  4129. ----------
  4130. pages : list of TiffPage
  4131. Sequence of TiffPages in series.
  4132. dtype : numpy.dtype
  4133. Data type (native byte order) of the image array in series.
  4134. shape : tuple
  4135. Dimensions of the image array in series.
  4136. axes : str
  4137. Labels of axes in shape. See TiffPage.axes.
  4138. offset : int or None
  4139. Position of image data in file if memory-mappable, else None.
  4140. """
  4141. def __init__(
  4142. self,
  4143. pages,
  4144. shape,
  4145. dtype,
  4146. axes,
  4147. parent=None,
  4148. name=None,
  4149. transform=None,
  4150. stype=None,
  4151. truncated=False,
  4152. ):
  4153. """Initialize instance."""
  4154. self.index = 0
  4155. self._pages = pages # might contain only first of contiguous pages
  4156. self.shape = tuple(shape)
  4157. self.axes = "".join(axes)
  4158. self.dtype = numpy.dtype(dtype)
  4159. self.stype = stype if stype else ""
  4160. self.name = name if name else ""
  4161. self.transform = transform
  4162. if parent:
  4163. self.parent = parent
  4164. elif pages:
  4165. self.parent = pages[0].parent
  4166. else:
  4167. self.parent = None
  4168. if len(pages) == 1 and not truncated:
  4169. self._len = int(product(self.shape) // product(pages[0].shape))
  4170. else:
  4171. self._len = len(pages)
  4172. def asarray(self, out=None):
  4173. """Return image data from series of TIFF pages as numpy array."""
  4174. if self.parent:
  4175. result = self.parent.asarray(series=self, out=out)
  4176. if self.transform is not None:
  4177. result = self.transform(result)
  4178. return result
  4179. @lazyattr
  4180. def offset(self):
  4181. """Return offset to series data in file, if any."""
  4182. if not self._pages:
  4183. return
  4184. pos = 0
  4185. for page in self._pages:
  4186. if page is None:
  4187. return
  4188. if not page.is_final:
  4189. return
  4190. if not pos:
  4191. pos = page.is_contiguous[0] + page.is_contiguous[1]
  4192. continue
  4193. if pos != page.is_contiguous[0]:
  4194. return
  4195. pos += page.is_contiguous[1]
  4196. page = self._pages[0]
  4197. offset = page.is_contiguous[0]
  4198. if (page.is_imagej or page.is_shaped) and len(self._pages) == 1:
  4199. # truncated files
  4200. return offset
  4201. if pos == offset + product(self.shape) * self.dtype.itemsize:
  4202. return offset
  4203. @property
  4204. def ndim(self):
  4205. """Return number of array dimensions."""
  4206. return len(self.shape)
  4207. @property
  4208. def size(self):
  4209. """Return number of elements in array."""
  4210. return int(product(self.shape))
  4211. @property
  4212. def pages(self):
  4213. """Return sequence of all pages in series."""
  4214. # a workaround to keep the old interface working
  4215. return self
  4216. def __len__(self):
  4217. """Return number of TiffPages in series."""
  4218. return self._len
  4219. def __getitem__(self, key):
  4220. """Return specified TiffPage."""
  4221. if len(self._pages) == 1 and 0 < key < self._len:
  4222. index = self._pages[0].index
  4223. return self.parent.pages[index + key]
  4224. return self._pages[key]
  4225. def __iter__(self):
  4226. """Return iterator over TiffPages in series."""
  4227. if len(self._pages) == self._len:
  4228. for page in self._pages:
  4229. yield page
  4230. else:
  4231. pages = self.parent.pages
  4232. index = self._pages[0].index
  4233. for i in range(self._len):
  4234. yield pages[index + i]
  4235. def __str__(self):
  4236. """Return string with information about series."""
  4237. s = " ".join(
  4238. s
  4239. for s in (
  4240. snipstr("'%s'" % self.name, 20) if self.name else "",
  4241. "x".join(str(i) for i in self.shape),
  4242. str(self.dtype),
  4243. self.axes,
  4244. self.stype,
  4245. "%i Pages" % len(self.pages),
  4246. ("Offset=%i" % self.offset) if self.offset else "",
  4247. )
  4248. if s
  4249. )
  4250. return "TiffPageSeries %i %s" % (self.index, s)
  4251. class TiffSequence(object):
  4252. """Sequence of TIFF files.
  4253. The image data in all files must match shape, dtype, etc.
  4254. Attributes
  4255. ----------
  4256. files : list
  4257. List of file names.
  4258. shape : tuple
  4259. Shape of image sequence. Excludes shape of image array.
  4260. axes : str
  4261. Labels of axes in shape.
  4262. Examples
  4263. --------
  4264. >>> # read image stack from sequence of TIFF files
  4265. >>> imsave('temp_C001T001.tif', numpy.random.rand(64, 64))
  4266. >>> imsave('temp_C001T002.tif', numpy.random.rand(64, 64))
  4267. >>> tifs = TiffSequence('temp_C001*.tif')
  4268. >>> tifs.shape
  4269. (1, 2)
  4270. >>> tifs.axes
  4271. 'CT'
  4272. >>> data = tifs.asarray()
  4273. >>> data.shape
  4274. (1, 2, 64, 64)
  4275. """
  4276. _patterns = {
  4277. "axes": r"""
  4278. # matches Olympus OIF and Leica TIFF series
  4279. _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))
  4280. _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))?
  4281. _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))?
  4282. _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))?
  4283. _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))?
  4284. _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))?
  4285. _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))?
  4286. """
  4287. }
  4288. class ParseError(Exception):
  4289. pass
  4290. def __init__(self, files, imread=TiffFile, pattern="axes", *args, **kwargs):
  4291. """Initialize instance from multiple files.
  4292. Parameters
  4293. ----------
  4294. files : str, pathlib.Path, or sequence thereof
  4295. Glob pattern or sequence of file names.
  4296. Binary streams are not supported.
  4297. imread : function or class
  4298. Image read function or class with asarray function returning numpy
  4299. array from single file.
  4300. pattern : str
  4301. Regular expression pattern that matches axes names and sequence
  4302. indices in file names.
  4303. By default, the pattern matches Olympus OIF and Leica TIFF series.
  4304. """
  4305. if isinstance(files, pathlib.Path):
  4306. files = str(files)
  4307. if isinstance(files, basestring):
  4308. files = natural_sorted(glob.glob(files))
  4309. files = list(files)
  4310. if not files:
  4311. raise ValueError("no files found")
  4312. if isinstance(files[0], pathlib.Path):
  4313. files = [str(pathlib.Path(f)) for f in files]
  4314. elif not isinstance(files[0], basestring):
  4315. raise ValueError("not a file name")
  4316. self.files = files
  4317. if hasattr(imread, "asarray"):
  4318. # redefine imread
  4319. _imread = imread
  4320. def imread(fname, *args, **kwargs):
  4321. with _imread(fname) as im:
  4322. return im.asarray(*args, **kwargs)
  4323. self.imread = imread
  4324. self.pattern = self._patterns.get(pattern, pattern)
  4325. try:
  4326. self._parse()
  4327. if not self.axes:
  4328. self.axes = "I"
  4329. except self.ParseError:
  4330. self.axes = "I"
  4331. self.shape = (len(files),)
  4332. self._startindex = (0,)
  4333. self._indices = tuple((i,) for i in range(len(files)))
  4334. def __str__(self):
  4335. """Return string with information about image sequence."""
  4336. return "\n".join(
  4337. [
  4338. self.files[0],
  4339. " size: %i" % len(self.files),
  4340. " axes: %s" % self.axes,
  4341. " shape: %s" % str(self.shape),
  4342. ]
  4343. )
  4344. def __len__(self):
  4345. return len(self.files)
  4346. def __enter__(self):
  4347. return self
  4348. def __exit__(self, exc_type, exc_value, traceback):
  4349. self.close()
  4350. def close(self):
  4351. pass
  4352. def asarray(self, out=None, *args, **kwargs):
  4353. """Read image data from all files and return as numpy array.
  4354. The args and kwargs parameters are passed to the imread function.
  4355. Raise IndexError or ValueError if image shapes do not match.
  4356. """
  4357. im = self.imread(self.files[0], *args, **kwargs)
  4358. shape = self.shape + im.shape
  4359. result = create_output(out, shape, dtype=im.dtype)
  4360. result = result.reshape(-1, *im.shape)
  4361. for index, fname in zip(self._indices, self.files):
  4362. index = [i - j for i, j in zip(index, self._startindex)]
  4363. index = numpy.ravel_multi_index(index, self.shape)
  4364. im = self.imread(fname, *args, **kwargs)
  4365. result[index] = im
  4366. result.shape = shape
  4367. return result
  4368. def _parse(self):
  4369. """Get axes and shape from file names."""
  4370. if not self.pattern:
  4371. raise self.ParseError("invalid pattern")
  4372. pattern = re.compile(self.pattern, re.IGNORECASE | re.VERBOSE)
  4373. matches = pattern.findall(self.files[0])
  4374. if not matches:
  4375. raise self.ParseError("pattern does not match file names")
  4376. matches = matches[-1]
  4377. if len(matches) % 2:
  4378. raise self.ParseError("pattern does not match axis name and index")
  4379. axes = "".join(m for m in matches[::2] if m)
  4380. if not axes:
  4381. raise self.ParseError("pattern does not match file names")
  4382. indices = []
  4383. for fname in self.files:
  4384. matches = pattern.findall(fname)[-1]
  4385. if axes != "".join(m for m in matches[::2] if m):
  4386. raise ValueError("axes do not match within the image sequence")
  4387. indices.append([int(m) for m in matches[1::2] if m])
  4388. shape = tuple(numpy.max(indices, axis=0))
  4389. startindex = tuple(numpy.min(indices, axis=0))
  4390. shape = tuple(i - j + 1 for i, j in zip(shape, startindex))
  4391. if product(shape) != len(self.files):
  4392. warnings.warn("files are missing. Missing data are zeroed")
  4393. self.axes = axes.upper()
  4394. self.shape = shape
  4395. self._indices = indices
  4396. self._startindex = startindex
  4397. class FileHandle(object):
  4398. """Binary file handle.
  4399. A limited, special purpose file handler that can:
  4400. * handle embedded files (for CZI within CZI files)
  4401. * re-open closed files (for multi-file formats, such as OME-TIFF)
  4402. * read and write numpy arrays and records from file like objects
  4403. Only 'rb' and 'wb' modes are supported. Concurrently reading and writing
  4404. of the same stream is untested.
  4405. When initialized from another file handle, do not use it unless this
  4406. FileHandle is closed.
  4407. Attributes
  4408. ----------
  4409. name : str
  4410. Name of the file.
  4411. path : str
  4412. Absolute path to file.
  4413. size : int
  4414. Size of file in bytes.
  4415. is_file : bool
  4416. If True, file has a filno and can be memory-mapped.
  4417. All attributes are read-only.
  4418. """
  4419. __slots__ = (
  4420. "_fh",
  4421. "_file",
  4422. "_mode",
  4423. "_name",
  4424. "_dir",
  4425. "_lock",
  4426. "_offset",
  4427. "_size",
  4428. "_close",
  4429. "is_file",
  4430. )
  4431. def __init__(self, file, mode="rb", name=None, offset=None, size=None):
  4432. """Initialize file handle from file name or another file handle.
  4433. Parameters
  4434. ----------
  4435. file : str, pathlib.Path, binary stream, or FileHandle
  4436. File name or seekable binary stream, such as an open file
  4437. or BytesIO.
  4438. mode : str
  4439. File open mode in case 'file' is a file name. Must be 'rb' or 'wb'.
  4440. name : str
  4441. Optional name of file in case 'file' is a binary stream.
  4442. offset : int
  4443. Optional start position of embedded file. By default, this is
  4444. the current file position.
  4445. size : int
  4446. Optional size of embedded file. By default, this is the number
  4447. of bytes from the 'offset' to the end of the file.
  4448. """
  4449. self._file = file
  4450. self._fh = None
  4451. self._mode = mode
  4452. self._name = name
  4453. self._dir = ""
  4454. self._offset = offset
  4455. self._size = size
  4456. self._close = True
  4457. self.is_file = False
  4458. self._lock = NullContext()
  4459. self.open()
  4460. def open(self):
  4461. """Open or re-open file."""
  4462. if self._fh:
  4463. return # file is open
  4464. if isinstance(self._file, pathlib.Path):
  4465. self._file = str(self._file)
  4466. if isinstance(self._file, basestring):
  4467. # file name
  4468. self._file = os.path.realpath(self._file)
  4469. self._dir, self._name = os.path.split(self._file)
  4470. self._fh = open(self._file, self._mode)
  4471. self._close = True
  4472. if self._offset is None:
  4473. self._offset = 0
  4474. elif isinstance(self._file, FileHandle):
  4475. # FileHandle
  4476. self._fh = self._file._fh
  4477. if self._offset is None:
  4478. self._offset = 0
  4479. self._offset += self._file._offset
  4480. self._close = False
  4481. if not self._name:
  4482. if self._offset:
  4483. name, ext = os.path.splitext(self._file._name)
  4484. self._name = "%s@%i%s" % (name, self._offset, ext)
  4485. else:
  4486. self._name = self._file._name
  4487. if self._mode and self._mode != self._file._mode:
  4488. raise ValueError("FileHandle has wrong mode")
  4489. self._mode = self._file._mode
  4490. self._dir = self._file._dir
  4491. elif hasattr(self._file, "seek"):
  4492. # binary stream: open file, BytesIO
  4493. try:
  4494. self._file.tell()
  4495. except Exception:
  4496. raise ValueError("binary stream is not seekable")
  4497. self._fh = self._file
  4498. if self._offset is None:
  4499. self._offset = self._file.tell()
  4500. self._close = False
  4501. if not self._name:
  4502. try:
  4503. self._dir, self._name = os.path.split(self._fh.name)
  4504. except AttributeError:
  4505. self._name = "Unnamed binary stream"
  4506. try:
  4507. self._mode = self._fh.mode
  4508. except AttributeError:
  4509. pass
  4510. else:
  4511. raise ValueError(
  4512. "The first parameter must be a file name, "
  4513. "seekable binary stream, or FileHandle"
  4514. )
  4515. if self._offset:
  4516. self._fh.seek(self._offset)
  4517. if self._size is None:
  4518. pos = self._fh.tell()
  4519. self._fh.seek(self._offset, 2)
  4520. self._size = self._fh.tell()
  4521. self._fh.seek(pos)
  4522. try:
  4523. self._fh.fileno()
  4524. self.is_file = True
  4525. except Exception:
  4526. self.is_file = False
  4527. def read(self, size=-1):
  4528. """Read 'size' bytes from file, or until EOF is reached."""
  4529. if size < 0 and self._offset:
  4530. size = self._size
  4531. return self._fh.read(size)
  4532. def write(self, bytestring):
  4533. """Write bytestring to file."""
  4534. return self._fh.write(bytestring)
  4535. def flush(self):
  4536. """Flush write buffers if applicable."""
  4537. return self._fh.flush()
  4538. def memmap_array(self, dtype, shape, offset=0, mode="r", order="C"):
  4539. """Return numpy.memmap of data stored in file."""
  4540. if not self.is_file:
  4541. raise ValueError("Cannot memory-map file without fileno")
  4542. return numpy.memmap(
  4543. self._fh,
  4544. dtype=dtype,
  4545. mode=mode,
  4546. offset=self._offset + offset,
  4547. shape=shape,
  4548. order=order,
  4549. )
  4550. def read_array(
  4551. self, dtype, count=-1, sep="", chunksize=2**25, out=None, native=False
  4552. ):
  4553. """Return numpy array from file.
  4554. Work around numpy issue #2230, "numpy.fromfile does not accept
  4555. StringIO object" https://github.com/numpy/numpy/issues/2230.
  4556. """
  4557. fh = self._fh
  4558. dtype = numpy.dtype(dtype)
  4559. size = self._size if count < 0 else count * dtype.itemsize
  4560. if out is None:
  4561. try:
  4562. result = numpy.fromfile(fh, dtype, count, sep)
  4563. except IOError:
  4564. # ByteIO
  4565. data = fh.read(size)
  4566. result = numpy.frombuffer(data, dtype, count).copy()
  4567. if native and not result.dtype.isnative:
  4568. # swap byte order and dtype without copy
  4569. result.byteswap(True)
  4570. result = result.view(result.dtype.newbyteorder())
  4571. return result
  4572. # Read data from file in chunks and copy to output array
  4573. shape = out.shape
  4574. size = min(out.nbytes, size)
  4575. out = out.reshape(-1)
  4576. index = 0
  4577. while size > 0:
  4578. data = fh.read(min(chunksize, size))
  4579. datasize = len(data)
  4580. if datasize == 0:
  4581. break
  4582. size -= datasize
  4583. data = numpy.frombuffer(data, dtype)
  4584. out[index : index + data.size] = data
  4585. index += data.size
  4586. if hasattr(out, "flush"):
  4587. out.flush()
  4588. return out.reshape(shape)
  4589. def read_record(self, dtype, shape=1, byteorder=None):
  4590. """Return numpy record from file."""
  4591. rec = numpy.rec
  4592. try:
  4593. record = rec.fromfile(self._fh, dtype, shape, byteorder=byteorder)
  4594. except Exception:
  4595. dtype = numpy.dtype(dtype)
  4596. if shape is None:
  4597. shape = self._size // dtype.itemsize
  4598. size = product(sequence(shape)) * dtype.itemsize
  4599. data = self._fh.read(size)
  4600. record = rec.fromstring(data, dtype, shape, byteorder=byteorder)
  4601. return record[0] if shape == 1 else record
  4602. def write_empty(self, size):
  4603. """Append size bytes to file. Position must be at end of file."""
  4604. if size < 1:
  4605. return
  4606. self._fh.seek(size - 1, 1)
  4607. self._fh.write(b"\x00")
  4608. def write_array(self, data):
  4609. """Write numpy array to binary file."""
  4610. try:
  4611. data.tofile(self._fh)
  4612. except Exception:
  4613. # BytesIO
  4614. self._fh.write(data.tostring())
  4615. def tell(self):
  4616. """Return file's current position."""
  4617. return self._fh.tell() - self._offset
  4618. def seek(self, offset, whence=0):
  4619. """Set file's current position."""
  4620. if self._offset:
  4621. if whence == 0:
  4622. self._fh.seek(self._offset + offset, whence)
  4623. return
  4624. elif whence == 2 and self._size > 0:
  4625. self._fh.seek(self._offset + self._size + offset, 0)
  4626. return
  4627. self._fh.seek(offset, whence)
  4628. def close(self):
  4629. """Close file."""
  4630. if self._close and self._fh:
  4631. self._fh.close()
  4632. self._fh = None
  4633. def __enter__(self):
  4634. return self
  4635. def __exit__(self, exc_type, exc_value, traceback):
  4636. self.close()
  4637. def __getattr__(self, name):
  4638. """Return attribute from underlying file object."""
  4639. if self._offset:
  4640. warnings.warn("FileHandle: '%s' not implemented for embedded files" % name)
  4641. return getattr(self._fh, name)
  4642. @property
  4643. def name(self):
  4644. return self._name
  4645. @property
  4646. def dirname(self):
  4647. return self._dir
  4648. @property
  4649. def path(self):
  4650. return os.path.join(self._dir, self._name)
  4651. @property
  4652. def size(self):
  4653. return self._size
  4654. @property
  4655. def closed(self):
  4656. return self._fh is None
  4657. @property
  4658. def lock(self):
  4659. return self._lock
  4660. @lock.setter
  4661. def lock(self, value):
  4662. self._lock = threading.RLock() if value else NullContext()
  4663. class NullContext(object):
  4664. """Null context manager.
  4665. >>> with NullContext():
  4666. ... pass
  4667. """
  4668. def __enter__(self):
  4669. return self
  4670. def __exit__(self, exc_type, exc_value, traceback):
  4671. pass
  4672. class OpenFileCache(object):
  4673. """Keep files open."""
  4674. __slots__ = ("files", "past", "lock", "size")
  4675. def __init__(self, size, lock=None):
  4676. """Initialize open file cache."""
  4677. self.past = [] # FIFO of opened files
  4678. self.files = {} # refcounts of opened files
  4679. self.lock = NullContext() if lock is None else lock
  4680. self.size = int(size)
  4681. def open(self, filehandle):
  4682. """Re-open file if necessary."""
  4683. with self.lock:
  4684. if filehandle in self.files:
  4685. self.files[filehandle] += 1
  4686. elif filehandle.closed:
  4687. filehandle.open()
  4688. self.files[filehandle] = 1
  4689. self.past.append(filehandle)
  4690. def close(self, filehandle):
  4691. """Close opened file if no longer used."""
  4692. with self.lock:
  4693. if filehandle in self.files:
  4694. self.files[filehandle] -= 1
  4695. # trim the file cache
  4696. index = 0
  4697. size = len(self.past)
  4698. while size > self.size and index < size:
  4699. filehandle = self.past[index]
  4700. if self.files[filehandle] == 0:
  4701. filehandle.close()
  4702. del self.files[filehandle]
  4703. del self.past[index]
  4704. size -= 1
  4705. else:
  4706. index += 1
  4707. def clear(self):
  4708. """Close all opened files if not in use."""
  4709. with self.lock:
  4710. for filehandle, refcount in list(self.files.items()):
  4711. if refcount == 0:
  4712. filehandle.close()
  4713. del self.files[filehandle]
  4714. del self.past[self.past.index(filehandle)]
  4715. class LazyConst(object):
  4716. """Class whose attributes are computed on first access from its methods."""
  4717. def __init__(self, cls):
  4718. self._cls = cls
  4719. self.__doc__ = getattr(cls, "__doc__")
  4720. def __getattr__(self, name):
  4721. func = getattr(self._cls, name)
  4722. if not callable(func):
  4723. return func
  4724. try:
  4725. value = func()
  4726. except TypeError:
  4727. # Python 2 unbound method
  4728. value = func.__func__()
  4729. setattr(self, name, value)
  4730. return value
  4731. @LazyConst
  4732. class TIFF(object):
  4733. """Namespace for module constants."""
  4734. def TAGS():
  4735. # TIFF tag codes and names from TIFF6, TIFF/EP, EXIF, and other specs
  4736. return {
  4737. 11: "ProcessingSoftware",
  4738. 254: "NewSubfileType",
  4739. 255: "SubfileType",
  4740. 256: "ImageWidth",
  4741. 257: "ImageLength",
  4742. 258: "BitsPerSample",
  4743. 259: "Compression",
  4744. 262: "PhotometricInterpretation",
  4745. 263: "Thresholding",
  4746. 264: "CellWidth",
  4747. 265: "CellLength",
  4748. 266: "FillOrder",
  4749. 269: "DocumentName",
  4750. 270: "ImageDescription",
  4751. 271: "Make",
  4752. 272: "Model",
  4753. 273: "StripOffsets",
  4754. 274: "Orientation",
  4755. 277: "SamplesPerPixel",
  4756. 278: "RowsPerStrip",
  4757. 279: "StripByteCounts",
  4758. 280: "MinSampleValue",
  4759. 281: "MaxSampleValue",
  4760. 282: "XResolution",
  4761. 283: "YResolution",
  4762. 284: "PlanarConfiguration",
  4763. 285: "PageName",
  4764. 286: "XPosition",
  4765. 287: "YPosition",
  4766. 288: "FreeOffsets",
  4767. 289: "FreeByteCounts",
  4768. 290: "GrayResponseUnit",
  4769. 291: "GrayResponseCurve",
  4770. 292: "T4Options",
  4771. 293: "T6Options",
  4772. 296: "ResolutionUnit",
  4773. 297: "PageNumber",
  4774. 300: "ColorResponseUnit",
  4775. 301: "TransferFunction",
  4776. 305: "Software",
  4777. 306: "DateTime",
  4778. 315: "Artist",
  4779. 316: "HostComputer",
  4780. 317: "Predictor",
  4781. 318: "WhitePoint",
  4782. 319: "PrimaryChromaticities",
  4783. 320: "ColorMap",
  4784. 321: "HalftoneHints",
  4785. 322: "TileWidth",
  4786. 323: "TileLength",
  4787. 324: "TileOffsets",
  4788. 325: "TileByteCounts",
  4789. 326: "BadFaxLines",
  4790. 327: "CleanFaxData",
  4791. 328: "ConsecutiveBadFaxLines",
  4792. 330: "SubIFDs",
  4793. 332: "InkSet",
  4794. 333: "InkNames",
  4795. 334: "NumberOfInks",
  4796. 336: "DotRange",
  4797. 337: "TargetPrinter",
  4798. 338: "ExtraSamples",
  4799. 339: "SampleFormat",
  4800. 340: "SMinSampleValue",
  4801. 341: "SMaxSampleValue",
  4802. 342: "TransferRange",
  4803. 343: "ClipPath",
  4804. 344: "XClipPathUnits",
  4805. 345: "YClipPathUnits",
  4806. 346: "Indexed",
  4807. 347: "JPEGTables",
  4808. 351: "OPIProxy",
  4809. 400: "GlobalParametersIFD",
  4810. 401: "ProfileType",
  4811. 402: "FaxProfile",
  4812. 403: "CodingMethods",
  4813. 404: "VersionYear",
  4814. 405: "ModeNumber",
  4815. 433: "Decode",
  4816. 434: "DefaultImageColor",
  4817. 435: "T82Options",
  4818. 437: "JPEGTables_", # 347
  4819. 512: "JPEGProc",
  4820. 513: "JPEGInterchangeFormat",
  4821. 514: "JPEGInterchangeFormatLength",
  4822. 515: "JPEGRestartInterval",
  4823. 517: "JPEGLosslessPredictors",
  4824. 518: "JPEGPointTransforms",
  4825. 519: "JPEGQTables",
  4826. 520: "JPEGDCTables",
  4827. 521: "JPEGACTables",
  4828. 529: "YCbCrCoefficients",
  4829. 530: "YCbCrSubSampling",
  4830. 531: "YCbCrPositioning",
  4831. 532: "ReferenceBlackWhite",
  4832. 559: "StripRowCounts",
  4833. 700: "XMP", # XMLPacket
  4834. 769: "GDIGamma", # GDI+
  4835. 770: "ICCProfileDescriptor", # GDI+
  4836. 771: "SRGBRenderingIntent", # GDI+
  4837. 800: "ImageTitle", # GDI+
  4838. 999: "USPTO_Miscellaneous",
  4839. 4864: "AndorId", # TODO: Andor Technology 4864 - 5030
  4840. 4869: "AndorTemperature",
  4841. 4876: "AndorExposureTime",
  4842. 4878: "AndorKineticCycleTime",
  4843. 4879: "AndorAccumulations",
  4844. 4881: "AndorAcquisitionCycleTime",
  4845. 4882: "AndorReadoutTime",
  4846. 4884: "AndorPhotonCounting",
  4847. 4885: "AndorEmDacLevel",
  4848. 4890: "AndorFrames",
  4849. 4896: "AndorHorizontalFlip",
  4850. 4897: "AndorVerticalFlip",
  4851. 4898: "AndorClockwise",
  4852. 4899: "AndorCounterClockwise",
  4853. 4904: "AndorVerticalClockVoltage",
  4854. 4905: "AndorVerticalShiftSpeed",
  4855. 4907: "AndorPreAmpSetting",
  4856. 4908: "AndorCameraSerial",
  4857. 4911: "AndorActualTemperature",
  4858. 4912: "AndorBaselineClamp",
  4859. 4913: "AndorPrescans",
  4860. 4914: "AndorModel",
  4861. 4915: "AndorChipSizeX",
  4862. 4916: "AndorChipSizeY",
  4863. 4944: "AndorBaselineOffset",
  4864. 4966: "AndorSoftwareVersion",
  4865. 18246: "Rating",
  4866. 18247: "XP_DIP_XML",
  4867. 18248: "StitchInfo",
  4868. 18249: "RatingPercent",
  4869. 20481: "ResolutionXUnit", # GDI+
  4870. 20482: "ResolutionYUnit", # GDI+
  4871. 20483: "ResolutionXLengthUnit", # GDI+
  4872. 20484: "ResolutionYLengthUnit", # GDI+
  4873. 20485: "PrintFlags", # GDI+
  4874. 20486: "PrintFlagsVersion", # GDI+
  4875. 20487: "PrintFlagsCrop", # GDI+
  4876. 20488: "PrintFlagsBleedWidth", # GDI+
  4877. 20489: "PrintFlagsBleedWidthScale", # GDI+
  4878. 20490: "HalftoneLPI", # GDI+
  4879. 20491: "HalftoneLPIUnit", # GDI+
  4880. 20492: "HalftoneDegree", # GDI+
  4881. 20493: "HalftoneShape", # GDI+
  4882. 20494: "HalftoneMisc", # GDI+
  4883. 20495: "HalftoneScreen", # GDI+
  4884. 20496: "JPEGQuality", # GDI+
  4885. 20497: "GridSize", # GDI+
  4886. 20498: "ThumbnailFormat", # GDI+
  4887. 20499: "ThumbnailWidth", # GDI+
  4888. 20500: "ThumbnailHeight", # GDI+
  4889. 20501: "ThumbnailColorDepth", # GDI+
  4890. 20502: "ThumbnailPlanes", # GDI+
  4891. 20503: "ThumbnailRawBytes", # GDI+
  4892. 20504: "ThumbnailSize", # GDI+
  4893. 20505: "ThumbnailCompressedSize", # GDI+
  4894. 20506: "ColorTransferFunction", # GDI+
  4895. 20507: "ThumbnailData",
  4896. 20512: "ThumbnailImageWidth", # GDI+
  4897. 20513: "ThumbnailImageHeight", # GDI+
  4898. 20514: "ThumbnailBitsPerSample", # GDI+
  4899. 20515: "ThumbnailCompression",
  4900. 20516: "ThumbnailPhotometricInterp", # GDI+
  4901. 20517: "ThumbnailImageDescription", # GDI+
  4902. 20518: "ThumbnailEquipMake", # GDI+
  4903. 20519: "ThumbnailEquipModel", # GDI+
  4904. 20520: "ThumbnailStripOffsets", # GDI+
  4905. 20521: "ThumbnailOrientation", # GDI+
  4906. 20522: "ThumbnailSamplesPerPixel", # GDI+
  4907. 20523: "ThumbnailRowsPerStrip", # GDI+
  4908. 20524: "ThumbnailStripBytesCount", # GDI+
  4909. 20525: "ThumbnailResolutionX",
  4910. 20526: "ThumbnailResolutionY",
  4911. 20527: "ThumbnailPlanarConfig", # GDI+
  4912. 20528: "ThumbnailResolutionUnit",
  4913. 20529: "ThumbnailTransferFunction",
  4914. 20530: "ThumbnailSoftwareUsed", # GDI+
  4915. 20531: "ThumbnailDateTime", # GDI+
  4916. 20532: "ThumbnailArtist", # GDI+
  4917. 20533: "ThumbnailWhitePoint", # GDI+
  4918. 20534: "ThumbnailPrimaryChromaticities", # GDI+
  4919. 20535: "ThumbnailYCbCrCoefficients", # GDI+
  4920. 20536: "ThumbnailYCbCrSubsampling", # GDI+
  4921. 20537: "ThumbnailYCbCrPositioning",
  4922. 20538: "ThumbnailRefBlackWhite", # GDI+
  4923. 20539: "ThumbnailCopyRight", # GDI+
  4924. 20545: "InteroperabilityIndex",
  4925. 20546: "InteroperabilityVersion",
  4926. 20624: "LuminanceTable",
  4927. 20625: "ChrominanceTable",
  4928. 20736: "FrameDelay", # GDI+
  4929. 20737: "LoopCount", # GDI+
  4930. 20738: "GlobalPalette", # GDI+
  4931. 20739: "IndexBackground", # GDI+
  4932. 20740: "IndexTransparent", # GDI+
  4933. 20752: "PixelUnit", # GDI+
  4934. 20753: "PixelPerUnitX", # GDI+
  4935. 20754: "PixelPerUnitY", # GDI+
  4936. 20755: "PaletteHistogram", # GDI+
  4937. 28672: "SonyRawFileType", # Sony ARW
  4938. 28722: "VignettingCorrParams", # Sony ARW
  4939. 28725: "ChromaticAberrationCorrParams", # Sony ARW
  4940. 28727: "DistortionCorrParams", # Sony ARW
  4941. # Private tags >= 32768
  4942. 32781: "ImageID",
  4943. 32931: "WangTag1",
  4944. 32932: "WangAnnotation",
  4945. 32933: "WangTag3",
  4946. 32934: "WangTag4",
  4947. 32953: "ImageReferencePoints",
  4948. 32954: "RegionXformTackPoint",
  4949. 32955: "WarpQuadrilateral",
  4950. 32956: "AffineTransformMat",
  4951. 32995: "Matteing",
  4952. 32996: "DataType",
  4953. 32997: "ImageDepth",
  4954. 32998: "TileDepth",
  4955. 33300: "ImageFullWidth",
  4956. 33301: "ImageFullLength",
  4957. 33302: "TextureFormat",
  4958. 33303: "TextureWrapModes",
  4959. 33304: "FieldOfViewCotangent",
  4960. 33305: "MatrixWorldToScreen",
  4961. 33306: "MatrixWorldToCamera",
  4962. 33405: "Model2",
  4963. 33421: "CFARepeatPatternDim",
  4964. 33422: "CFAPattern",
  4965. 33423: "BatteryLevel",
  4966. 33424: "KodakIFD",
  4967. 33434: "ExposureTime",
  4968. 33437: "FNumber",
  4969. 33432: "Copyright",
  4970. 33445: "MDFileTag",
  4971. 33446: "MDScalePixel",
  4972. 33447: "MDColorTable",
  4973. 33448: "MDLabName",
  4974. 33449: "MDSampleInfo",
  4975. 33450: "MDPrepDate",
  4976. 33451: "MDPrepTime",
  4977. 33452: "MDFileUnits",
  4978. 33550: "ModelPixelScaleTag",
  4979. 33589: "AdventScale",
  4980. 33590: "AdventRevision",
  4981. 33628: "UIC1tag", # Metamorph Universal Imaging Corp STK
  4982. 33629: "UIC2tag",
  4983. 33630: "UIC3tag",
  4984. 33631: "UIC4tag",
  4985. 33723: "IPTCNAA",
  4986. 33858: "ExtendedTagsOffset", # DEFF points IFD with private tags
  4987. 33918: "IntergraphPacketData", # INGRPacketDataTag
  4988. 33919: "IntergraphFlagRegisters", # INGRFlagRegisters
  4989. 33920: "IntergraphMatrixTag", # IrasBTransformationMatrix
  4990. 33921: "INGRReserved",
  4991. 33922: "ModelTiepointTag",
  4992. 33923: "LeicaMagic",
  4993. 34016: "Site",
  4994. 34017: "ColorSequence",
  4995. 34018: "IT8Header",
  4996. 34019: "RasterPadding",
  4997. 34020: "BitsPerRunLength",
  4998. 34021: "BitsPerExtendedRunLength",
  4999. 34022: "ColorTable",
  5000. 34023: "ImageColorIndicator",
  5001. 34024: "BackgroundColorIndicator",
  5002. 34025: "ImageColorValue",
  5003. 34026: "BackgroundColorValue",
  5004. 34027: "PixelIntensityRange",
  5005. 34028: "TransparencyIndicator",
  5006. 34029: "ColorCharacterization",
  5007. 34030: "HCUsage",
  5008. 34031: "TrapIndicator",
  5009. 34032: "CMYKEquivalent",
  5010. 34118: "CZ_SEM", # Zeiss SEM
  5011. 34152: "AFCP_IPTC",
  5012. 34232: "PixelMagicJBIGOptions",
  5013. 34263: "JPLCartoIFD",
  5014. 34122: "IPLAB", # number of images
  5015. 34264: "ModelTransformationTag",
  5016. 34306: "WB_GRGBLevels", # Leaf MOS
  5017. 34310: "LeafData",
  5018. 34361: "MM_Header",
  5019. 34362: "MM_Stamp",
  5020. 34363: "MM_Unknown",
  5021. 34377: "ImageResources", # Photoshop
  5022. 34386: "MM_UserBlock",
  5023. 34412: "CZ_LSMINFO",
  5024. 34665: "ExifTag",
  5025. 34675: "InterColorProfile", # ICCProfile
  5026. 34680: "FEI_SFEG", #
  5027. 34682: "FEI_HELIOS", #
  5028. 34683: "FEI_TITAN", #
  5029. 34687: "FXExtensions",
  5030. 34688: "MultiProfiles",
  5031. 34689: "SharedData",
  5032. 34690: "T88Options",
  5033. 34710: "MarCCD", # offset to MarCCD header
  5034. 34732: "ImageLayer",
  5035. 34735: "GeoKeyDirectoryTag",
  5036. 34736: "GeoDoubleParamsTag",
  5037. 34737: "GeoAsciiParamsTag",
  5038. 34750: "JBIGOptions",
  5039. 34821: "PIXTIFF", # ? Pixel Translations Inc
  5040. 34850: "ExposureProgram",
  5041. 34852: "SpectralSensitivity",
  5042. 34853: "GPSTag", # GPSIFD
  5043. 34855: "ISOSpeedRatings",
  5044. 34856: "OECF",
  5045. 34857: "Interlace",
  5046. 34858: "TimeZoneOffset",
  5047. 34859: "SelfTimerMode",
  5048. 34864: "SensitivityType",
  5049. 34865: "StandardOutputSensitivity",
  5050. 34866: "RecommendedExposureIndex",
  5051. 34867: "ISOSpeed",
  5052. 34868: "ISOSpeedLatitudeyyy",
  5053. 34869: "ISOSpeedLatitudezzz",
  5054. 34908: "HylaFAXFaxRecvParams",
  5055. 34909: "HylaFAXFaxSubAddress",
  5056. 34910: "HylaFAXFaxRecvTime",
  5057. 34911: "FaxDcs",
  5058. 34929: "FedexEDR",
  5059. 34954: "LeafSubIFD",
  5060. 34959: "Aphelion1",
  5061. 34960: "Aphelion2",
  5062. 34961: "AphelionInternal", # ADCIS
  5063. 36864: "ExifVersion",
  5064. 36867: "DateTimeOriginal",
  5065. 36868: "DateTimeDigitized",
  5066. 36873: "GooglePlusUploadCode",
  5067. 36880: "OffsetTime",
  5068. 36881: "OffsetTimeOriginal",
  5069. 36882: "OffsetTimeDigitized",
  5070. # TODO: Pilatus/CHESS/TV6 36864..37120 conflicting with Exif tags
  5071. # 36864: 'TVX ?',
  5072. # 36865: 'TVX_NumExposure',
  5073. # 36866: 'TVX_NumBackground',
  5074. # 36867: 'TVX_ExposureTime',
  5075. # 36868: 'TVX_BackgroundTime',
  5076. # 36870: 'TVX ?',
  5077. # 36873: 'TVX_SubBpp',
  5078. # 36874: 'TVX_SubWide',
  5079. # 36875: 'TVX_SubHigh',
  5080. # 36876: 'TVX_BlackLevel',
  5081. # 36877: 'TVX_DarkCurrent',
  5082. # 36878: 'TVX_ReadNoise',
  5083. # 36879: 'TVX_DarkCurrentNoise',
  5084. # 36880: 'TVX_BeamMonitor',
  5085. # 37120: 'TVX_UserVariables', # A/D values
  5086. 37121: "ComponentsConfiguration",
  5087. 37122: "CompressedBitsPerPixel",
  5088. 37377: "ShutterSpeedValue",
  5089. 37378: "ApertureValue",
  5090. 37379: "BrightnessValue",
  5091. 37380: "ExposureBiasValue",
  5092. 37381: "MaxApertureValue",
  5093. 37382: "SubjectDistance",
  5094. 37383: "MeteringMode",
  5095. 37384: "LightSource",
  5096. 37385: "Flash",
  5097. 37386: "FocalLength",
  5098. 37387: "FlashEnergy_", # 37387
  5099. 37388: "SpatialFrequencyResponse_", # 37388
  5100. 37389: "Noise",
  5101. 37390: "FocalPlaneXResolution",
  5102. 37391: "FocalPlaneYResolution",
  5103. 37392: "FocalPlaneResolutionUnit",
  5104. 37393: "ImageNumber",
  5105. 37394: "SecurityClassification",
  5106. 37395: "ImageHistory",
  5107. 37396: "SubjectLocation",
  5108. 37397: "ExposureIndex",
  5109. 37398: "TIFFEPStandardID",
  5110. 37399: "SensingMethod",
  5111. 37434: "CIP3DataFile",
  5112. 37435: "CIP3Sheet",
  5113. 37436: "CIP3Side",
  5114. 37439: "StoNits",
  5115. 37500: "MakerNote",
  5116. 37510: "UserComment",
  5117. 37520: "SubsecTime",
  5118. 37521: "SubsecTimeOriginal",
  5119. 37522: "SubsecTimeDigitized",
  5120. 37679: "MODIText", # Microsoft Office Document Imaging
  5121. 37680: "MODIOLEPropertySetStorage",
  5122. 37681: "MODIPositioning",
  5123. 37706: "TVIPS", # offset to TemData structure
  5124. 37707: "TVIPS1",
  5125. 37708: "TVIPS2", # same TemData structure as undefined
  5126. 37724: "ImageSourceData", # Photoshop
  5127. 37888: "Temperature",
  5128. 37889: "Humidity",
  5129. 37890: "Pressure",
  5130. 37891: "WaterDepth",
  5131. 37892: "Acceleration",
  5132. 37893: "CameraElevationAngle",
  5133. 40001: "MC_IpWinScal", # Media Cybernetics
  5134. 40100: "MC_IdOld",
  5135. 40965: "InteroperabilityTag", # InteropOffset
  5136. 40091: "XPTitle",
  5137. 40092: "XPComment",
  5138. 40093: "XPAuthor",
  5139. 40094: "XPKeywords",
  5140. 40095: "XPSubject",
  5141. 40960: "FlashpixVersion",
  5142. 40961: "ColorSpace",
  5143. 40962: "PixelXDimension",
  5144. 40963: "PixelYDimension",
  5145. 40964: "RelatedSoundFile",
  5146. 40976: "SamsungRawPointersOffset",
  5147. 40977: "SamsungRawPointersLength",
  5148. 41217: "SamsungRawByteOrder",
  5149. 41218: "SamsungRawUnknown",
  5150. 41483: "FlashEnergy",
  5151. 41484: "SpatialFrequencyResponse",
  5152. 41485: "Noise_", # 37389
  5153. 41486: "FocalPlaneXResolution_", # 37390
  5154. 41487: "FocalPlaneYResolution_", # 37391
  5155. 41488: "FocalPlaneResolutionUnit_", # 37392
  5156. 41489: "ImageNumber_", # 37393
  5157. 41490: "SecurityClassification_", # 37394
  5158. 41491: "ImageHistory_", # 37395
  5159. 41492: "SubjectLocation_", # 37395
  5160. 41493: "ExposureIndex_ ", # 37397
  5161. 41494: "TIFF-EPStandardID",
  5162. 41495: "SensingMethod_", # 37399
  5163. 41728: "FileSource",
  5164. 41729: "SceneType",
  5165. 41730: "CFAPattern_", # 33422
  5166. 41985: "CustomRendered",
  5167. 41986: "ExposureMode",
  5168. 41987: "WhiteBalance",
  5169. 41988: "DigitalZoomRatio",
  5170. 41989: "FocalLengthIn35mmFilm",
  5171. 41990: "SceneCaptureType",
  5172. 41991: "GainControl",
  5173. 41992: "Contrast",
  5174. 41993: "Saturation",
  5175. 41994: "Sharpness",
  5176. 41995: "DeviceSettingDescription",
  5177. 41996: "SubjectDistanceRange",
  5178. 42016: "ImageUniqueID",
  5179. 42032: "CameraOwnerName",
  5180. 42033: "BodySerialNumber",
  5181. 42034: "LensSpecification",
  5182. 42035: "LensMake",
  5183. 42036: "LensModel",
  5184. 42037: "LensSerialNumber",
  5185. 42112: "GDAL_METADATA",
  5186. 42113: "GDAL_NODATA",
  5187. 42240: "Gamma",
  5188. 43314: "NIHImageHeader",
  5189. 44992: "ExpandSoftware",
  5190. 44993: "ExpandLens",
  5191. 44994: "ExpandFilm",
  5192. 44995: "ExpandFilterLens",
  5193. 44996: "ExpandScanner",
  5194. 44997: "ExpandFlashLamp",
  5195. 48129: "PixelFormat", # HDP and WDP
  5196. 48130: "Transformation",
  5197. 48131: "Uncompressed",
  5198. 48132: "ImageType",
  5199. 48256: "ImageWidth_", # 256
  5200. 48257: "ImageHeight_",
  5201. 48258: "WidthResolution",
  5202. 48259: "HeightResolution",
  5203. 48320: "ImageOffset",
  5204. 48321: "ImageByteCount",
  5205. 48322: "AlphaOffset",
  5206. 48323: "AlphaByteCount",
  5207. 48324: "ImageDataDiscard",
  5208. 48325: "AlphaDataDiscard",
  5209. 50215: "OceScanjobDescription",
  5210. 50216: "OceApplicationSelector",
  5211. 50217: "OceIdentificationNumber",
  5212. 50218: "OceImageLogicCharacteristics",
  5213. 50255: "Annotations",
  5214. 50288: "MC_Id", # Media Cybernetics
  5215. 50289: "MC_XYPosition",
  5216. 50290: "MC_ZPosition",
  5217. 50291: "MC_XYCalibration",
  5218. 50292: "MC_LensCharacteristics",
  5219. 50293: "MC_ChannelName",
  5220. 50294: "MC_ExcitationWavelength",
  5221. 50295: "MC_TimeStamp",
  5222. 50296: "MC_FrameProperties",
  5223. 50341: "PrintImageMatching",
  5224. 50495: "PCO_RAW", # TODO: PCO CamWare
  5225. 50547: "OriginalFileName",
  5226. 50560: "USPTO_OriginalContentType", # US Patent Office
  5227. 50561: "USPTO_RotationCode",
  5228. 50656: "CR2CFAPattern",
  5229. 50706: "DNGVersion", # DNG 50706 .. 51112
  5230. 50707: "DNGBackwardVersion",
  5231. 50708: "UniqueCameraModel",
  5232. 50709: "LocalizedCameraModel",
  5233. 50710: "CFAPlaneColor",
  5234. 50711: "CFALayout",
  5235. 50712: "LinearizationTable",
  5236. 50713: "BlackLevelRepeatDim",
  5237. 50714: "BlackLevel",
  5238. 50715: "BlackLevelDeltaH",
  5239. 50716: "BlackLevelDeltaV",
  5240. 50717: "WhiteLevel",
  5241. 50718: "DefaultScale",
  5242. 50719: "DefaultCropOrigin",
  5243. 50720: "DefaultCropSize",
  5244. 50721: "ColorMatrix1",
  5245. 50722: "ColorMatrix2",
  5246. 50723: "CameraCalibration1",
  5247. 50724: "CameraCalibration2",
  5248. 50725: "ReductionMatrix1",
  5249. 50726: "ReductionMatrix2",
  5250. 50727: "AnalogBalance",
  5251. 50728: "AsShotNeutral",
  5252. 50729: "AsShotWhiteXY",
  5253. 50730: "BaselineExposure",
  5254. 50731: "BaselineNoise",
  5255. 50732: "BaselineSharpness",
  5256. 50733: "BayerGreenSplit",
  5257. 50734: "LinearResponseLimit",
  5258. 50735: "CameraSerialNumber",
  5259. 50736: "LensInfo",
  5260. 50737: "ChromaBlurRadius",
  5261. 50738: "AntiAliasStrength",
  5262. 50739: "ShadowScale",
  5263. 50740: "DNGPrivateData",
  5264. 50741: "MakerNoteSafety",
  5265. 50752: "RawImageSegmentation",
  5266. 50778: "CalibrationIlluminant1",
  5267. 50779: "CalibrationIlluminant2",
  5268. 50780: "BestQualityScale",
  5269. 50781: "RawDataUniqueID",
  5270. 50784: "AliasLayerMetadata",
  5271. 50827: "OriginalRawFileName",
  5272. 50828: "OriginalRawFileData",
  5273. 50829: "ActiveArea",
  5274. 50830: "MaskedAreas",
  5275. 50831: "AsShotICCProfile",
  5276. 50832: "AsShotPreProfileMatrix",
  5277. 50833: "CurrentICCProfile",
  5278. 50834: "CurrentPreProfileMatrix",
  5279. 50838: "IJMetadataByteCounts",
  5280. 50839: "IJMetadata",
  5281. 50844: "RPCCoefficientTag",
  5282. 50879: "ColorimetricReference",
  5283. 50885: "SRawType",
  5284. 50898: "PanasonicTitle",
  5285. 50899: "PanasonicTitle2",
  5286. 50931: "CameraCalibrationSignature",
  5287. 50932: "ProfileCalibrationSignature",
  5288. 50933: "ProfileIFD",
  5289. 50934: "AsShotProfileName",
  5290. 50935: "NoiseReductionApplied",
  5291. 50936: "ProfileName",
  5292. 50937: "ProfileHueSatMapDims",
  5293. 50938: "ProfileHueSatMapData1",
  5294. 50939: "ProfileHueSatMapData2",
  5295. 50940: "ProfileToneCurve",
  5296. 50941: "ProfileEmbedPolicy",
  5297. 50942: "ProfileCopyright",
  5298. 50964: "ForwardMatrix1",
  5299. 50965: "ForwardMatrix2",
  5300. 50966: "PreviewApplicationName",
  5301. 50967: "PreviewApplicationVersion",
  5302. 50968: "PreviewSettingsName",
  5303. 50969: "PreviewSettingsDigest",
  5304. 50970: "PreviewColorSpace",
  5305. 50971: "PreviewDateTime",
  5306. 50972: "RawImageDigest",
  5307. 50973: "OriginalRawFileDigest",
  5308. 50974: "SubTileBlockSize",
  5309. 50975: "RowInterleaveFactor",
  5310. 50981: "ProfileLookTableDims",
  5311. 50982: "ProfileLookTableData",
  5312. 51008: "OpcodeList1",
  5313. 51009: "OpcodeList2",
  5314. 51022: "OpcodeList3",
  5315. 51023: "FibicsXML", #
  5316. 51041: "NoiseProfile",
  5317. 51043: "TimeCodes",
  5318. 51044: "FrameRate",
  5319. 51058: "TStop",
  5320. 51081: "ReelName",
  5321. 51089: "OriginalDefaultFinalSize",
  5322. 51090: "OriginalBestQualitySize",
  5323. 51091: "OriginalDefaultCropSize",
  5324. 51105: "CameraLabel",
  5325. 51107: "ProfileHueSatMapEncoding",
  5326. 51108: "ProfileLookTableEncoding",
  5327. 51109: "BaselineExposureOffset",
  5328. 51110: "DefaultBlackRender",
  5329. 51111: "NewRawImageDigest",
  5330. 51112: "RawToPreviewGain",
  5331. 51125: "DefaultUserCrop",
  5332. 51123: "MicroManagerMetadata",
  5333. 59932: "Padding",
  5334. 59933: "OffsetSchema",
  5335. # Reusable Tags 65000-65535
  5336. # 65000: Dimap_Document XML
  5337. # 65000-65112: Photoshop Camera RAW EXIF tags
  5338. # 65000: 'OwnerName',
  5339. # 65001: 'SerialNumber',
  5340. # 65002: 'Lens',
  5341. # 65024: 'KDC_IFD',
  5342. # 65100: 'RawFile',
  5343. # 65101: 'Converter',
  5344. # 65102: 'WhiteBalance',
  5345. # 65105: 'Exposure',
  5346. # 65106: 'Shadows',
  5347. # 65107: 'Brightness',
  5348. # 65108: 'Contrast',
  5349. # 65109: 'Saturation',
  5350. # 65110: 'Sharpness',
  5351. # 65111: 'Smoothness',
  5352. # 65112: 'MoireFilter',
  5353. 65200: "FlexXML", #
  5354. 65563: "PerSample",
  5355. }
  5356. def TAG_NAMES():
  5357. return {v: c for c, v in TIFF.TAGS.items()}
  5358. def TAG_READERS():
  5359. # Map TIFF tag codes to import functions
  5360. return {
  5361. 320: read_colormap,
  5362. # 700: read_bytes, # read_utf8,
  5363. # 34377: read_bytes,
  5364. 33723: read_bytes,
  5365. # 34675: read_bytes,
  5366. 33628: read_uic1tag, # Universal Imaging Corp STK
  5367. 33629: read_uic2tag,
  5368. 33630: read_uic3tag,
  5369. 33631: read_uic4tag,
  5370. 34118: read_cz_sem, # Carl Zeiss SEM
  5371. 34361: read_mm_header, # Olympus FluoView
  5372. 34362: read_mm_stamp,
  5373. 34363: read_numpy, # MM_Unknown
  5374. 34386: read_numpy, # MM_UserBlock
  5375. 34412: read_cz_lsminfo, # Carl Zeiss LSM
  5376. 34680: read_fei_metadata, # S-FEG
  5377. 34682: read_fei_metadata, # Helios NanoLab
  5378. 37706: read_tvips_header, # TVIPS EMMENU
  5379. 37724: read_bytes, # ImageSourceData
  5380. 33923: read_bytes, # read_leica_magic
  5381. 43314: read_nih_image_header,
  5382. # 40001: read_bytes,
  5383. 40100: read_bytes,
  5384. 50288: read_bytes,
  5385. 50296: read_bytes,
  5386. 50839: read_bytes,
  5387. 51123: read_json,
  5388. 34665: read_exif_ifd,
  5389. 34853: read_gps_ifd,
  5390. 40965: read_interoperability_ifd,
  5391. }
  5392. def TAG_TUPLE():
  5393. # Tags whose values must be stored as tuples
  5394. return frozenset((273, 279, 324, 325, 530, 531, 34736))
  5395. def TAG_ATTRIBUTES():
  5396. # Map tag codes to TiffPage attribute names
  5397. return {
  5398. "ImageWidth": "imagewidth",
  5399. "ImageLength": "imagelength",
  5400. "BitsPerSample": "bitspersample",
  5401. "Compression": "compression",
  5402. "PlanarConfiguration": "planarconfig",
  5403. "FillOrder": "fillorder",
  5404. "PhotometricInterpretation": "photometric",
  5405. "ColorMap": "colormap",
  5406. "ImageDescription": "description",
  5407. "ImageDescription1": "description1",
  5408. "SamplesPerPixel": "samplesperpixel",
  5409. "RowsPerStrip": "rowsperstrip",
  5410. "Software": "software",
  5411. "Predictor": "predictor",
  5412. "TileWidth": "tilewidth",
  5413. "TileLength": "tilelength",
  5414. "ExtraSamples": "extrasamples",
  5415. "SampleFormat": "sampleformat",
  5416. "ImageDepth": "imagedepth",
  5417. "TileDepth": "tiledepth",
  5418. }
  5419. def TAG_ENUM():
  5420. return {
  5421. # 254: TIFF.FILETYPE,
  5422. 255: TIFF.OFILETYPE,
  5423. 259: TIFF.COMPRESSION,
  5424. 262: TIFF.PHOTOMETRIC,
  5425. 263: TIFF.THRESHHOLD,
  5426. 266: TIFF.FILLORDER,
  5427. 274: TIFF.ORIENTATION,
  5428. 284: TIFF.PLANARCONFIG,
  5429. 290: TIFF.GRAYRESPONSEUNIT,
  5430. # 292: TIFF.GROUP3OPT,
  5431. # 293: TIFF.GROUP4OPT,
  5432. 296: TIFF.RESUNIT,
  5433. 300: TIFF.COLORRESPONSEUNIT,
  5434. 317: TIFF.PREDICTOR,
  5435. 338: TIFF.EXTRASAMPLE,
  5436. 339: TIFF.SAMPLEFORMAT,
  5437. # 512: TIFF.JPEGPROC,
  5438. # 531: TIFF.YCBCRPOSITION,
  5439. }
  5440. def FILETYPE():
  5441. class FILETYPE(enum.IntFlag):
  5442. # Python 3.6 only
  5443. UNDEFINED = 0
  5444. REDUCEDIMAGE = 1
  5445. PAGE = 2
  5446. MASK = 4
  5447. return FILETYPE
  5448. def OFILETYPE():
  5449. class OFILETYPE(enum.IntEnum):
  5450. UNDEFINED = 0
  5451. IMAGE = 1
  5452. REDUCEDIMAGE = 2
  5453. PAGE = 3
  5454. return OFILETYPE
  5455. def COMPRESSION():
  5456. class COMPRESSION(enum.IntEnum):
  5457. NONE = 1 # Uncompressed
  5458. CCITTRLE = 2 # CCITT 1D
  5459. CCITT_T4 = 3 # 'T4/Group 3 Fax',
  5460. CCITT_T6 = 4 # 'T6/Group 4 Fax',
  5461. LZW = 5
  5462. OJPEG = 6 # old-style JPEG
  5463. JPEG = 7
  5464. ADOBE_DEFLATE = 8
  5465. JBIG_BW = 9
  5466. JBIG_COLOR = 10
  5467. JPEG_99 = 99
  5468. KODAK_262 = 262
  5469. NEXT = 32766
  5470. SONY_ARW = 32767
  5471. PACKED_RAW = 32769
  5472. SAMSUNG_SRW = 32770
  5473. CCIRLEW = 32771
  5474. SAMSUNG_SRW2 = 32772
  5475. PACKBITS = 32773
  5476. THUNDERSCAN = 32809
  5477. IT8CTPAD = 32895
  5478. IT8LW = 32896
  5479. IT8MP = 32897
  5480. IT8BL = 32898
  5481. PIXARFILM = 32908
  5482. PIXARLOG = 32909
  5483. DEFLATE = 32946
  5484. DCS = 32947
  5485. APERIO_JP2000_YCBC = 33003 # Leica Aperio
  5486. APERIO_JP2000_RGB = 33005 # Leica Aperio
  5487. JBIG = 34661
  5488. SGILOG = 34676
  5489. SGILOG24 = 34677
  5490. JPEG2000 = 34712
  5491. NIKON_NEF = 34713
  5492. JBIG2 = 34715
  5493. MDI_BINARY = 34718 # 'Microsoft Document Imaging
  5494. MDI_PROGRESSIVE = 34719 # 'Microsoft Document Imaging
  5495. MDI_VECTOR = 34720 # 'Microsoft Document Imaging
  5496. JPEG_LOSSY = 34892
  5497. LZMA = 34925
  5498. ZSTD = 34926
  5499. OPS_PNG = 34933 # Objective Pathology Services
  5500. OPS_JPEGXR = 34934 # Objective Pathology Services
  5501. PIXTIFF = 50013
  5502. KODAK_DCR = 65000
  5503. PENTAX_PEF = 65535
  5504. # def __bool__(self): return self != 1 # Python 3.6 only
  5505. return COMPRESSION
  5506. def PHOTOMETRIC():
  5507. class PHOTOMETRIC(enum.IntEnum):
  5508. MINISWHITE = 0
  5509. MINISBLACK = 1
  5510. RGB = 2
  5511. PALETTE = 3
  5512. MASK = 4
  5513. SEPARATED = 5 # CMYK
  5514. YCBCR = 6
  5515. CIELAB = 8
  5516. ICCLAB = 9
  5517. ITULAB = 10
  5518. CFA = 32803 # Color Filter Array
  5519. LOGL = 32844
  5520. LOGLUV = 32845
  5521. LINEAR_RAW = 34892
  5522. return PHOTOMETRIC
  5523. def THRESHHOLD():
  5524. class THRESHHOLD(enum.IntEnum):
  5525. BILEVEL = 1
  5526. HALFTONE = 2
  5527. ERRORDIFFUSE = 3
  5528. return THRESHHOLD
  5529. def FILLORDER():
  5530. class FILLORDER(enum.IntEnum):
  5531. MSB2LSB = 1
  5532. LSB2MSB = 2
  5533. return FILLORDER
  5534. def ORIENTATION():
  5535. class ORIENTATION(enum.IntEnum):
  5536. TOPLEFT = 1
  5537. TOPRIGHT = 2
  5538. BOTRIGHT = 3
  5539. BOTLEFT = 4
  5540. LEFTTOP = 5
  5541. RIGHTTOP = 6
  5542. RIGHTBOT = 7
  5543. LEFTBOT = 8
  5544. return ORIENTATION
  5545. def PLANARCONFIG():
  5546. class PLANARCONFIG(enum.IntEnum):
  5547. CONTIG = 1
  5548. SEPARATE = 2
  5549. return PLANARCONFIG
  5550. def GRAYRESPONSEUNIT():
  5551. class GRAYRESPONSEUNIT(enum.IntEnum):
  5552. _10S = 1
  5553. _100S = 2
  5554. _1000S = 3
  5555. _10000S = 4
  5556. _100000S = 5
  5557. return GRAYRESPONSEUNIT
  5558. def GROUP4OPT():
  5559. class GROUP4OPT(enum.IntEnum):
  5560. UNCOMPRESSED = 2
  5561. return GROUP4OPT
  5562. def RESUNIT():
  5563. class RESUNIT(enum.IntEnum):
  5564. NONE = 1
  5565. INCH = 2
  5566. CENTIMETER = 3
  5567. # def __bool__(self): return self != 1 # Python 3.6 only
  5568. return RESUNIT
  5569. def COLORRESPONSEUNIT():
  5570. class COLORRESPONSEUNIT(enum.IntEnum):
  5571. _10S = 1
  5572. _100S = 2
  5573. _1000S = 3
  5574. _10000S = 4
  5575. _100000S = 5
  5576. return COLORRESPONSEUNIT
  5577. def PREDICTOR():
  5578. class PREDICTOR(enum.IntEnum):
  5579. NONE = 1
  5580. HORIZONTAL = 2
  5581. FLOATINGPOINT = 3
  5582. # def __bool__(self): return self != 1 # Python 3.6 only
  5583. return PREDICTOR
  5584. def EXTRASAMPLE():
  5585. class EXTRASAMPLE(enum.IntEnum):
  5586. UNSPECIFIED = 0
  5587. ASSOCALPHA = 1
  5588. UNASSALPHA = 2
  5589. return EXTRASAMPLE
  5590. def SAMPLEFORMAT():
  5591. class SAMPLEFORMAT(enum.IntEnum):
  5592. UINT = 1
  5593. INT = 2
  5594. IEEEFP = 3
  5595. VOID = 4
  5596. COMPLEXINT = 5
  5597. COMPLEXIEEEFP = 6
  5598. return SAMPLEFORMAT
  5599. def DATATYPES():
  5600. class DATATYPES(enum.IntEnum):
  5601. NOTYPE = 0
  5602. BYTE = 1
  5603. ASCII = 2
  5604. SHORT = 3
  5605. LONG = 4
  5606. RATIONAL = 5
  5607. SBYTE = 6
  5608. UNDEFINED = 7
  5609. SSHORT = 8
  5610. SLONG = 9
  5611. SRATIONAL = 10
  5612. FLOAT = 11
  5613. DOUBLE = 12
  5614. IFD = 13
  5615. UNICODE = 14
  5616. COMPLEX = 15
  5617. LONG8 = 16
  5618. SLONG8 = 17
  5619. IFD8 = 18
  5620. return DATATYPES
  5621. def DATA_FORMATS():
  5622. # Map TIFF DATATYPES to Python struct formats
  5623. return {
  5624. 1: "1B", # BYTE 8-bit unsigned integer.
  5625. 2: "1s", # ASCII 8-bit byte that contains a 7-bit ASCII code;
  5626. # the last byte must be NULL (binary zero).
  5627. 3: "1H", # SHORT 16-bit (2-byte) unsigned integer
  5628. 4: "1I", # LONG 32-bit (4-byte) unsigned integer.
  5629. 5: "2I", # RATIONAL Two LONGs: the first represents the numerator
  5630. # of a fraction; the second, the denominator.
  5631. 6: "1b", # SBYTE An 8-bit signed (twos-complement) integer.
  5632. 7: "1B", # UNDEFINED An 8-bit byte that may contain anything,
  5633. # depending on the definition of the field.
  5634. 8: "1h", # SSHORT A 16-bit (2-byte) signed (twos-complement)
  5635. # integer.
  5636. 9: "1i", # SLONG A 32-bit (4-byte) signed (twos-complement)
  5637. # integer.
  5638. 10: "2i", # SRATIONAL Two SLONGs: the first represents the
  5639. # numerator of a fraction, the second the denominator.
  5640. 11: "1f", # FLOAT Single precision (4-byte) IEEE format.
  5641. 12: "1d", # DOUBLE Double precision (8-byte) IEEE format.
  5642. 13: "1I", # IFD unsigned 4 byte IFD offset.
  5643. # 14: '', # UNICODE
  5644. # 15: '', # COMPLEX
  5645. 16: "1Q", # LONG8 unsigned 8 byte integer (BigTiff)
  5646. 17: "1q", # SLONG8 signed 8 byte integer (BigTiff)
  5647. 18: "1Q", # IFD8 unsigned 8 byte IFD offset (BigTiff)
  5648. }
  5649. def DATA_DTYPES():
  5650. # Map numpy dtypes to TIFF DATATYPES
  5651. return {
  5652. "B": 1,
  5653. "s": 2,
  5654. "H": 3,
  5655. "I": 4,
  5656. "2I": 5,
  5657. "b": 6,
  5658. "h": 8,
  5659. "i": 9,
  5660. "2i": 10,
  5661. "f": 11,
  5662. "d": 12,
  5663. "Q": 16,
  5664. "q": 17,
  5665. }
  5666. def SAMPLE_DTYPES():
  5667. # Map TIFF SampleFormats and BitsPerSample to numpy dtype
  5668. return {
  5669. (1, 1): "?", # bitmap
  5670. (1, 2): "B",
  5671. (1, 3): "B",
  5672. (1, 4): "B",
  5673. (1, 5): "B",
  5674. (1, 6): "B",
  5675. (1, 7): "B",
  5676. (1, 8): "B",
  5677. (1, 9): "H",
  5678. (1, 10): "H",
  5679. (1, 11): "H",
  5680. (1, 12): "H",
  5681. (1, 13): "H",
  5682. (1, 14): "H",
  5683. (1, 15): "H",
  5684. (1, 16): "H",
  5685. (1, 17): "I",
  5686. (1, 18): "I",
  5687. (1, 19): "I",
  5688. (1, 20): "I",
  5689. (1, 21): "I",
  5690. (1, 22): "I",
  5691. (1, 23): "I",
  5692. (1, 24): "I",
  5693. (1, 25): "I",
  5694. (1, 26): "I",
  5695. (1, 27): "I",
  5696. (1, 28): "I",
  5697. (1, 29): "I",
  5698. (1, 30): "I",
  5699. (1, 31): "I",
  5700. (1, 32): "I",
  5701. (1, 64): "Q",
  5702. (2, 8): "b",
  5703. (2, 16): "h",
  5704. (2, 32): "i",
  5705. (2, 64): "q",
  5706. (3, 16): "e",
  5707. (3, 32): "f",
  5708. (3, 64): "d",
  5709. (6, 64): "F",
  5710. (6, 128): "D",
  5711. (1, (5, 6, 5)): "B",
  5712. }
  5713. def COMPESSORS():
  5714. # Map COMPRESSION to compress functions and default compression levels
  5715. class Compressors(object):
  5716. """Delay import compressor functions."""
  5717. def __init__(self):
  5718. self._compressors = {8: (zlib.compress, 6), 32946: (zlib.compress, 6)}
  5719. def __getitem__(self, key):
  5720. if key in self._compressors:
  5721. return self._compressors[key]
  5722. if key == 34925:
  5723. try:
  5724. import lzma # delayed import
  5725. except ImportError:
  5726. try:
  5727. import backports.lzma as lzma # delayed import
  5728. except ImportError:
  5729. raise KeyError
  5730. def lzma_compress(x, level):
  5731. return lzma.compress(x)
  5732. self._compressors[key] = lzma_compress, 0
  5733. return lzma_compress, 0
  5734. if key == 34926:
  5735. try:
  5736. import zstd # delayed import
  5737. except ImportError:
  5738. raise KeyError
  5739. self._compressors[key] = zstd.compress, 9
  5740. return zstd.compress, 9
  5741. raise KeyError
  5742. def __contains__(self, key):
  5743. try:
  5744. self[key]
  5745. return True
  5746. except KeyError:
  5747. return False
  5748. return Compressors()
  5749. def DECOMPESSORS():
  5750. # Map COMPRESSION to decompress functions
  5751. class Decompressors(object):
  5752. """Delay import decompressor functions."""
  5753. def __init__(self):
  5754. self._decompressors = {
  5755. None: identityfunc,
  5756. 1: identityfunc,
  5757. 5: decode_lzw,
  5758. 8: zlib.decompress,
  5759. 32773: decode_packbits,
  5760. 32946: zlib.decompress,
  5761. }
  5762. def __getitem__(self, key):
  5763. if key in self._decompressors:
  5764. return self._decompressors[key]
  5765. if key == 7:
  5766. try:
  5767. from imagecodecs import jpeg, jpeg_12
  5768. except ImportError:
  5769. raise KeyError
  5770. def decode_jpeg(x, table, bps, colorspace=None):
  5771. if bps == 8:
  5772. return jpeg.decode_jpeg(x, table, colorspace)
  5773. elif bps == 12:
  5774. return jpeg_12.decode_jpeg_12(x, table, colorspace)
  5775. else:
  5776. raise ValueError("bitspersample not supported")
  5777. self._decompressors[key] = decode_jpeg
  5778. return decode_jpeg
  5779. if key == 34925:
  5780. try:
  5781. import lzma # delayed import
  5782. except ImportError:
  5783. try:
  5784. import backports.lzma as lzma # delayed import
  5785. except ImportError:
  5786. raise KeyError
  5787. self._decompressors[key] = lzma.decompress
  5788. return lzma.decompress
  5789. if key == 34926:
  5790. try:
  5791. import zstd # delayed import
  5792. except ImportError:
  5793. raise KeyError
  5794. self._decompressors[key] = zstd.decompress
  5795. return zstd.decompress
  5796. raise KeyError
  5797. def __contains__(self, item):
  5798. try:
  5799. self[item]
  5800. return True
  5801. except KeyError:
  5802. return False
  5803. return Decompressors()
  5804. def FRAME_ATTRS():
  5805. # Attributes that a TiffFrame shares with its keyframe
  5806. return set("shape ndim size dtype axes is_final".split())
  5807. def FILE_FLAGS():
  5808. # TiffFile and TiffPage 'is_\*' attributes
  5809. exclude = set(
  5810. "reduced final memmappable contiguous tiled " "chroma_subsampled".split()
  5811. )
  5812. return set(
  5813. a[3:] for a in dir(TiffPage) if a[:3] == "is_" and a[3:] not in exclude
  5814. )
  5815. def FILE_EXTENSIONS():
  5816. # TIFF file extensions
  5817. return tuple(
  5818. "tif tiff ome.tif lsm stk qptiff pcoraw "
  5819. "gel seq svs bif tf8 tf2 btf".split()
  5820. )
  5821. def FILEOPEN_FILTER():
  5822. # String for use in Windows File Open box
  5823. return [
  5824. ("%s files" % ext.upper(), "*.%s" % ext) for ext in TIFF.FILE_EXTENSIONS
  5825. ] + [("allfiles", "*")]
  5826. def AXES_LABELS():
  5827. # TODO: is there a standard for character axes labels?
  5828. axes = {
  5829. "X": "width",
  5830. "Y": "height",
  5831. "Z": "depth",
  5832. "S": "sample", # rgb(a)
  5833. "I": "series", # general sequence, plane, page, IFD
  5834. "T": "time",
  5835. "C": "channel", # color, emission wavelength
  5836. "A": "angle",
  5837. "P": "phase", # formerly F # P is Position in LSM!
  5838. "R": "tile", # region, point, mosaic
  5839. "H": "lifetime", # histogram
  5840. "E": "lambda", # excitation wavelength
  5841. "L": "exposure", # lux
  5842. "V": "event",
  5843. "Q": "other",
  5844. "M": "mosaic", # LSM 6
  5845. }
  5846. axes.update(dict((v, k) for k, v in axes.items()))
  5847. return axes
  5848. def ANDOR_TAGS():
  5849. # Andor Technology tags #4864 - 5030
  5850. return set(range(4864, 5030))
  5851. def EXIF_TAGS():
  5852. tags = {
  5853. # 65000 - 65112 Photoshop Camera RAW EXIF tags
  5854. 65000: "OwnerName",
  5855. 65001: "SerialNumber",
  5856. 65002: "Lens",
  5857. 65100: "RawFile",
  5858. 65101: "Converter",
  5859. 65102: "WhiteBalance",
  5860. 65105: "Exposure",
  5861. 65106: "Shadows",
  5862. 65107: "Brightness",
  5863. 65108: "Contrast",
  5864. 65109: "Saturation",
  5865. 65110: "Sharpness",
  5866. 65111: "Smoothness",
  5867. 65112: "MoireFilter",
  5868. }
  5869. tags.update(TIFF.TAGS)
  5870. return tags
  5871. def GPS_TAGS():
  5872. return {
  5873. 0: "GPSVersionID",
  5874. 1: "GPSLatitudeRef",
  5875. 2: "GPSLatitude",
  5876. 3: "GPSLongitudeRef",
  5877. 4: "GPSLongitude",
  5878. 5: "GPSAltitudeRef",
  5879. 6: "GPSAltitude",
  5880. 7: "GPSTimeStamp",
  5881. 8: "GPSSatellites",
  5882. 9: "GPSStatus",
  5883. 10: "GPSMeasureMode",
  5884. 11: "GPSDOP",
  5885. 12: "GPSSpeedRef",
  5886. 13: "GPSSpeed",
  5887. 14: "GPSTrackRef",
  5888. 15: "GPSTrack",
  5889. 16: "GPSImgDirectionRef",
  5890. 17: "GPSImgDirection",
  5891. 18: "GPSMapDatum",
  5892. 19: "GPSDestLatitudeRef",
  5893. 20: "GPSDestLatitude",
  5894. 21: "GPSDestLongitudeRef",
  5895. 22: "GPSDestLongitude",
  5896. 23: "GPSDestBearingRef",
  5897. 24: "GPSDestBearing",
  5898. 25: "GPSDestDistanceRef",
  5899. 26: "GPSDestDistance",
  5900. 27: "GPSProcessingMethod",
  5901. 28: "GPSAreaInformation",
  5902. 29: "GPSDateStamp",
  5903. 30: "GPSDifferential",
  5904. 31: "GPSHPositioningError",
  5905. }
  5906. def IOP_TAGS():
  5907. return {
  5908. 1: "InteroperabilityIndex",
  5909. 2: "InteroperabilityVersion",
  5910. 4096: "RelatedImageFileFormat",
  5911. 4097: "RelatedImageWidth",
  5912. 4098: "RelatedImageLength",
  5913. }
  5914. def GEO_KEYS():
  5915. return {
  5916. 1024: "GTModelTypeGeoKey",
  5917. 1025: "GTRasterTypeGeoKey",
  5918. 1026: "GTCitationGeoKey",
  5919. 2048: "GeographicTypeGeoKey",
  5920. 2049: "GeogCitationGeoKey",
  5921. 2050: "GeogGeodeticDatumGeoKey",
  5922. 2051: "GeogPrimeMeridianGeoKey",
  5923. 2052: "GeogLinearUnitsGeoKey",
  5924. 2053: "GeogLinearUnitSizeGeoKey",
  5925. 2054: "GeogAngularUnitsGeoKey",
  5926. 2055: "GeogAngularUnitsSizeGeoKey",
  5927. 2056: "GeogEllipsoidGeoKey",
  5928. 2057: "GeogSemiMajorAxisGeoKey",
  5929. 2058: "GeogSemiMinorAxisGeoKey",
  5930. 2059: "GeogInvFlatteningGeoKey",
  5931. 2060: "GeogAzimuthUnitsGeoKey",
  5932. 2061: "GeogPrimeMeridianLongGeoKey",
  5933. 2062: "GeogTOWGS84GeoKey",
  5934. 3059: "ProjLinearUnitsInterpCorrectGeoKey", # GDAL
  5935. 3072: "ProjectedCSTypeGeoKey",
  5936. 3073: "PCSCitationGeoKey",
  5937. 3074: "ProjectionGeoKey",
  5938. 3075: "ProjCoordTransGeoKey",
  5939. 3076: "ProjLinearUnitsGeoKey",
  5940. 3077: "ProjLinearUnitSizeGeoKey",
  5941. 3078: "ProjStdParallel1GeoKey",
  5942. 3079: "ProjStdParallel2GeoKey",
  5943. 3080: "ProjNatOriginLongGeoKey",
  5944. 3081: "ProjNatOriginLatGeoKey",
  5945. 3082: "ProjFalseEastingGeoKey",
  5946. 3083: "ProjFalseNorthingGeoKey",
  5947. 3084: "ProjFalseOriginLongGeoKey",
  5948. 3085: "ProjFalseOriginLatGeoKey",
  5949. 3086: "ProjFalseOriginEastingGeoKey",
  5950. 3087: "ProjFalseOriginNorthingGeoKey",
  5951. 3088: "ProjCenterLongGeoKey",
  5952. 3089: "ProjCenterLatGeoKey",
  5953. 3090: "ProjCenterEastingGeoKey",
  5954. 3091: "ProjFalseOriginNorthingGeoKey",
  5955. 3092: "ProjScaleAtNatOriginGeoKey",
  5956. 3093: "ProjScaleAtCenterGeoKey",
  5957. 3094: "ProjAzimuthAngleGeoKey",
  5958. 3095: "ProjStraightVertPoleLongGeoKey",
  5959. 3096: "ProjRectifiedGridAngleGeoKey",
  5960. 4096: "VerticalCSTypeGeoKey",
  5961. 4097: "VerticalCitationGeoKey",
  5962. 4098: "VerticalDatumGeoKey",
  5963. 4099: "VerticalUnitsGeoKey",
  5964. }
  5965. def GEO_CODES():
  5966. try:
  5967. from .tifffile_geodb import GEO_CODES # delayed import
  5968. except (ImportError, ValueError):
  5969. try:
  5970. from tifffile_geodb import GEO_CODES # delayed import
  5971. except (ImportError, ValueError):
  5972. GEO_CODES = {}
  5973. return GEO_CODES
  5974. def CZ_LSMINFO():
  5975. return [
  5976. ("MagicNumber", "u4"),
  5977. ("StructureSize", "i4"),
  5978. ("DimensionX", "i4"),
  5979. ("DimensionY", "i4"),
  5980. ("DimensionZ", "i4"),
  5981. ("DimensionChannels", "i4"),
  5982. ("DimensionTime", "i4"),
  5983. ("DataType", "i4"), # DATATYPES
  5984. ("ThumbnailX", "i4"),
  5985. ("ThumbnailY", "i4"),
  5986. ("VoxelSizeX", "f8"),
  5987. ("VoxelSizeY", "f8"),
  5988. ("VoxelSizeZ", "f8"),
  5989. ("OriginX", "f8"),
  5990. ("OriginY", "f8"),
  5991. ("OriginZ", "f8"),
  5992. ("ScanType", "u2"),
  5993. ("SpectralScan", "u2"),
  5994. ("TypeOfData", "u4"), # TYPEOFDATA
  5995. ("OffsetVectorOverlay", "u4"),
  5996. ("OffsetInputLut", "u4"),
  5997. ("OffsetOutputLut", "u4"),
  5998. ("OffsetChannelColors", "u4"),
  5999. ("TimeIntervall", "f8"),
  6000. ("OffsetChannelDataTypes", "u4"),
  6001. ("OffsetScanInformation", "u4"), # SCANINFO
  6002. ("OffsetKsData", "u4"),
  6003. ("OffsetTimeStamps", "u4"),
  6004. ("OffsetEventList", "u4"),
  6005. ("OffsetRoi", "u4"),
  6006. ("OffsetBleachRoi", "u4"),
  6007. ("OffsetNextRecording", "u4"),
  6008. # LSM 2.0 ends here
  6009. ("DisplayAspectX", "f8"),
  6010. ("DisplayAspectY", "f8"),
  6011. ("DisplayAspectZ", "f8"),
  6012. ("DisplayAspectTime", "f8"),
  6013. ("OffsetMeanOfRoisOverlay", "u4"),
  6014. ("OffsetTopoIsolineOverlay", "u4"),
  6015. ("OffsetTopoProfileOverlay", "u4"),
  6016. ("OffsetLinescanOverlay", "u4"),
  6017. ("ToolbarFlags", "u4"),
  6018. ("OffsetChannelWavelength", "u4"),
  6019. ("OffsetChannelFactors", "u4"),
  6020. ("ObjectiveSphereCorrection", "f8"),
  6021. ("OffsetUnmixParameters", "u4"),
  6022. # LSM 3.2, 4.0 end here
  6023. ("OffsetAcquisitionParameters", "u4"),
  6024. ("OffsetCharacteristics", "u4"),
  6025. ("OffsetPalette", "u4"),
  6026. ("TimeDifferenceX", "f8"),
  6027. ("TimeDifferenceY", "f8"),
  6028. ("TimeDifferenceZ", "f8"),
  6029. ("InternalUse1", "u4"),
  6030. ("DimensionP", "i4"),
  6031. ("DimensionM", "i4"),
  6032. ("DimensionsReserved", "16i4"),
  6033. ("OffsetTilePositions", "u4"),
  6034. ("", "9u4"), # Reserved
  6035. ("OffsetPositions", "u4"),
  6036. # ('', '21u4'), # must be 0
  6037. ]
  6038. def CZ_LSMINFO_READERS():
  6039. # Import functions for CZ_LSMINFO sub-records
  6040. # TODO: read more CZ_LSMINFO sub-records
  6041. return {
  6042. "ScanInformation": read_lsm_scaninfo,
  6043. "TimeStamps": read_lsm_timestamps,
  6044. "EventList": read_lsm_eventlist,
  6045. "ChannelColors": read_lsm_channelcolors,
  6046. "Positions": read_lsm_floatpairs,
  6047. "TilePositions": read_lsm_floatpairs,
  6048. "VectorOverlay": None,
  6049. "InputLut": None,
  6050. "OutputLut": None,
  6051. "TimeIntervall": None,
  6052. "ChannelDataTypes": None,
  6053. "KsData": None,
  6054. "Roi": None,
  6055. "BleachRoi": None,
  6056. "NextRecording": None,
  6057. "MeanOfRoisOverlay": None,
  6058. "TopoIsolineOverlay": None,
  6059. "TopoProfileOverlay": None,
  6060. "ChannelWavelength": None,
  6061. "SphereCorrection": None,
  6062. "ChannelFactors": None,
  6063. "UnmixParameters": None,
  6064. "AcquisitionParameters": None,
  6065. "Characteristics": None,
  6066. }
  6067. def CZ_LSMINFO_SCANTYPE():
  6068. # Map CZ_LSMINFO.ScanType to dimension order
  6069. return {
  6070. 0: "XYZCT", # 'Stack' normal x-y-z-scan
  6071. 1: "XYZCT", # 'Z-Scan' x-z-plane Y=1
  6072. 2: "XYZCT", # 'Line'
  6073. 3: "XYTCZ", # 'Time Series Plane' time series x-y XYCTZ ? Z=1
  6074. 4: "XYZTC", # 'Time Series z-Scan' time series x-z
  6075. 5: "XYTCZ", # 'Time Series Mean-of-ROIs'
  6076. 6: "XYZTC", # 'Time Series Stack' time series x-y-z
  6077. 7: "XYCTZ", # Spline Scan
  6078. 8: "XYCZT", # Spline Plane x-z
  6079. 9: "XYTCZ", # Time Series Spline Plane x-z
  6080. 10: "XYZCT", # 'Time Series Point' point mode
  6081. }
  6082. def CZ_LSMINFO_DIMENSIONS():
  6083. # Map dimension codes to CZ_LSMINFO attribute
  6084. return {
  6085. "X": "DimensionX",
  6086. "Y": "DimensionY",
  6087. "Z": "DimensionZ",
  6088. "C": "DimensionChannels",
  6089. "T": "DimensionTime",
  6090. "P": "DimensionP",
  6091. "M": "DimensionM",
  6092. }
  6093. def CZ_LSMINFO_DATATYPES():
  6094. # Description of CZ_LSMINFO.DataType
  6095. return {
  6096. 0: "varying data types",
  6097. 1: "8 bit unsigned integer",
  6098. 2: "12 bit unsigned integer",
  6099. 5: "32 bit float",
  6100. }
  6101. def CZ_LSMINFO_TYPEOFDATA():
  6102. # Description of CZ_LSMINFO.TypeOfData
  6103. return {
  6104. 0: "Original scan data",
  6105. 1: "Calculated data",
  6106. 2: "3D reconstruction",
  6107. 3: "Topography height map",
  6108. }
  6109. def CZ_LSMINFO_SCANINFO_ARRAYS():
  6110. return {
  6111. 0x20000000: "Tracks",
  6112. 0x30000000: "Lasers",
  6113. 0x60000000: "DetectionChannels",
  6114. 0x80000000: "IlluminationChannels",
  6115. 0xA0000000: "BeamSplitters",
  6116. 0xC0000000: "DataChannels",
  6117. 0x11000000: "Timers",
  6118. 0x13000000: "Markers",
  6119. }
  6120. def CZ_LSMINFO_SCANINFO_STRUCTS():
  6121. return {
  6122. # 0x10000000: 'Recording',
  6123. 0x40000000: "Track",
  6124. 0x50000000: "Laser",
  6125. 0x70000000: "DetectionChannel",
  6126. 0x90000000: "IlluminationChannel",
  6127. 0xB0000000: "BeamSplitter",
  6128. 0xD0000000: "DataChannel",
  6129. 0x12000000: "Timer",
  6130. 0x14000000: "Marker",
  6131. }
  6132. def CZ_LSMINFO_SCANINFO_ATTRIBUTES():
  6133. return {
  6134. # Recording
  6135. 0x10000001: "Name",
  6136. 0x10000002: "Description",
  6137. 0x10000003: "Notes",
  6138. 0x10000004: "Objective",
  6139. 0x10000005: "ProcessingSummary",
  6140. 0x10000006: "SpecialScanMode",
  6141. 0x10000007: "ScanType",
  6142. 0x10000008: "ScanMode",
  6143. 0x10000009: "NumberOfStacks",
  6144. 0x1000000A: "LinesPerPlane",
  6145. 0x1000000B: "SamplesPerLine",
  6146. 0x1000000C: "PlanesPerVolume",
  6147. 0x1000000D: "ImagesWidth",
  6148. 0x1000000E: "ImagesHeight",
  6149. 0x1000000F: "ImagesNumberPlanes",
  6150. 0x10000010: "ImagesNumberStacks",
  6151. 0x10000011: "ImagesNumberChannels",
  6152. 0x10000012: "LinscanXySize",
  6153. 0x10000013: "ScanDirection",
  6154. 0x10000014: "TimeSeries",
  6155. 0x10000015: "OriginalScanData",
  6156. 0x10000016: "ZoomX",
  6157. 0x10000017: "ZoomY",
  6158. 0x10000018: "ZoomZ",
  6159. 0x10000019: "Sample0X",
  6160. 0x1000001A: "Sample0Y",
  6161. 0x1000001B: "Sample0Z",
  6162. 0x1000001C: "SampleSpacing",
  6163. 0x1000001D: "LineSpacing",
  6164. 0x1000001E: "PlaneSpacing",
  6165. 0x1000001F: "PlaneWidth",
  6166. 0x10000020: "PlaneHeight",
  6167. 0x10000021: "VolumeDepth",
  6168. 0x10000023: "Nutation",
  6169. 0x10000034: "Rotation",
  6170. 0x10000035: "Precession",
  6171. 0x10000036: "Sample0time",
  6172. 0x10000037: "StartScanTriggerIn",
  6173. 0x10000038: "StartScanTriggerOut",
  6174. 0x10000039: "StartScanEvent",
  6175. 0x10000040: "StartScanTime",
  6176. 0x10000041: "StopScanTriggerIn",
  6177. 0x10000042: "StopScanTriggerOut",
  6178. 0x10000043: "StopScanEvent",
  6179. 0x10000044: "StopScanTime",
  6180. 0x10000045: "UseRois",
  6181. 0x10000046: "UseReducedMemoryRois",
  6182. 0x10000047: "User",
  6183. 0x10000048: "UseBcCorrection",
  6184. 0x10000049: "PositionBcCorrection1",
  6185. 0x10000050: "PositionBcCorrection2",
  6186. 0x10000051: "InterpolationY",
  6187. 0x10000052: "CameraBinning",
  6188. 0x10000053: "CameraSupersampling",
  6189. 0x10000054: "CameraFrameWidth",
  6190. 0x10000055: "CameraFrameHeight",
  6191. 0x10000056: "CameraOffsetX",
  6192. 0x10000057: "CameraOffsetY",
  6193. 0x10000059: "RtBinning",
  6194. 0x1000005A: "RtFrameWidth",
  6195. 0x1000005B: "RtFrameHeight",
  6196. 0x1000005C: "RtRegionWidth",
  6197. 0x1000005D: "RtRegionHeight",
  6198. 0x1000005E: "RtOffsetX",
  6199. 0x1000005F: "RtOffsetY",
  6200. 0x10000060: "RtZoom",
  6201. 0x10000061: "RtLinePeriod",
  6202. 0x10000062: "Prescan",
  6203. 0x10000063: "ScanDirectionZ",
  6204. # Track
  6205. 0x40000001: "MultiplexType", # 0 After Line; 1 After Frame
  6206. 0x40000002: "MultiplexOrder",
  6207. 0x40000003: "SamplingMode", # 0 Sample; 1 Line Avg; 2 Frame Avg
  6208. 0x40000004: "SamplingMethod", # 1 Mean; 2 Sum
  6209. 0x40000005: "SamplingNumber",
  6210. 0x40000006: "Acquire",
  6211. 0x40000007: "SampleObservationTime",
  6212. 0x4000000B: "TimeBetweenStacks",
  6213. 0x4000000C: "Name",
  6214. 0x4000000D: "Collimator1Name",
  6215. 0x4000000E: "Collimator1Position",
  6216. 0x4000000F: "Collimator2Name",
  6217. 0x40000010: "Collimator2Position",
  6218. 0x40000011: "IsBleachTrack",
  6219. 0x40000012: "IsBleachAfterScanNumber",
  6220. 0x40000013: "BleachScanNumber",
  6221. 0x40000014: "TriggerIn",
  6222. 0x40000015: "TriggerOut",
  6223. 0x40000016: "IsRatioTrack",
  6224. 0x40000017: "BleachCount",
  6225. 0x40000018: "SpiCenterWavelength",
  6226. 0x40000019: "PixelTime",
  6227. 0x40000021: "CondensorFrontlens",
  6228. 0x40000023: "FieldStopValue",
  6229. 0x40000024: "IdCondensorAperture",
  6230. 0x40000025: "CondensorAperture",
  6231. 0x40000026: "IdCondensorRevolver",
  6232. 0x40000027: "CondensorFilter",
  6233. 0x40000028: "IdTransmissionFilter1",
  6234. 0x40000029: "IdTransmission1",
  6235. 0x40000030: "IdTransmissionFilter2",
  6236. 0x40000031: "IdTransmission2",
  6237. 0x40000032: "RepeatBleach",
  6238. 0x40000033: "EnableSpotBleachPos",
  6239. 0x40000034: "SpotBleachPosx",
  6240. 0x40000035: "SpotBleachPosy",
  6241. 0x40000036: "SpotBleachPosz",
  6242. 0x40000037: "IdTubelens",
  6243. 0x40000038: "IdTubelensPosition",
  6244. 0x40000039: "TransmittedLight",
  6245. 0x4000003A: "ReflectedLight",
  6246. 0x4000003B: "SimultanGrabAndBleach",
  6247. 0x4000003C: "BleachPixelTime",
  6248. # Laser
  6249. 0x50000001: "Name",
  6250. 0x50000002: "Acquire",
  6251. 0x50000003: "Power",
  6252. # DetectionChannel
  6253. 0x70000001: "IntegrationMode",
  6254. 0x70000002: "SpecialMode",
  6255. 0x70000003: "DetectorGainFirst",
  6256. 0x70000004: "DetectorGainLast",
  6257. 0x70000005: "AmplifierGainFirst",
  6258. 0x70000006: "AmplifierGainLast",
  6259. 0x70000007: "AmplifierOffsFirst",
  6260. 0x70000008: "AmplifierOffsLast",
  6261. 0x70000009: "PinholeDiameter",
  6262. 0x7000000A: "CountingTrigger",
  6263. 0x7000000B: "Acquire",
  6264. 0x7000000C: "PointDetectorName",
  6265. 0x7000000D: "AmplifierName",
  6266. 0x7000000E: "PinholeName",
  6267. 0x7000000F: "FilterSetName",
  6268. 0x70000010: "FilterName",
  6269. 0x70000013: "IntegratorName",
  6270. 0x70000014: "ChannelName",
  6271. 0x70000015: "DetectorGainBc1",
  6272. 0x70000016: "DetectorGainBc2",
  6273. 0x70000017: "AmplifierGainBc1",
  6274. 0x70000018: "AmplifierGainBc2",
  6275. 0x70000019: "AmplifierOffsetBc1",
  6276. 0x70000020: "AmplifierOffsetBc2",
  6277. 0x70000021: "SpectralScanChannels",
  6278. 0x70000022: "SpiWavelengthStart",
  6279. 0x70000023: "SpiWavelengthStop",
  6280. 0x70000026: "DyeName",
  6281. 0x70000027: "DyeFolder",
  6282. # IlluminationChannel
  6283. 0x90000001: "Name",
  6284. 0x90000002: "Power",
  6285. 0x90000003: "Wavelength",
  6286. 0x90000004: "Aquire",
  6287. 0x90000005: "DetchannelName",
  6288. 0x90000006: "PowerBc1",
  6289. 0x90000007: "PowerBc2",
  6290. # BeamSplitter
  6291. 0xB0000001: "FilterSet",
  6292. 0xB0000002: "Filter",
  6293. 0xB0000003: "Name",
  6294. # DataChannel
  6295. 0xD0000001: "Name",
  6296. 0xD0000003: "Acquire",
  6297. 0xD0000004: "Color",
  6298. 0xD0000005: "SampleType",
  6299. 0xD0000006: "BitsPerSample",
  6300. 0xD0000007: "RatioType",
  6301. 0xD0000008: "RatioTrack1",
  6302. 0xD0000009: "RatioTrack2",
  6303. 0xD000000A: "RatioChannel1",
  6304. 0xD000000B: "RatioChannel2",
  6305. 0xD000000C: "RatioConst1",
  6306. 0xD000000D: "RatioConst2",
  6307. 0xD000000E: "RatioConst3",
  6308. 0xD000000F: "RatioConst4",
  6309. 0xD0000010: "RatioConst5",
  6310. 0xD0000011: "RatioConst6",
  6311. 0xD0000012: "RatioFirstImages1",
  6312. 0xD0000013: "RatioFirstImages2",
  6313. 0xD0000014: "DyeName",
  6314. 0xD0000015: "DyeFolder",
  6315. 0xD0000016: "Spectrum",
  6316. 0xD0000017: "Acquire",
  6317. # Timer
  6318. 0x12000001: "Name",
  6319. 0x12000002: "Description",
  6320. 0x12000003: "Interval",
  6321. 0x12000004: "TriggerIn",
  6322. 0x12000005: "TriggerOut",
  6323. 0x12000006: "ActivationTime",
  6324. 0x12000007: "ActivationNumber",
  6325. # Marker
  6326. 0x14000001: "Name",
  6327. 0x14000002: "Description",
  6328. 0x14000003: "TriggerIn",
  6329. 0x14000004: "TriggerOut",
  6330. }
  6331. def NIH_IMAGE_HEADER():
  6332. return [
  6333. ("FileID", "a8"),
  6334. ("nLines", "i2"),
  6335. ("PixelsPerLine", "i2"),
  6336. ("Version", "i2"),
  6337. ("OldLutMode", "i2"),
  6338. ("OldnColors", "i2"),
  6339. ("Colors", "u1", (3, 32)),
  6340. ("OldColorStart", "i2"),
  6341. ("ColorWidth", "i2"),
  6342. ("ExtraColors", "u2", (6, 3)),
  6343. ("nExtraColors", "i2"),
  6344. ("ForegroundIndex", "i2"),
  6345. ("BackgroundIndex", "i2"),
  6346. ("XScale", "f8"),
  6347. ("Unused2", "i2"),
  6348. ("Unused3", "i2"),
  6349. ("UnitsID", "i2"), # NIH_UNITS_TYPE
  6350. ("p1", [("x", "i2"), ("y", "i2")]),
  6351. ("p2", [("x", "i2"), ("y", "i2")]),
  6352. ("CurveFitType", "i2"), # NIH_CURVEFIT_TYPE
  6353. ("nCoefficients", "i2"),
  6354. ("Coeff", "f8", 6),
  6355. ("UMsize", "u1"),
  6356. ("UM", "a15"),
  6357. ("UnusedBoolean", "u1"),
  6358. ("BinaryPic", "b1"),
  6359. ("SliceStart", "i2"),
  6360. ("SliceEnd", "i2"),
  6361. ("ScaleMagnification", "f4"),
  6362. ("nSlices", "i2"),
  6363. ("SliceSpacing", "f4"),
  6364. ("CurrentSlice", "i2"),
  6365. ("FrameInterval", "f4"),
  6366. ("PixelAspectRatio", "f4"),
  6367. ("ColorStart", "i2"),
  6368. ("ColorEnd", "i2"),
  6369. ("nColors", "i2"),
  6370. ("Fill1", "3u2"),
  6371. ("Fill2", "3u2"),
  6372. ("Table", "u1"), # NIH_COLORTABLE_TYPE
  6373. ("LutMode", "u1"), # NIH_LUTMODE_TYPE
  6374. ("InvertedTable", "b1"),
  6375. ("ZeroClip", "b1"),
  6376. ("XUnitSize", "u1"),
  6377. ("XUnit", "a11"),
  6378. ("StackType", "i2"), # NIH_STACKTYPE_TYPE
  6379. # ('UnusedBytes', 'u1', 200)
  6380. ]
  6381. def NIH_COLORTABLE_TYPE():
  6382. return (
  6383. "CustomTable",
  6384. "AppleDefault",
  6385. "Pseudo20",
  6386. "Pseudo32",
  6387. "Rainbow",
  6388. "Fire1",
  6389. "Fire2",
  6390. "Ice",
  6391. "Grays",
  6392. "Spectrum",
  6393. )
  6394. def NIH_LUTMODE_TYPE():
  6395. return (
  6396. "PseudoColor",
  6397. "OldAppleDefault",
  6398. "OldSpectrum",
  6399. "GrayScale",
  6400. "ColorLut",
  6401. "CustomGrayscale",
  6402. )
  6403. def NIH_CURVEFIT_TYPE():
  6404. return (
  6405. "StraightLine",
  6406. "Poly2",
  6407. "Poly3",
  6408. "Poly4",
  6409. "Poly5",
  6410. "ExpoFit",
  6411. "PowerFit",
  6412. "LogFit",
  6413. "RodbardFit",
  6414. "SpareFit1",
  6415. "Uncalibrated",
  6416. "UncalibratedOD",
  6417. )
  6418. def NIH_UNITS_TYPE():
  6419. return (
  6420. "Nanometers",
  6421. "Micrometers",
  6422. "Millimeters",
  6423. "Centimeters",
  6424. "Meters",
  6425. "Kilometers",
  6426. "Inches",
  6427. "Feet",
  6428. "Miles",
  6429. "Pixels",
  6430. "OtherUnits",
  6431. )
  6432. def NIH_STACKTYPE_TYPE():
  6433. return ("VolumeStack", "RGBStack", "MovieStack", "HSVStack")
  6434. def TVIPS_HEADER_V1():
  6435. # TVIPS TemData structure from EMMENU Help file
  6436. return [
  6437. ("Version", "i4"),
  6438. ("CommentV1", "a80"),
  6439. ("HighTension", "i4"),
  6440. ("SphericalAberration", "i4"),
  6441. ("IlluminationAperture", "i4"),
  6442. ("Magnification", "i4"),
  6443. ("PostMagnification", "i4"),
  6444. ("FocalLength", "i4"),
  6445. ("Defocus", "i4"),
  6446. ("Astigmatism", "i4"),
  6447. ("AstigmatismDirection", "i4"),
  6448. ("BiprismVoltage", "i4"),
  6449. ("SpecimenTiltAngle", "i4"),
  6450. ("SpecimenTiltDirection", "i4"),
  6451. ("IlluminationTiltDirection", "i4"),
  6452. ("IlluminationTiltAngle", "i4"),
  6453. ("ImageMode", "i4"),
  6454. ("EnergySpread", "i4"),
  6455. ("ChromaticAberration", "i4"),
  6456. ("ShutterType", "i4"),
  6457. ("DefocusSpread", "i4"),
  6458. ("CcdNumber", "i4"),
  6459. ("CcdSize", "i4"),
  6460. ("OffsetXV1", "i4"),
  6461. ("OffsetYV1", "i4"),
  6462. ("PhysicalPixelSize", "i4"),
  6463. ("Binning", "i4"),
  6464. ("ReadoutSpeed", "i4"),
  6465. ("GainV1", "i4"),
  6466. ("SensitivityV1", "i4"),
  6467. ("ExposureTimeV1", "i4"),
  6468. ("FlatCorrected", "i4"),
  6469. ("DeadPxCorrected", "i4"),
  6470. ("ImageMean", "i4"),
  6471. ("ImageStd", "i4"),
  6472. ("DisplacementX", "i4"),
  6473. ("DisplacementY", "i4"),
  6474. ("DateV1", "i4"),
  6475. ("TimeV1", "i4"),
  6476. ("ImageMin", "i4"),
  6477. ("ImageMax", "i4"),
  6478. ("ImageStatisticsQuality", "i4"),
  6479. ]
  6480. def TVIPS_HEADER_V2():
  6481. return [
  6482. ("ImageName", "V160"), # utf16
  6483. ("ImageFolder", "V160"),
  6484. ("ImageSizeX", "i4"),
  6485. ("ImageSizeY", "i4"),
  6486. ("ImageSizeZ", "i4"),
  6487. ("ImageSizeE", "i4"),
  6488. ("ImageDataType", "i4"),
  6489. ("Date", "i4"),
  6490. ("Time", "i4"),
  6491. ("Comment", "V1024"),
  6492. ("ImageHistory", "V1024"),
  6493. ("Scaling", "16f4"),
  6494. ("ImageStatistics", "16c16"),
  6495. ("ImageType", "i4"),
  6496. ("ImageDisplaType", "i4"),
  6497. ("PixelSizeX", "f4"), # distance between two px in x, [nm]
  6498. ("PixelSizeY", "f4"), # distance between two px in y, [nm]
  6499. ("ImageDistanceZ", "f4"),
  6500. ("ImageDistanceE", "f4"),
  6501. ("ImageMisc", "32f4"),
  6502. ("TemType", "V160"),
  6503. ("TemHighTension", "f4"),
  6504. ("TemAberrations", "32f4"),
  6505. ("TemEnergy", "32f4"),
  6506. ("TemMode", "i4"),
  6507. ("TemMagnification", "f4"),
  6508. ("TemMagnificationCorrection", "f4"),
  6509. ("PostMagnification", "f4"),
  6510. ("TemStageType", "i4"),
  6511. ("TemStagePosition", "5f4"), # x, y, z, a, b
  6512. ("TemImageShift", "2f4"),
  6513. ("TemBeamShift", "2f4"),
  6514. ("TemBeamTilt", "2f4"),
  6515. ("TilingParameters", "7f4"), # 0: tiling? 1:x 2:y 3: max x
  6516. # 4: max y 5: overlap x 6: overlap y
  6517. ("TemIllumination", "3f4"), # 0: spotsize 1: intensity
  6518. ("TemShutter", "i4"),
  6519. ("TemMisc", "32f4"),
  6520. ("CameraType", "V160"),
  6521. ("PhysicalPixelSizeX", "f4"),
  6522. ("PhysicalPixelSizeY", "f4"),
  6523. ("OffsetX", "i4"),
  6524. ("OffsetY", "i4"),
  6525. ("BinningX", "i4"),
  6526. ("BinningY", "i4"),
  6527. ("ExposureTime", "f4"),
  6528. ("Gain", "f4"),
  6529. ("ReadoutRate", "f4"),
  6530. ("FlatfieldDescription", "V160"),
  6531. ("Sensitivity", "f4"),
  6532. ("Dose", "f4"),
  6533. ("CamMisc", "32f4"),
  6534. ("FeiMicroscopeInformation", "V1024"),
  6535. ("FeiSpecimenInformation", "V1024"),
  6536. ("Magic", "u4"),
  6537. ]
  6538. def MM_HEADER():
  6539. # Olympus FluoView MM_Header
  6540. MM_DIMENSION = [
  6541. ("Name", "a16"),
  6542. ("Size", "i4"),
  6543. ("Origin", "f8"),
  6544. ("Resolution", "f8"),
  6545. ("Unit", "a64"),
  6546. ]
  6547. return [
  6548. ("HeaderFlag", "i2"),
  6549. ("ImageType", "u1"),
  6550. ("ImageName", "a257"),
  6551. ("OffsetData", "u4"),
  6552. ("PaletteSize", "i4"),
  6553. ("OffsetPalette0", "u4"),
  6554. ("OffsetPalette1", "u4"),
  6555. ("CommentSize", "i4"),
  6556. ("OffsetComment", "u4"),
  6557. ("Dimensions", MM_DIMENSION, 10),
  6558. ("OffsetPosition", "u4"),
  6559. ("MapType", "i2"),
  6560. ("MapMin", "f8"),
  6561. ("MapMax", "f8"),
  6562. ("MinValue", "f8"),
  6563. ("MaxValue", "f8"),
  6564. ("OffsetMap", "u4"),
  6565. ("Gamma", "f8"),
  6566. ("Offset", "f8"),
  6567. ("GrayChannel", MM_DIMENSION),
  6568. ("OffsetThumbnail", "u4"),
  6569. ("VoiceField", "i4"),
  6570. ("OffsetVoiceField", "u4"),
  6571. ]
  6572. def MM_DIMENSIONS():
  6573. # Map FluoView MM_Header.Dimensions to axes characters
  6574. return {
  6575. "X": "X",
  6576. "Y": "Y",
  6577. "Z": "Z",
  6578. "T": "T",
  6579. "CH": "C",
  6580. "WAVELENGTH": "C",
  6581. "TIME": "T",
  6582. "XY": "R",
  6583. "EVENT": "V",
  6584. "EXPOSURE": "L",
  6585. }
  6586. def UIC_TAGS():
  6587. # Map Universal Imaging Corporation MetaMorph internal tag ids to
  6588. # name and type
  6589. from fractions import Fraction # delayed import
  6590. return [
  6591. ("AutoScale", int),
  6592. ("MinScale", int),
  6593. ("MaxScale", int),
  6594. ("SpatialCalibration", int),
  6595. ("XCalibration", Fraction),
  6596. ("YCalibration", Fraction),
  6597. ("CalibrationUnits", str),
  6598. ("Name", str),
  6599. ("ThreshState", int),
  6600. ("ThreshStateRed", int),
  6601. ("tagid_10", None), # undefined
  6602. ("ThreshStateGreen", int),
  6603. ("ThreshStateBlue", int),
  6604. ("ThreshStateLo", int),
  6605. ("ThreshStateHi", int),
  6606. ("Zoom", int),
  6607. ("CreateTime", julian_datetime),
  6608. ("LastSavedTime", julian_datetime),
  6609. ("currentBuffer", int),
  6610. ("grayFit", None),
  6611. ("grayPointCount", None),
  6612. ("grayX", Fraction),
  6613. ("grayY", Fraction),
  6614. ("grayMin", Fraction),
  6615. ("grayMax", Fraction),
  6616. ("grayUnitName", str),
  6617. ("StandardLUT", int),
  6618. ("wavelength", int),
  6619. ("StagePosition", "(%i,2,2)u4"), # N xy positions as fract
  6620. ("CameraChipOffset", "(%i,2,2)u4"), # N xy offsets as fract
  6621. ("OverlayMask", None),
  6622. ("OverlayCompress", None),
  6623. ("Overlay", None),
  6624. ("SpecialOverlayMask", None),
  6625. ("SpecialOverlayCompress", None),
  6626. ("SpecialOverlay", None),
  6627. ("ImageProperty", read_uic_image_property),
  6628. ("StageLabel", "%ip"), # N str
  6629. ("AutoScaleLoInfo", Fraction),
  6630. ("AutoScaleHiInfo", Fraction),
  6631. ("AbsoluteZ", "(%i,2)u4"), # N fractions
  6632. ("AbsoluteZValid", "(%i,)u4"), # N long
  6633. ("Gamma", "I"), # 'I' uses offset
  6634. ("GammaRed", "I"),
  6635. ("GammaGreen", "I"),
  6636. ("GammaBlue", "I"),
  6637. ("CameraBin", "2I"),
  6638. ("NewLUT", int),
  6639. ("ImagePropertyEx", None),
  6640. ("PlaneProperty", int),
  6641. ("UserLutTable", "(256,3)u1"),
  6642. ("RedAutoScaleInfo", int),
  6643. ("RedAutoScaleLoInfo", Fraction),
  6644. ("RedAutoScaleHiInfo", Fraction),
  6645. ("RedMinScaleInfo", int),
  6646. ("RedMaxScaleInfo", int),
  6647. ("GreenAutoScaleInfo", int),
  6648. ("GreenAutoScaleLoInfo", Fraction),
  6649. ("GreenAutoScaleHiInfo", Fraction),
  6650. ("GreenMinScaleInfo", int),
  6651. ("GreenMaxScaleInfo", int),
  6652. ("BlueAutoScaleInfo", int),
  6653. ("BlueAutoScaleLoInfo", Fraction),
  6654. ("BlueAutoScaleHiInfo", Fraction),
  6655. ("BlueMinScaleInfo", int),
  6656. ("BlueMaxScaleInfo", int),
  6657. # ('OverlayPlaneColor', read_uic_overlay_plane_color),
  6658. ]
  6659. def PILATUS_HEADER():
  6660. # PILATUS CBF Header Specification, Version 1.4
  6661. # Map key to [value_indices], type
  6662. return {
  6663. "Detector": ([slice(1, None)], str),
  6664. "Pixel_size": ([1, 4], float),
  6665. "Silicon": ([3], float),
  6666. "Exposure_time": ([1], float),
  6667. "Exposure_period": ([1], float),
  6668. "Tau": ([1], float),
  6669. "Count_cutoff": ([1], int),
  6670. "Threshold_setting": ([1], float),
  6671. "Gain_setting": ([1, 2], str),
  6672. "N_excluded_pixels": ([1], int),
  6673. "Excluded_pixels": ([1], str),
  6674. "Flat_field": ([1], str),
  6675. "Trim_file": ([1], str),
  6676. "Image_path": ([1], str),
  6677. # optional
  6678. "Wavelength": ([1], float),
  6679. "Energy_range": ([1, 2], float),
  6680. "Detector_distance": ([1], float),
  6681. "Detector_Voffset": ([1], float),
  6682. "Beam_xy": ([1, 2], float),
  6683. "Flux": ([1], str),
  6684. "Filter_transmission": ([1], float),
  6685. "Start_angle": ([1], float),
  6686. "Angle_increment": ([1], float),
  6687. "Detector_2theta": ([1], float),
  6688. "Polarization": ([1], float),
  6689. "Alpha": ([1], float),
  6690. "Kappa": ([1], float),
  6691. "Phi": ([1], float),
  6692. "Phi_increment": ([1], float),
  6693. "Chi": ([1], float),
  6694. "Chi_increment": ([1], float),
  6695. "Oscillation_axis": ([slice(1, None)], str),
  6696. "N_oscillations": ([1], int),
  6697. "Start_position": ([1], float),
  6698. "Position_increment": ([1], float),
  6699. "Shutter_time": ([1], float),
  6700. "Omega": ([1], float),
  6701. "Omega_increment": ([1], float),
  6702. }
  6703. def REVERSE_BITORDER_BYTES():
  6704. # Bytes with reversed bitorder
  6705. return (
  6706. b"\x00\x80@\xc0 \xa0`\xe0\x10\x90P\xd00\xb0p\xf0\x08\x88H\xc8("
  6707. b"\xa8h\xe8\x18\x98X\xd88\xb8x\xf8\x04\x84D\xc4$\xa4d\xe4\x14"
  6708. b"\x94T\xd44\xb4t\xf4\x0c\x8cL\xcc,\xacl\xec\x1c\x9c\\\xdc<\xbc|"
  6709. b'\xfc\x02\x82B\xc2"\xa2b\xe2\x12\x92R\xd22\xb2r\xf2\n\x8aJ\xca*'
  6710. b"\xaaj\xea\x1a\x9aZ\xda:\xbaz\xfa\x06\x86F\xc6&\xa6f\xe6\x16"
  6711. b"\x96V\xd66\xb6v\xf6\x0e\x8eN\xce.\xaen\xee\x1e\x9e^\xde>\xbe~"
  6712. b"\xfe\x01\x81A\xc1!\xa1a\xe1\x11\x91Q\xd11\xb1q\xf1\t\x89I\xc9)"
  6713. b"\xa9i\xe9\x19\x99Y\xd99\xb9y\xf9\x05\x85E\xc5%\xa5e\xe5\x15"
  6714. b"\x95U\xd55\xb5u\xf5\r\x8dM\xcd-\xadm\xed\x1d\x9d]\xdd=\xbd}"
  6715. b"\xfd\x03\x83C\xc3#\xa3c\xe3\x13\x93S\xd33\xb3s\xf3\x0b\x8bK"
  6716. b"\xcb+\xabk\xeb\x1b\x9b[\xdb;\xbb{\xfb\x07\x87G\xc7'\xa7g\xe7"
  6717. b"\x17\x97W\xd77\xb7w\xf7\x0f\x8fO\xcf/\xafo\xef\x1f\x9f_"
  6718. b"\xdf?\xbf\x7f\xff"
  6719. )
  6720. def REVERSE_BITORDER_ARRAY():
  6721. # Numpy array of bytes with reversed bitorder
  6722. return numpy.frombuffer(TIFF.REVERSE_BITORDER_BYTES, dtype="uint8")
  6723. def ALLOCATIONGRANULARITY():
  6724. # alignment for writing contiguous data to TIFF
  6725. import mmap # delayed import
  6726. return mmap.ALLOCATIONGRANULARITY
  6727. def read_tags(fh, byteorder, offsetsize, tagnames, customtags=None, maxifds=None):
  6728. """Read tags from chain of IFDs and return as list of dicts.
  6729. The file handle position must be at a valid IFD header.
  6730. """
  6731. if offsetsize == 4:
  6732. offsetformat = byteorder + "I"
  6733. tagnosize = 2
  6734. tagnoformat = byteorder + "H"
  6735. tagsize = 12
  6736. tagformat1 = byteorder + "HH"
  6737. tagformat2 = byteorder + "I4s"
  6738. elif offsetsize == 8:
  6739. offsetformat = byteorder + "Q"
  6740. tagnosize = 8
  6741. tagnoformat = byteorder + "Q"
  6742. tagsize = 20
  6743. tagformat1 = byteorder + "HH"
  6744. tagformat2 = byteorder + "Q8s"
  6745. else:
  6746. raise ValueError("invalid offset size")
  6747. if customtags is None:
  6748. customtags = {}
  6749. if maxifds is None:
  6750. maxifds = 2**32
  6751. result = []
  6752. unpack = struct.unpack
  6753. offset = fh.tell()
  6754. while len(result) < maxifds:
  6755. # loop over IFDs
  6756. try:
  6757. tagno = unpack(tagnoformat, fh.read(tagnosize))[0]
  6758. if tagno > 4096:
  6759. raise ValueError("suspicious number of tags")
  6760. except Exception:
  6761. warnings.warn("corrupted tag list at offset %i" % offset)
  6762. break
  6763. tags = {}
  6764. data = fh.read(tagsize * tagno)
  6765. pos = fh.tell()
  6766. index = 0
  6767. for _ in range(tagno):
  6768. code, type_ = unpack(tagformat1, data[index : index + 4])
  6769. count, value = unpack(tagformat2, data[index + 4 : index + tagsize])
  6770. index += tagsize
  6771. name = tagnames.get(code, str(code))
  6772. try:
  6773. dtype = TIFF.DATA_FORMATS[type_]
  6774. except KeyError:
  6775. raise TiffTag.Error("unknown tag data type %i" % type_)
  6776. fmt = "%s%i%s" % (byteorder, count * int(dtype[0]), dtype[1])
  6777. size = struct.calcsize(fmt)
  6778. if size > offsetsize or code in customtags:
  6779. offset = unpack(offsetformat, value)[0]
  6780. if offset < 8 or offset > fh.size - size:
  6781. raise TiffTag.Error("invalid tag value offset %i" % offset)
  6782. fh.seek(offset)
  6783. if code in customtags:
  6784. readfunc = customtags[code][1]
  6785. value = readfunc(fh, byteorder, dtype, count, offsetsize)
  6786. elif type_ == 7 or (count > 1 and dtype[-1] == "B"):
  6787. value = read_bytes(fh, byteorder, dtype, count, offsetsize)
  6788. elif code in tagnames or dtype[-1] == "s":
  6789. value = unpack(fmt, fh.read(size))
  6790. else:
  6791. value = read_numpy(fh, byteorder, dtype, count, offsetsize)
  6792. elif dtype[-1] == "B" or type_ == 7:
  6793. value = value[:size]
  6794. else:
  6795. value = unpack(fmt, value[:size])
  6796. if code not in customtags and code not in TIFF.TAG_TUPLE:
  6797. if len(value) == 1:
  6798. value = value[0]
  6799. if type_ != 7 and dtype[-1] == "s" and isinstance(value, bytes):
  6800. # TIFF ASCII fields can contain multiple strings,
  6801. # each terminated with a NUL
  6802. try:
  6803. value = bytes2str(stripascii(value).strip())
  6804. except UnicodeDecodeError:
  6805. warnings.warn("tag %i: coercing invalid ASCII to bytes" % code)
  6806. tags[name] = value
  6807. result.append(tags)
  6808. # read offset to next page
  6809. fh.seek(pos)
  6810. offset = unpack(offsetformat, fh.read(offsetsize))[0]
  6811. if offset == 0:
  6812. break
  6813. if offset >= fh.size:
  6814. warnings.warn("invalid page offset %i" % offset)
  6815. break
  6816. fh.seek(offset)
  6817. if result and maxifds == 1:
  6818. result = result[0]
  6819. return result
  6820. def read_exif_ifd(fh, byteorder, dtype, count, offsetsize):
  6821. """Read EXIF tags from file and return as dict."""
  6822. exif = read_tags(fh, byteorder, offsetsize, TIFF.EXIF_TAGS, maxifds=1)
  6823. for name in ("ExifVersion", "FlashpixVersion"):
  6824. try:
  6825. exif[name] = bytes2str(exif[name])
  6826. except Exception:
  6827. pass
  6828. if "UserComment" in exif:
  6829. idcode = exif["UserComment"][:8]
  6830. try:
  6831. if idcode == b"ASCII\x00\x00\x00":
  6832. exif["UserComment"] = bytes2str(exif["UserComment"][8:])
  6833. elif idcode == b"UNICODE\x00":
  6834. exif["UserComment"] = exif["UserComment"][8:].decode("utf-16")
  6835. except Exception:
  6836. pass
  6837. return exif
  6838. def read_gps_ifd(fh, byteorder, dtype, count, offsetsize):
  6839. """Read GPS tags from file and return as dict."""
  6840. return read_tags(fh, byteorder, offsetsize, TIFF.GPS_TAGS, maxifds=1)
  6841. def read_interoperability_ifd(fh, byteorder, dtype, count, offsetsize):
  6842. """Read Interoperability tags from file and return as dict."""
  6843. tag_names = {1: "InteroperabilityIndex"}
  6844. return read_tags(fh, byteorder, offsetsize, tag_names, maxifds=1)
  6845. def read_bytes(fh, byteorder, dtype, count, offsetsize):
  6846. """Read tag data from file and return as byte string."""
  6847. dtype = "B" if dtype[-1] == "s" else byteorder + dtype[-1]
  6848. count *= numpy.dtype(dtype).itemsize
  6849. data = fh.read(count)
  6850. if len(data) != count:
  6851. warnings.warn("failed to read all bytes: %i, %i" % (len(data), count))
  6852. return data
  6853. def read_utf8(fh, byteorder, dtype, count, offsetsize):
  6854. """Read tag data from file and return as unicode string."""
  6855. return fh.read(count).decode("utf-8")
  6856. def read_numpy(fh, byteorder, dtype, count, offsetsize):
  6857. """Read tag data from file and return as numpy array."""
  6858. dtype = "b" if dtype[-1] == "s" else byteorder + dtype[-1]
  6859. return fh.read_array(dtype, count)
  6860. def read_colormap(fh, byteorder, dtype, count, offsetsize):
  6861. """Read ColorMap data from file and return as numpy array."""
  6862. cmap = fh.read_array(byteorder + dtype[-1], count)
  6863. cmap.shape = (3, -1)
  6864. return cmap
  6865. def read_json(fh, byteorder, dtype, count, offsetsize):
  6866. """Read JSON tag data from file and return as object."""
  6867. data = fh.read(count)
  6868. try:
  6869. return json.loads(unicode(stripnull(data), "utf-8"))
  6870. except ValueError:
  6871. warnings.warn("invalid JSON '%s'" % data)
  6872. def read_mm_header(fh, byteorder, dtype, count, offsetsize):
  6873. """Read FluoView mm_header tag from file and return as dict."""
  6874. mmh = fh.read_record(TIFF.MM_HEADER, byteorder=byteorder)
  6875. mmh = recarray2dict(mmh)
  6876. mmh["Dimensions"] = [
  6877. (bytes2str(d[0]).strip(), d[1], d[2], d[3], bytes2str(d[4]).strip())
  6878. for d in mmh["Dimensions"]
  6879. ]
  6880. d = mmh["GrayChannel"]
  6881. mmh["GrayChannel"] = (
  6882. bytes2str(d[0]).strip(),
  6883. d[1],
  6884. d[2],
  6885. d[3],
  6886. bytes2str(d[4]).strip(),
  6887. )
  6888. return mmh
  6889. def read_mm_stamp(fh, byteorder, dtype, count, offsetsize):
  6890. """Read FluoView mm_stamp tag from file and return as numpy.ndarray."""
  6891. return fh.read_array(byteorder + "f8", 8)
  6892. def read_uic1tag(fh, byteorder, dtype, count, offsetsize, planecount=None):
  6893. """Read MetaMorph STK UIC1Tag from file and return as dict.
  6894. Return empty dictionary if planecount is unknown.
  6895. """
  6896. assert dtype in ("2I", "1I") and byteorder == "<"
  6897. result = {}
  6898. if dtype == "2I":
  6899. # pre MetaMorph 2.5 (not tested)
  6900. values = fh.read_array("<u4", 2 * count).reshape(count, 2)
  6901. result = {"ZDistance": values[:, 0] / values[:, 1]}
  6902. elif planecount:
  6903. for _ in range(count):
  6904. tagid = struct.unpack("<I", fh.read(4))[0]
  6905. if tagid in (28, 29, 37, 40, 41):
  6906. # silently skip unexpected tags
  6907. fh.read(4)
  6908. continue
  6909. name, value = read_uic_tag(fh, tagid, planecount, offset=True)
  6910. result[name] = value
  6911. return result
  6912. def read_uic2tag(fh, byteorder, dtype, planecount, offsetsize):
  6913. """Read MetaMorph STK UIC2Tag from file and return as dict."""
  6914. assert dtype == "2I" and byteorder == "<"
  6915. values = fh.read_array("<u4", 6 * planecount).reshape(planecount, 6)
  6916. return {
  6917. "ZDistance": values[:, 0] / values[:, 1],
  6918. "DateCreated": values[:, 2], # julian days
  6919. "TimeCreated": values[:, 3], # milliseconds
  6920. "DateModified": values[:, 4], # julian days
  6921. "TimeModified": values[:, 5],
  6922. } # milliseconds
  6923. def read_uic3tag(fh, byteorder, dtype, planecount, offsetsize):
  6924. """Read MetaMorph STK UIC3Tag from file and return as dict."""
  6925. assert dtype == "2I" and byteorder == "<"
  6926. values = fh.read_array("<u4", 2 * planecount).reshape(planecount, 2)
  6927. return {"Wavelengths": values[:, 0] / values[:, 1]}
  6928. def read_uic4tag(fh, byteorder, dtype, planecount, offsetsize):
  6929. """Read MetaMorph STK UIC4Tag from file and return as dict."""
  6930. assert dtype == "1I" and byteorder == "<"
  6931. result = {}
  6932. while True:
  6933. tagid = struct.unpack("<H", fh.read(2))[0]
  6934. if tagid == 0:
  6935. break
  6936. name, value = read_uic_tag(fh, tagid, planecount, offset=False)
  6937. result[name] = value
  6938. return result
  6939. def read_uic_tag(fh, tagid, planecount, offset):
  6940. """Read a single UIC tag value from file and return tag name and value.
  6941. UIC1Tags use an offset.
  6942. """
  6943. def read_int(count=1):
  6944. value = struct.unpack("<%iI" % count, fh.read(4 * count))
  6945. return value[0] if count == 1 else value
  6946. try:
  6947. name, dtype = TIFF.UIC_TAGS[tagid]
  6948. except IndexError:
  6949. # unknown tag
  6950. return "_TagId%i" % tagid, read_int()
  6951. Fraction = TIFF.UIC_TAGS[4][1]
  6952. if offset:
  6953. pos = fh.tell()
  6954. if dtype not in (int, None):
  6955. off = read_int()
  6956. if off < 8:
  6957. if dtype is str:
  6958. return name, ""
  6959. warnings.warn("invalid offset for uic tag '%s': %i" % (name, off))
  6960. return name, off
  6961. fh.seek(off)
  6962. if dtype is None:
  6963. # skip
  6964. name = "_" + name
  6965. value = read_int()
  6966. elif dtype is int:
  6967. # int
  6968. value = read_int()
  6969. elif dtype is Fraction:
  6970. # fraction
  6971. value = read_int(2)
  6972. value = value[0] / value[1]
  6973. elif dtype is julian_datetime:
  6974. # datetime
  6975. value = julian_datetime(*read_int(2))
  6976. elif dtype is read_uic_image_property:
  6977. # ImagePropertyEx
  6978. value = read_uic_image_property(fh)
  6979. elif dtype is str:
  6980. # pascal string
  6981. size = read_int()
  6982. if 0 <= size < 2**10:
  6983. value = struct.unpack("%is" % size, fh.read(size))[0][:-1]
  6984. value = bytes2str(stripnull(value))
  6985. elif offset:
  6986. value = ""
  6987. warnings.warn("corrupt string in uic tag '%s'" % name)
  6988. else:
  6989. raise ValueError("invalid string size: %i" % size)
  6990. elif dtype == "%ip":
  6991. # sequence of pascal strings
  6992. value = []
  6993. for _ in range(planecount):
  6994. size = read_int()
  6995. if 0 <= size < 2**10:
  6996. string = struct.unpack("%is" % size, fh.read(size))[0][:-1]
  6997. string = bytes2str(stripnull(string))
  6998. value.append(string)
  6999. elif offset:
  7000. warnings.warn("corrupt string in uic tag '%s'" % name)
  7001. else:
  7002. raise ValueError("invalid string size: %i" % size)
  7003. else:
  7004. # struct or numpy type
  7005. dtype = "<" + dtype
  7006. if "%i" in dtype:
  7007. dtype = dtype % planecount
  7008. if "(" in dtype:
  7009. # numpy type
  7010. value = fh.read_array(dtype, 1)[0]
  7011. if value.shape[-1] == 2:
  7012. # assume fractions
  7013. value = value[..., 0] / value[..., 1]
  7014. else:
  7015. # struct format
  7016. value = struct.unpack(dtype, fh.read(struct.calcsize(dtype)))
  7017. if len(value) == 1:
  7018. value = value[0]
  7019. if offset:
  7020. fh.seek(pos + 4)
  7021. return name, value
  7022. def read_uic_image_property(fh):
  7023. """Read UIC ImagePropertyEx tag from file and return as dict."""
  7024. # TODO: test this
  7025. size = struct.unpack("B", fh.read(1))[0]
  7026. name = struct.unpack("%is" % size, fh.read(size))[0][:-1]
  7027. flags, prop = struct.unpack("<IB", fh.read(5))
  7028. if prop == 1:
  7029. value = struct.unpack("II", fh.read(8))
  7030. value = value[0] / value[1]
  7031. else:
  7032. size = struct.unpack("B", fh.read(1))[0]
  7033. value = struct.unpack("%is" % size, fh.read(size))[0]
  7034. return dict(name=name, flags=flags, value=value)
  7035. def read_cz_lsminfo(fh, byteorder, dtype, count, offsetsize):
  7036. """Read CZ_LSMINFO tag from file and return as dict."""
  7037. assert byteorder == "<"
  7038. magic_number, structure_size = struct.unpack("<II", fh.read(8))
  7039. if magic_number not in (50350412, 67127628):
  7040. raise ValueError("invalid CZ_LSMINFO structure")
  7041. fh.seek(-8, 1)
  7042. if structure_size < numpy.dtype(TIFF.CZ_LSMINFO).itemsize:
  7043. # adjust structure according to structure_size
  7044. lsminfo = []
  7045. size = 0
  7046. for name, dtype in TIFF.CZ_LSMINFO:
  7047. size += numpy.dtype(dtype).itemsize
  7048. if size > structure_size:
  7049. break
  7050. lsminfo.append((name, dtype))
  7051. else:
  7052. lsminfo = TIFF.CZ_LSMINFO
  7053. lsminfo = fh.read_record(lsminfo, byteorder=byteorder)
  7054. lsminfo = recarray2dict(lsminfo)
  7055. # read LSM info subrecords at offsets
  7056. for name, reader in TIFF.CZ_LSMINFO_READERS.items():
  7057. if reader is None:
  7058. continue
  7059. offset = lsminfo.get("Offset" + name, 0)
  7060. if offset < 8:
  7061. continue
  7062. fh.seek(offset)
  7063. try:
  7064. lsminfo[name] = reader(fh)
  7065. except ValueError:
  7066. pass
  7067. return lsminfo
  7068. def read_lsm_floatpairs(fh):
  7069. """Read LSM sequence of float pairs from file and return as list."""
  7070. size = struct.unpack("<i", fh.read(4))[0]
  7071. return fh.read_array("<2f8", count=size)
  7072. def read_lsm_positions(fh):
  7073. """Read LSM positions from file and return as list."""
  7074. size = struct.unpack("<I", fh.read(4))[0]
  7075. return fh.read_array("<2f8", count=size)
  7076. def read_lsm_timestamps(fh):
  7077. """Read LSM time stamps from file and return as list."""
  7078. size, count = struct.unpack("<ii", fh.read(8))
  7079. if size != (8 + 8 * count):
  7080. warnings.warn("invalid LSM TimeStamps block")
  7081. return []
  7082. # return struct.unpack('<%dd' % count, fh.read(8*count))
  7083. return fh.read_array("<f8", count=count)
  7084. def read_lsm_eventlist(fh):
  7085. """Read LSM events from file and return as list of (time, type, text)."""
  7086. count = struct.unpack("<II", fh.read(8))[1]
  7087. events = []
  7088. while count > 0:
  7089. esize, etime, etype = struct.unpack("<IdI", fh.read(16))
  7090. etext = bytes2str(stripnull(fh.read(esize - 16)))
  7091. events.append((etime, etype, etext))
  7092. count -= 1
  7093. return events
  7094. def read_lsm_channelcolors(fh):
  7095. """Read LSM ChannelColors structure from file and return as dict."""
  7096. result = {"Mono": False, "Colors": [], "ColorNames": []}
  7097. pos = fh.tell()
  7098. (size, ncolors, nnames, coffset, noffset, mono) = struct.unpack(
  7099. "<IIIIII", fh.read(24)
  7100. )
  7101. if ncolors != nnames:
  7102. warnings.warn("invalid LSM ChannelColors structure")
  7103. return result
  7104. result["Mono"] = bool(mono)
  7105. # Colors
  7106. fh.seek(pos + coffset)
  7107. colors = fh.read_array("uint8", count=ncolors * 4).reshape((ncolors, 4))
  7108. result["Colors"] = colors.tolist()
  7109. # ColorNames
  7110. fh.seek(pos + noffset)
  7111. buffer = fh.read(size - noffset)
  7112. names = []
  7113. while len(buffer) > 4:
  7114. size = struct.unpack("<I", buffer[:4])[0]
  7115. names.append(bytes2str(buffer[4 : 3 + size]))
  7116. buffer = buffer[4 + size :]
  7117. result["ColorNames"] = names
  7118. return result
  7119. def read_lsm_scaninfo(fh):
  7120. """Read LSM ScanInfo structure from file and return as dict."""
  7121. block = {}
  7122. blocks = [block]
  7123. unpack = struct.unpack
  7124. if struct.unpack("<I", fh.read(4))[0] != 0x10000000:
  7125. # not a Recording sub block
  7126. warnings.warn("invalid LSM ScanInfo structure")
  7127. return block
  7128. fh.read(8)
  7129. while True:
  7130. entry, dtype, size = unpack("<III", fh.read(12))
  7131. if dtype == 2:
  7132. # ascii
  7133. value = bytes2str(stripnull(fh.read(size)))
  7134. elif dtype == 4:
  7135. # long
  7136. value = unpack("<i", fh.read(4))[0]
  7137. elif dtype == 5:
  7138. # rational
  7139. value = unpack("<d", fh.read(8))[0]
  7140. else:
  7141. value = 0
  7142. if entry in TIFF.CZ_LSMINFO_SCANINFO_ARRAYS:
  7143. blocks.append(block)
  7144. name = TIFF.CZ_LSMINFO_SCANINFO_ARRAYS[entry]
  7145. newobj = []
  7146. block[name] = newobj
  7147. block = newobj
  7148. elif entry in TIFF.CZ_LSMINFO_SCANINFO_STRUCTS:
  7149. blocks.append(block)
  7150. newobj = {}
  7151. block.append(newobj)
  7152. block = newobj
  7153. elif entry in TIFF.CZ_LSMINFO_SCANINFO_ATTRIBUTES:
  7154. name = TIFF.CZ_LSMINFO_SCANINFO_ATTRIBUTES[entry]
  7155. block[name] = value
  7156. elif entry == 0xFFFFFFFF:
  7157. # end sub block
  7158. block = blocks.pop()
  7159. else:
  7160. # unknown entry
  7161. block["Entry0x%x" % entry] = value
  7162. if not blocks:
  7163. break
  7164. return block
  7165. def read_tvips_header(fh, byteorder, dtype, count, offsetsize):
  7166. """Read TVIPS EM-MENU headers and return as dict."""
  7167. result = {}
  7168. header = fh.read_record(TIFF.TVIPS_HEADER_V1, byteorder=byteorder)
  7169. for name, typestr in TIFF.TVIPS_HEADER_V1:
  7170. result[name] = header[name].tolist()
  7171. if header["Version"] == 2:
  7172. header = fh.read_record(TIFF.TVIPS_HEADER_V2, byteorder=byteorder)
  7173. if header["Magic"] != int(0xAAAAAAAA):
  7174. warnings.warn("invalid TVIPS v2 magic number")
  7175. return {}
  7176. # decode utf16 strings
  7177. for name, typestr in TIFF.TVIPS_HEADER_V2:
  7178. if typestr.startswith("V"):
  7179. s = header[name].tostring().decode("utf16", errors="ignore")
  7180. result[name] = stripnull(s, null="\0")
  7181. else:
  7182. result[name] = header[name].tolist()
  7183. # convert nm to m
  7184. for axis in "XY":
  7185. header["PhysicalPixelSize" + axis] /= 1e9
  7186. header["PixelSize" + axis] /= 1e9
  7187. elif header.version != 1:
  7188. warnings.warn("unknown TVIPS header version")
  7189. return {}
  7190. return result
  7191. def read_fei_metadata(fh, byteorder, dtype, count, offsetsize):
  7192. """Read FEI SFEG/HELIOS headers and return as dict."""
  7193. result = {}
  7194. section = {}
  7195. data = bytes2str(fh.read(count))
  7196. for line in data.splitlines():
  7197. line = line.strip()
  7198. if line.startswith("["):
  7199. section = {}
  7200. result[line[1:-1]] = section
  7201. continue
  7202. try:
  7203. key, value = line.split("=")
  7204. except ValueError:
  7205. continue
  7206. section[key] = astype(value)
  7207. return result
  7208. def read_cz_sem(fh, byteorder, dtype, count, offsetsize):
  7209. """Read Zeiss SEM tag and return as dict."""
  7210. result = {"": ()}
  7211. key = None
  7212. data = bytes2str(fh.read(count))
  7213. for line in data.splitlines():
  7214. if line.isupper():
  7215. key = line.lower()
  7216. elif key:
  7217. try:
  7218. name, value = line.split("=")
  7219. except ValueError:
  7220. continue
  7221. value = value.strip()
  7222. unit = ""
  7223. try:
  7224. v, u = value.split()
  7225. number = astype(v, (int, float))
  7226. if number != v:
  7227. value = number
  7228. unit = u
  7229. except Exception:
  7230. number = astype(value, (int, float))
  7231. if number != value:
  7232. value = number
  7233. if value in ("No", "Off"):
  7234. value = False
  7235. elif value in ("Yes", "On"):
  7236. value = True
  7237. result[key] = (name.strip(), value)
  7238. if unit:
  7239. result[key] += (unit,)
  7240. key = None
  7241. else:
  7242. result[""] += (astype(line, (int, float)),)
  7243. return result
  7244. def read_nih_image_header(fh, byteorder, dtype, count, offsetsize):
  7245. """Read NIH_IMAGE_HEADER tag from file and return as dict."""
  7246. a = fh.read_record(TIFF.NIH_IMAGE_HEADER, byteorder=byteorder)
  7247. a = a.view(a.dtype.newbyteorder(byteorder))
  7248. a = recarray2dict(a)
  7249. a["XUnit"] = a["XUnit"][: a["XUnitSize"]]
  7250. a["UM"] = a["UM"][: a["UMsize"]]
  7251. return a
  7252. def read_scanimage_metadata(fh):
  7253. """Read ScanImage BigTIFF v3 static and ROI metadata from open file.
  7254. Return non-varying frame data as dict and ROI group data as JSON.
  7255. The settings can be used to read image data and metadata without parsing
  7256. the TIFF file.
  7257. Raise ValueError if file does not contain valid ScanImage v3 metadata.
  7258. """
  7259. fh.seek(0)
  7260. try:
  7261. byteorder, version = struct.unpack("<2sH", fh.read(4))
  7262. if byteorder != b"II" or version != 43:
  7263. raise Exception
  7264. fh.seek(16)
  7265. magic, version, size0, size1 = struct.unpack("<IIII", fh.read(16))
  7266. if magic != 117637889 or version != 3:
  7267. raise Exception
  7268. except Exception:
  7269. raise ValueError("not a ScanImage BigTIFF v3 file")
  7270. frame_data = matlabstr2py(bytes2str(fh.read(size0)[:-1]))
  7271. roi_data = read_json(fh, "<", None, size1, None) if size1 > 1 else {}
  7272. return frame_data, roi_data
  7273. def read_micromanager_metadata(fh):
  7274. """Read MicroManager non-TIFF settings from open file and return as dict.
  7275. The settings can be used to read image data without parsing the TIFF file.
  7276. Raise ValueError if the file does not contain valid MicroManager metadata.
  7277. """
  7278. fh.seek(0)
  7279. try:
  7280. byteorder = {b"II": "<", b"MM": ">"}[fh.read(2)]
  7281. except IndexError:
  7282. raise ValueError("not a MicroManager TIFF file")
  7283. result = {}
  7284. fh.seek(8)
  7285. (
  7286. index_header,
  7287. index_offset,
  7288. display_header,
  7289. display_offset,
  7290. comments_header,
  7291. comments_offset,
  7292. summary_header,
  7293. summary_length,
  7294. ) = struct.unpack(byteorder + "IIIIIIII", fh.read(32))
  7295. if summary_header != 2355492:
  7296. raise ValueError("invalid MicroManager summary header")
  7297. result["Summary"] = read_json(fh, byteorder, None, summary_length, None)
  7298. if index_header != 54773648:
  7299. raise ValueError("invalid MicroManager index header")
  7300. fh.seek(index_offset)
  7301. header, count = struct.unpack(byteorder + "II", fh.read(8))
  7302. if header != 3453623:
  7303. raise ValueError("invalid MicroManager index header")
  7304. data = struct.unpack(byteorder + "IIIII" * count, fh.read(20 * count))
  7305. result["IndexMap"] = {
  7306. "Channel": data[::5],
  7307. "Slice": data[1::5],
  7308. "Frame": data[2::5],
  7309. "Position": data[3::5],
  7310. "Offset": data[4::5],
  7311. }
  7312. if display_header != 483765892:
  7313. raise ValueError("invalid MicroManager display header")
  7314. fh.seek(display_offset)
  7315. header, count = struct.unpack(byteorder + "II", fh.read(8))
  7316. if header != 347834724:
  7317. raise ValueError("invalid MicroManager display header")
  7318. result["DisplaySettings"] = read_json(fh, byteorder, None, count, None)
  7319. if comments_header != 99384722:
  7320. raise ValueError("invalid MicroManager comments header")
  7321. fh.seek(comments_offset)
  7322. header, count = struct.unpack(byteorder + "II", fh.read(8))
  7323. if header != 84720485:
  7324. raise ValueError("invalid MicroManager comments header")
  7325. result["Comments"] = read_json(fh, byteorder, None, count, None)
  7326. return result
  7327. def read_metaseries_catalog(fh):
  7328. """Read MetaSeries non-TIFF hint catalog from file.
  7329. Raise ValueError if the file does not contain a valid hint catalog.
  7330. """
  7331. # TODO: implement read_metaseries_catalog
  7332. raise NotImplementedError()
  7333. def imagej_metadata_tags(metadata, byteorder):
  7334. """Return IJMetadata and IJMetadataByteCounts tags from metadata dict.
  7335. The tags can be passed to the TiffWriter.save function as extratags.
  7336. The metadata dict may contain the following keys and values:
  7337. Info : str
  7338. Human-readable information as string.
  7339. Labels : sequence of str
  7340. Human-readable labels for each channel.
  7341. Ranges : sequence of doubles
  7342. Lower and upper values for each channel.
  7343. LUTs : sequence of (3, 256) uint8 ndarrays
  7344. Color palettes for each channel.
  7345. Plot : bytes
  7346. Undocumented ImageJ internal format.
  7347. ROI: bytes
  7348. Undocumented ImageJ internal region of interest format.
  7349. Overlays : bytes
  7350. Undocumented ImageJ internal format.
  7351. """
  7352. header = [{">": b"IJIJ", "<": b"JIJI"}[byteorder]]
  7353. bytecounts = [0]
  7354. body = []
  7355. def _string(data, byteorder):
  7356. return data.encode("utf-16" + {">": "be", "<": "le"}[byteorder])
  7357. def _doubles(data, byteorder):
  7358. return struct.pack(byteorder + ("d" * len(data)), *data)
  7359. def _ndarray(data, byteorder):
  7360. return data.tobytes()
  7361. def _bytes(data, byteorder):
  7362. return data
  7363. metadata_types = (
  7364. ("Info", b"info", 1, _string),
  7365. ("Labels", b"labl", None, _string),
  7366. ("Ranges", b"rang", 1, _doubles),
  7367. ("LUTs", b"luts", None, _ndarray),
  7368. ("Plot", b"plot", 1, _bytes),
  7369. ("ROI", b"roi ", 1, _bytes),
  7370. ("Overlays", b"over", None, _bytes),
  7371. )
  7372. for key, mtype, count, func in metadata_types:
  7373. if key.lower() in metadata:
  7374. key = key.lower()
  7375. elif key not in metadata:
  7376. continue
  7377. if byteorder == "<":
  7378. mtype = mtype[::-1]
  7379. values = metadata[key]
  7380. if count is None:
  7381. count = len(values)
  7382. else:
  7383. values = [values]
  7384. header.append(mtype + struct.pack(byteorder + "I", count))
  7385. for value in values:
  7386. data = func(value, byteorder)
  7387. body.append(data)
  7388. bytecounts.append(len(data))
  7389. if not body:
  7390. return ()
  7391. body = b"".join(body)
  7392. header = b"".join(header)
  7393. data = header + body
  7394. bytecounts[0] = len(header)
  7395. bytecounts = struct.pack(byteorder + ("I" * len(bytecounts)), *bytecounts)
  7396. return (
  7397. (50839, "B", len(data), data, True),
  7398. (50838, "I", len(bytecounts) // 4, bytecounts, True),
  7399. )
  7400. def imagej_metadata(data, bytecounts, byteorder):
  7401. """Return IJMetadata tag value as dict.
  7402. The 'Info' string can have multiple formats, e.g. OIF or ScanImage,
  7403. that might be parsed into dicts using the matlabstr2py or
  7404. oiffile.SettingsFile functions.
  7405. """
  7406. def _string(data, byteorder):
  7407. return data.decode("utf-16" + {">": "be", "<": "le"}[byteorder])
  7408. def _doubles(data, byteorder):
  7409. return struct.unpack(byteorder + ("d" * (len(data) // 8)), data)
  7410. def _lut(data, byteorder):
  7411. return numpy.frombuffer(data, "uint8").reshape(-1, 256)
  7412. def _bytes(data, byteorder):
  7413. return data
  7414. metadata_types = { # big-endian
  7415. b"info": ("Info", _string),
  7416. b"labl": ("Labels", _string),
  7417. b"rang": ("Ranges", _doubles),
  7418. b"luts": ("LUTs", _lut),
  7419. b"plot": ("Plots", _bytes),
  7420. b"roi ": ("ROI", _bytes),
  7421. b"over": ("Overlays", _bytes),
  7422. }
  7423. metadata_types.update( # little-endian
  7424. dict((k[::-1], v) for k, v in metadata_types.items())
  7425. )
  7426. if not bytecounts:
  7427. raise ValueError("no ImageJ metadata")
  7428. if data[:4] not in (b"IJIJ", b"JIJI"):
  7429. raise ValueError("invalid ImageJ metadata")
  7430. header_size = bytecounts[0]
  7431. if header_size < 12 or header_size > 804:
  7432. raise ValueError("invalid ImageJ metadata header size")
  7433. ntypes = (header_size - 4) // 8
  7434. header = struct.unpack(byteorder + "4sI" * ntypes, data[4 : 4 + ntypes * 8])
  7435. pos = 4 + ntypes * 8
  7436. counter = 0
  7437. result = {}
  7438. for mtype, count in zip(header[::2], header[1::2]):
  7439. values = []
  7440. name, func = metadata_types.get(mtype, (bytes2str(mtype), read_bytes))
  7441. for _ in range(count):
  7442. counter += 1
  7443. pos1 = pos + bytecounts[counter]
  7444. values.append(func(data[pos:pos1], byteorder))
  7445. pos = pos1
  7446. result[name.strip()] = values[0] if count == 1 else values
  7447. return result
  7448. def imagej_description_metadata(description):
  7449. """Return metatata from ImageJ image description as dict.
  7450. Raise ValueError if not a valid ImageJ description.
  7451. >>> description = 'ImageJ=1.11a\\nimages=510\\nhyperstack=true\\n'
  7452. >>> imagej_description_metadata(description) # doctest: +SKIP
  7453. {'ImageJ': '1.11a', 'images': 510, 'hyperstack': True}
  7454. """
  7455. def _bool(val):
  7456. return {"true": True, "false": False}[val.lower()]
  7457. result = {}
  7458. for line in description.splitlines():
  7459. try:
  7460. key, val = line.split("=")
  7461. except Exception:
  7462. continue
  7463. key = key.strip()
  7464. val = val.strip()
  7465. for dtype in (int, float, _bool):
  7466. try:
  7467. val = dtype(val)
  7468. break
  7469. except Exception:
  7470. pass
  7471. result[key] = val
  7472. if "ImageJ" not in result:
  7473. raise ValueError("not a ImageJ image description")
  7474. return result
  7475. def imagej_description(
  7476. shape,
  7477. rgb=None,
  7478. colormaped=False,
  7479. version="1.11a",
  7480. hyperstack=None,
  7481. mode=None,
  7482. loop=None,
  7483. **kwargs,
  7484. ):
  7485. """Return ImageJ image description from data shape.
  7486. ImageJ can handle up to 6 dimensions in order TZCYXS.
  7487. >>> imagej_description((51, 5, 2, 196, 171)) # doctest: +SKIP
  7488. ImageJ=1.11a
  7489. images=510
  7490. channels=2
  7491. slices=5
  7492. frames=51
  7493. hyperstack=true
  7494. mode=grayscale
  7495. loop=false
  7496. """
  7497. if colormaped:
  7498. raise NotImplementedError("ImageJ colormapping not supported")
  7499. shape = imagej_shape(shape, rgb=rgb)
  7500. rgb = shape[-1] in (3, 4)
  7501. result = ["ImageJ=%s" % version]
  7502. append = []
  7503. result.append("images=%i" % product(shape[:-3]))
  7504. if hyperstack is None:
  7505. hyperstack = True
  7506. append.append("hyperstack=true")
  7507. else:
  7508. append.append("hyperstack=%s" % bool(hyperstack))
  7509. if shape[2] > 1:
  7510. result.append("channels=%i" % shape[2])
  7511. if mode is None and not rgb:
  7512. mode = "grayscale"
  7513. if hyperstack and mode:
  7514. append.append("mode=%s" % mode)
  7515. if shape[1] > 1:
  7516. result.append("slices=%i" % shape[1])
  7517. if shape[0] > 1:
  7518. result.append("frames=%i" % shape[0])
  7519. if loop is None:
  7520. append.append("loop=false")
  7521. if loop is not None:
  7522. append.append("loop=%s" % bool(loop))
  7523. for key, value in kwargs.items():
  7524. append.append("%s=%s" % (key.lower(), value))
  7525. return "\n".join(result + append + [""])
  7526. def imagej_shape(shape, rgb=None):
  7527. """Return shape normalized to 6D ImageJ hyperstack TZCYXS.
  7528. Raise ValueError if not a valid ImageJ hyperstack shape.
  7529. >>> imagej_shape((2, 3, 4, 5, 3), False)
  7530. (2, 3, 4, 5, 3, 1)
  7531. """
  7532. shape = tuple(int(i) for i in shape)
  7533. ndim = len(shape)
  7534. if 1 > ndim > 6:
  7535. raise ValueError("invalid ImageJ hyperstack: not 2 to 6 dimensional")
  7536. if rgb is None:
  7537. rgb = shape[-1] in (3, 4) and ndim > 2
  7538. if rgb and shape[-1] not in (3, 4):
  7539. raise ValueError("invalid ImageJ hyperstack: not a RGB image")
  7540. if not rgb and ndim == 6 and shape[-1] != 1:
  7541. raise ValueError("invalid ImageJ hyperstack: not a non-RGB image")
  7542. if rgb or shape[-1] == 1:
  7543. return (1,) * (6 - ndim) + shape
  7544. return (1,) * (5 - ndim) + shape + (1,)
  7545. def json_description(shape, **metadata):
  7546. """Return JSON image description from data shape and other meta data.
  7547. Return UTF-8 encoded JSON.
  7548. >>> json_description((256, 256, 3), axes='YXS') # doctest: +SKIP
  7549. b'{"shape": [256, 256, 3], "axes": "YXS"}'
  7550. """
  7551. metadata.update(shape=shape)
  7552. return json.dumps(metadata) # .encode('utf-8')
  7553. def json_description_metadata(description):
  7554. """Return metatata from JSON formatted image description as dict.
  7555. Raise ValuError if description is of unknown format.
  7556. >>> description = '{"shape": [256, 256, 3], "axes": "YXS"}'
  7557. >>> json_description_metadata(description) # doctest: +SKIP
  7558. {'shape': [256, 256, 3], 'axes': 'YXS'}
  7559. >>> json_description_metadata('shape=(256, 256, 3)')
  7560. {'shape': (256, 256, 3)}
  7561. """
  7562. if description[:6] == "shape=":
  7563. # old style 'shaped' description; not JSON
  7564. shape = tuple(int(i) for i in description[7:-1].split(","))
  7565. return dict(shape=shape)
  7566. if description[:1] == "{" and description[-1:] == "}":
  7567. # JSON description
  7568. return json.loads(description)
  7569. raise ValueError("invalid JSON image description", description)
  7570. def fluoview_description_metadata(description, ignoresections=None):
  7571. """Return metatata from FluoView image description as dict.
  7572. The FluoView image description format is unspecified. Expect failures.
  7573. >>> descr = ('[Intensity Mapping]\\nMap Ch0: Range=00000 to 02047\\n'
  7574. ... '[Intensity Mapping End]')
  7575. >>> fluoview_description_metadata(descr)
  7576. {'Intensity Mapping': {'Map Ch0: Range': '00000 to 02047'}}
  7577. """
  7578. if not description.startswith("["):
  7579. raise ValueError("invalid FluoView image description")
  7580. if ignoresections is None:
  7581. ignoresections = {"Region Info (Fields)", "Protocol Description"}
  7582. result = {}
  7583. sections = [result]
  7584. comment = False
  7585. for line in description.splitlines():
  7586. if not comment:
  7587. line = line.strip()
  7588. if not line:
  7589. continue
  7590. if line[0] == "[":
  7591. if line[-5:] == " End]":
  7592. # close section
  7593. del sections[-1]
  7594. section = sections[-1]
  7595. name = line[1:-5]
  7596. if comment:
  7597. section[name] = "\n".join(section[name])
  7598. if name[:4] == "LUT ":
  7599. a = numpy.array(section[name], dtype="uint8")
  7600. a.shape = -1, 3
  7601. section[name] = a
  7602. continue
  7603. # new section
  7604. comment = False
  7605. name = line[1:-1]
  7606. if name[:4] == "LUT ":
  7607. section = []
  7608. elif name in ignoresections:
  7609. section = []
  7610. comment = True
  7611. else:
  7612. section = {}
  7613. sections.append(section)
  7614. result[name] = section
  7615. continue
  7616. # add entry
  7617. if comment:
  7618. section.append(line)
  7619. continue
  7620. line = line.split("=", 1)
  7621. if len(line) == 1:
  7622. section[line[0].strip()] = None
  7623. continue
  7624. key, value = line
  7625. if key[:4] == "RGB ":
  7626. section.extend(int(rgb) for rgb in value.split())
  7627. else:
  7628. section[key.strip()] = astype(value.strip())
  7629. return result
  7630. def pilatus_description_metadata(description):
  7631. """Return metatata from Pilatus image description as dict.
  7632. Return metadata from Pilatus pixel array detectors by Dectris, created
  7633. by camserver or TVX software.
  7634. >>> pilatus_description_metadata('# Pixel_size 172e-6 m x 172e-6 m')
  7635. {'Pixel_size': (0.000172, 0.000172)}
  7636. """
  7637. result = {}
  7638. if not description.startswith("# "):
  7639. return result
  7640. for c in "#:=,()":
  7641. description = description.replace(c, " ")
  7642. for line in description.split("\n"):
  7643. if line[:2] != " ":
  7644. continue
  7645. line = line.split()
  7646. name = line[0]
  7647. if line[0] not in TIFF.PILATUS_HEADER:
  7648. try:
  7649. result["DateTime"] = datetime.datetime.strptime(
  7650. " ".join(line), "%Y-%m-%dT%H %M %S.%f"
  7651. )
  7652. except Exception:
  7653. result[name] = " ".join(line[1:])
  7654. continue
  7655. indices, dtype = TIFF.PILATUS_HEADER[line[0]]
  7656. if isinstance(indices[0], slice):
  7657. # assumes one slice
  7658. values = line[indices[0]]
  7659. else:
  7660. values = [line[i] for i in indices]
  7661. if dtype is float and values[0] == "not":
  7662. values = ["NaN"]
  7663. values = tuple(dtype(v) for v in values)
  7664. if dtype == str:
  7665. values = " ".join(values)
  7666. elif len(values) == 1:
  7667. values = values[0]
  7668. result[name] = values
  7669. return result
  7670. def svs_description_metadata(description):
  7671. """Return metatata from Aperio image description as dict.
  7672. The Aperio image description format is unspecified. Expect failures.
  7673. >>> svs_description_metadata('Aperio Image Library v1.0')
  7674. {'Aperio Image Library': 'v1.0'}
  7675. """
  7676. if not description.startswith("Aperio Image Library "):
  7677. raise ValueError("invalid Aperio image description")
  7678. result = {}
  7679. lines = description.split("\n")
  7680. key, value = lines[0].strip().rsplit(None, 1) # 'Aperio Image Library'
  7681. result[key.strip()] = value.strip()
  7682. if len(lines) == 1:
  7683. return result
  7684. items = lines[1].split("|")
  7685. result[""] = items[0].strip() # TODO: parse this?
  7686. for item in items[1:]:
  7687. key, value = item.split(" = ")
  7688. result[key.strip()] = astype(value.strip())
  7689. return result
  7690. def stk_description_metadata(description):
  7691. """Return metadata from MetaMorph image description as list of dict.
  7692. The MetaMorph image description format is unspecified. Expect failures.
  7693. """
  7694. description = description.strip()
  7695. if not description:
  7696. return []
  7697. try:
  7698. description = bytes2str(description)
  7699. except UnicodeDecodeError:
  7700. warnings.warn("failed to parse MetaMorph image description")
  7701. return []
  7702. result = []
  7703. for plane in description.split("\x00"):
  7704. d = {}
  7705. for line in plane.split("\r\n"):
  7706. line = line.split(":", 1)
  7707. if len(line) > 1:
  7708. name, value = line
  7709. d[name.strip()] = astype(value.strip())
  7710. else:
  7711. value = line[0].strip()
  7712. if value:
  7713. if "" in d:
  7714. d[""].append(value)
  7715. else:
  7716. d[""] = [value]
  7717. result.append(d)
  7718. return result
  7719. def metaseries_description_metadata(description):
  7720. """Return metatata from MetaSeries image description as dict."""
  7721. if not description.startswith("<MetaData>"):
  7722. raise ValueError("invalid MetaSeries image description")
  7723. from xml.etree import cElementTree as etree # delayed import
  7724. root = etree.fromstring(description)
  7725. types = {"float": float, "int": int, "bool": lambda x: asbool(x, "on", "off")}
  7726. def parse(root, result):
  7727. # recursive
  7728. for child in root:
  7729. attrib = child.attrib
  7730. if not attrib:
  7731. result[child.tag] = parse(child, {})
  7732. continue
  7733. if "id" in attrib:
  7734. i = attrib["id"]
  7735. t = attrib["type"]
  7736. v = attrib["value"]
  7737. if t in types:
  7738. result[i] = types[t](v)
  7739. else:
  7740. result[i] = v
  7741. return result
  7742. adict = parse(root, {})
  7743. if "Description" in adict:
  7744. adict["Description"] = adict["Description"].replace("&#13;&#10;", "\n")
  7745. return adict
  7746. def scanimage_description_metadata(description):
  7747. """Return metatata from ScanImage image description as dict."""
  7748. return matlabstr2py(description)
  7749. def scanimage_artist_metadata(artist):
  7750. """Return metatata from ScanImage artist tag as dict."""
  7751. try:
  7752. return json.loads(artist)
  7753. except ValueError:
  7754. warnings.warn("invalid JSON '%s'" % artist)
  7755. def _replace_by(module_function, package=__package__, warn=None, prefix="_"):
  7756. """Try replace decorated function by module.function."""
  7757. return lambda f: f # imageio: just use what's in here
  7758. def _warn(e, warn):
  7759. if warn is None:
  7760. warn = "\n Functionality might be degraded or be slow.\n"
  7761. elif warn is True:
  7762. warn = ""
  7763. elif not warn:
  7764. return
  7765. warnings.warn("%s%s" % (e, warn))
  7766. try:
  7767. from importlib import import_module
  7768. except ImportError as e:
  7769. _warn(e, warn)
  7770. return identityfunc
  7771. def decorate(func, module_function=module_function, warn=warn):
  7772. module, function = module_function.split(".")
  7773. try:
  7774. if package:
  7775. module = import_module("." + module, package=package)
  7776. else:
  7777. module = import_module(module)
  7778. except Exception as e:
  7779. _warn(e, warn)
  7780. return func
  7781. try:
  7782. func, oldfunc = getattr(module, function), func
  7783. except Exception as e:
  7784. _warn(e, warn)
  7785. return func
  7786. globals()[prefix + func.__name__] = oldfunc
  7787. return func
  7788. return decorate
  7789. def decode_floats(data):
  7790. """Decode floating point horizontal differencing.
  7791. The TIFF predictor type 3 reorders the bytes of the image values and
  7792. applies horizontal byte differencing to improve compression of floating
  7793. point images. The ordering of interleaved color channels is preserved.
  7794. Parameters
  7795. ----------
  7796. data : numpy.ndarray
  7797. The image to be decoded. The dtype must be a floating point.
  7798. The shape must include the number of contiguous samples per pixel
  7799. even if 1.
  7800. """
  7801. shape = data.shape
  7802. dtype = data.dtype
  7803. if len(shape) < 3:
  7804. raise ValueError("invalid data shape")
  7805. if dtype.char not in "dfe":
  7806. raise ValueError("not a floating point image")
  7807. littleendian = data.dtype.byteorder == "<" or (
  7808. sys.byteorder == "little" and data.dtype.byteorder == "="
  7809. )
  7810. # undo horizontal byte differencing
  7811. data = data.view("uint8")
  7812. data.shape = shape[:-2] + (-1,) + shape[-1:]
  7813. numpy.cumsum(data, axis=-2, dtype="uint8", out=data)
  7814. # reorder bytes
  7815. if littleendian:
  7816. data.shape = shape[:-2] + (-1,) + shape[-2:]
  7817. data = numpy.swapaxes(data, -3, -2)
  7818. data = numpy.swapaxes(data, -2, -1)
  7819. data = data[..., ::-1]
  7820. # back to float
  7821. data = numpy.ascontiguousarray(data)
  7822. data = data.view(dtype)
  7823. data.shape = shape
  7824. return data
  7825. @_replace_by("_tifffile.decode_packbits")
  7826. def decode_packbits(encoded):
  7827. """Decompress PackBits encoded byte string.
  7828. PackBits is a simple byte-oriented run-length compression scheme.
  7829. """
  7830. func = ord if sys.version[0] == "2" else identityfunc
  7831. result = []
  7832. result_extend = result.extend
  7833. i = 0
  7834. try:
  7835. while True:
  7836. n = func(encoded[i]) + 1
  7837. i += 1
  7838. if n < 129:
  7839. result_extend(encoded[i : i + n])
  7840. i += n
  7841. elif n > 129:
  7842. result_extend(encoded[i : i + 1] * (258 - n))
  7843. i += 1
  7844. except IndexError:
  7845. pass
  7846. return b"".join(result) if sys.version[0] == "2" else bytes(result)
  7847. @_replace_by("_tifffile.decode_lzw")
  7848. def decode_lzw(encoded):
  7849. """Decompress LZW (Lempel-Ziv-Welch) encoded TIFF strip (byte string).
  7850. The strip must begin with a CLEAR code and end with an EOI code.
  7851. This implementation of the LZW decoding algorithm is described in (1) and
  7852. is not compatible with old style LZW compressed files like quad-lzw.tif.
  7853. """
  7854. len_encoded = len(encoded)
  7855. bitcount_max = len_encoded * 8
  7856. unpack = struct.unpack
  7857. if sys.version[0] == "2":
  7858. newtable = [chr(i) for i in range(256)]
  7859. else:
  7860. newtable = [bytes([i]) for i in range(256)]
  7861. newtable.extend((0, 0))
  7862. def next_code():
  7863. """Return integer of 'bitw' bits at 'bitcount' position in encoded."""
  7864. start = bitcount // 8
  7865. s = encoded[start : start + 4]
  7866. try:
  7867. code = unpack(">I", s)[0]
  7868. except Exception:
  7869. code = unpack(">I", s + b"\x00" * (4 - len(s)))[0]
  7870. code <<= bitcount % 8
  7871. code &= mask
  7872. return code >> shr
  7873. switchbitch = { # code: bit-width, shr-bits, bit-mask
  7874. 255: (9, 23, int(9 * "1" + "0" * 23, 2)),
  7875. 511: (10, 22, int(10 * "1" + "0" * 22, 2)),
  7876. 1023: (11, 21, int(11 * "1" + "0" * 21, 2)),
  7877. 2047: (12, 20, int(12 * "1" + "0" * 20, 2)),
  7878. }
  7879. bitw, shr, mask = switchbitch[255]
  7880. bitcount = 0
  7881. if len_encoded < 4:
  7882. raise ValueError("strip must be at least 4 characters long")
  7883. if next_code() != 256:
  7884. raise ValueError("strip must begin with CLEAR code")
  7885. code = 0
  7886. oldcode = 0
  7887. result = []
  7888. result_append = result.append
  7889. while True:
  7890. code = next_code() # ~5% faster when inlining this function
  7891. bitcount += bitw
  7892. if code == 257 or bitcount >= bitcount_max: # EOI
  7893. break
  7894. if code == 256: # CLEAR
  7895. table = newtable[:]
  7896. table_append = table.append
  7897. lentable = 258
  7898. bitw, shr, mask = switchbitch[255]
  7899. code = next_code()
  7900. bitcount += bitw
  7901. if code == 257: # EOI
  7902. break
  7903. result_append(table[code])
  7904. else:
  7905. if code < lentable:
  7906. decoded = table[code]
  7907. newcode = table[oldcode] + decoded[:1]
  7908. else:
  7909. newcode = table[oldcode]
  7910. newcode += newcode[:1]
  7911. decoded = newcode
  7912. result_append(decoded)
  7913. table_append(newcode)
  7914. lentable += 1
  7915. oldcode = code
  7916. if lentable in switchbitch:
  7917. bitw, shr, mask = switchbitch[lentable]
  7918. if code != 257:
  7919. warnings.warn("unexpected end of LZW stream (code %i)" % code)
  7920. return b"".join(result)
  7921. @_replace_by("_tifffile.unpack_ints")
  7922. def unpack_ints(data, dtype, itemsize, runlen=0):
  7923. """Decompress byte string to array of integers of any bit size <= 32.
  7924. This Python implementation is slow and only handles itemsizes 1, 2, 4, 8,
  7925. 16, 32, and 64.
  7926. Parameters
  7927. ----------
  7928. data : byte str
  7929. Data to decompress.
  7930. dtype : numpy.dtype or str
  7931. A numpy boolean or integer type.
  7932. itemsize : int
  7933. Number of bits per integer.
  7934. runlen : int
  7935. Number of consecutive integers, after which to start at next byte.
  7936. Examples
  7937. --------
  7938. >>> unpack_ints(b'a', 'B', 1)
  7939. array([0, 1, 1, 0, 0, 0, 0, 1], dtype=uint8)
  7940. >>> unpack_ints(b'ab', 'B', 2)
  7941. array([1, 2, 0, 1, 1, 2, 0, 2], dtype=uint8)
  7942. """
  7943. if itemsize == 1: # bitarray
  7944. data = numpy.frombuffer(data, "|B")
  7945. data = numpy.unpackbits(data)
  7946. if runlen % 8:
  7947. data = data.reshape(-1, runlen + (8 - runlen % 8))
  7948. data = data[:, :runlen].reshape(-1)
  7949. return data.astype(dtype)
  7950. dtype = numpy.dtype(dtype)
  7951. if itemsize in (8, 16, 32, 64):
  7952. return numpy.frombuffer(data, dtype)
  7953. if itemsize not in (1, 2, 4, 8, 16, 32):
  7954. raise ValueError("itemsize not supported: %i" % itemsize)
  7955. if dtype.kind not in "biu":
  7956. raise ValueError("invalid dtype")
  7957. itembytes = next(i for i in (1, 2, 4, 8) if 8 * i >= itemsize)
  7958. if itembytes != dtype.itemsize:
  7959. raise ValueError("dtype.itemsize too small")
  7960. if runlen == 0:
  7961. runlen = (8 * len(data)) // itemsize
  7962. skipbits = runlen * itemsize % 8
  7963. if skipbits:
  7964. skipbits = 8 - skipbits
  7965. shrbits = itembytes * 8 - itemsize
  7966. bitmask = int(itemsize * "1" + "0" * shrbits, 2)
  7967. dtypestr = ">" + dtype.char # dtype always big-endian?
  7968. unpack = struct.unpack
  7969. size = runlen * (len(data) * 8 // (runlen * itemsize + skipbits))
  7970. result = numpy.empty((size,), dtype)
  7971. bitcount = 0
  7972. for i in range(size):
  7973. start = bitcount // 8
  7974. s = data[start : start + itembytes]
  7975. try:
  7976. code = unpack(dtypestr, s)[0]
  7977. except Exception:
  7978. code = unpack(dtypestr, s + b"\x00" * (itembytes - len(s)))[0]
  7979. code <<= bitcount % 8
  7980. code &= bitmask
  7981. result[i] = code >> shrbits
  7982. bitcount += itemsize
  7983. if (i + 1) % runlen == 0:
  7984. bitcount += skipbits
  7985. return result
  7986. def unpack_rgb(data, dtype="<B", bitspersample=(5, 6, 5), rescale=True):
  7987. """Return array from byte string containing packed samples.
  7988. Use to unpack RGB565 or RGB555 to RGB888 format.
  7989. Parameters
  7990. ----------
  7991. data : byte str
  7992. The data to be decoded. Samples in each pixel are stored consecutively.
  7993. Pixels are aligned to 8, 16, or 32 bit boundaries.
  7994. dtype : numpy.dtype
  7995. The sample data type. The byteorder applies also to the data stream.
  7996. bitspersample : tuple
  7997. Number of bits for each sample in a pixel.
  7998. rescale : bool
  7999. Upscale samples to the number of bits in dtype.
  8000. Returns
  8001. -------
  8002. result : ndarray
  8003. Flattened array of unpacked samples of native dtype.
  8004. Examples
  8005. --------
  8006. >>> data = struct.pack('BBBB', 0x21, 0x08, 0xff, 0xff)
  8007. >>> print(unpack_rgb(data, '<B', (5, 6, 5), False))
  8008. [ 1 1 1 31 63 31]
  8009. >>> print(unpack_rgb(data, '<B', (5, 6, 5)))
  8010. [ 8 4 8 255 255 255]
  8011. >>> print(unpack_rgb(data, '<B', (5, 5, 5)))
  8012. [ 16 8 8 255 255 255]
  8013. """
  8014. dtype = numpy.dtype(dtype)
  8015. bits = int(numpy.sum(bitspersample))
  8016. if not (bits <= 32 and all(i <= dtype.itemsize * 8 for i in bitspersample)):
  8017. raise ValueError("sample size not supported: %s" % str(bitspersample))
  8018. dt = next(i for i in "BHI" if numpy.dtype(i).itemsize * 8 >= bits)
  8019. data = numpy.frombuffer(data, dtype.byteorder + dt)
  8020. result = numpy.empty((data.size, len(bitspersample)), dtype.char)
  8021. for i, bps in enumerate(bitspersample):
  8022. t = data >> int(numpy.sum(bitspersample[i + 1 :]))
  8023. t &= int("0b" + "1" * bps, 2)
  8024. if rescale:
  8025. o = ((dtype.itemsize * 8) // bps + 1) * bps
  8026. if o > data.dtype.itemsize * 8:
  8027. t = t.astype("I")
  8028. t *= (2**o - 1) // (2**bps - 1)
  8029. t //= 2 ** (o - (dtype.itemsize * 8))
  8030. result[:, i] = t
  8031. return result.reshape(-1)
  8032. @_replace_by("_tifffile.reverse_bitorder")
  8033. def reverse_bitorder(data):
  8034. """Reverse bits in each byte of byte string or numpy array.
  8035. Decode data where pixels with lower column values are stored in the
  8036. lower-order bits of the bytes (FillOrder is LSB2MSB).
  8037. Parameters
  8038. ----------
  8039. data : byte string or ndarray
  8040. The data to be bit reversed. If byte string, a new bit-reversed byte
  8041. string is returned. Numpy arrays are bit-reversed in-place.
  8042. Examples
  8043. --------
  8044. >>> reverse_bitorder(b'\\x01\\x64')
  8045. b'\\x80&'
  8046. >>> data = numpy.array([1, 666], dtype='uint16')
  8047. >>> reverse_bitorder(data)
  8048. >>> data
  8049. array([ 128, 16473], dtype=uint16)
  8050. """
  8051. try:
  8052. view = data.view("uint8")
  8053. numpy.take(TIFF.REVERSE_BITORDER_ARRAY, view, out=view)
  8054. except AttributeError:
  8055. return data.translate(TIFF.REVERSE_BITORDER_BYTES)
  8056. except ValueError:
  8057. raise NotImplementedError("slices of arrays not supported")
  8058. def apply_colormap(image, colormap, contig=True):
  8059. """Return palette-colored image.
  8060. The image values are used to index the colormap on axis 1. The returned
  8061. image is of shape image.shape+colormap.shape[0] and dtype colormap.dtype.
  8062. Parameters
  8063. ----------
  8064. image : numpy.ndarray
  8065. Indexes into the colormap.
  8066. colormap : numpy.ndarray
  8067. RGB lookup table aka palette of shape (3, 2**bits_per_sample).
  8068. contig : bool
  8069. If True, return a contiguous array.
  8070. Examples
  8071. --------
  8072. >>> image = numpy.arange(256, dtype='uint8')
  8073. >>> colormap = numpy.vstack([image, image, image]).astype('uint16') * 256
  8074. >>> apply_colormap(image, colormap)[-1]
  8075. array([65280, 65280, 65280], dtype=uint16)
  8076. """
  8077. image = numpy.take(colormap, image, axis=1)
  8078. image = numpy.rollaxis(image, 0, image.ndim)
  8079. if contig:
  8080. image = numpy.ascontiguousarray(image)
  8081. return image
  8082. def reorient(image, orientation):
  8083. """Return reoriented view of image array.
  8084. Parameters
  8085. ----------
  8086. image : numpy.ndarray
  8087. Non-squeezed output of asarray() functions.
  8088. Axes -3 and -2 must be image length and width respectively.
  8089. orientation : int or str
  8090. One of TIFF.ORIENTATION names or values.
  8091. """
  8092. ORIENTATION = TIFF.ORIENTATION
  8093. orientation = enumarg(ORIENTATION, orientation)
  8094. if orientation == ORIENTATION.TOPLEFT:
  8095. return image
  8096. elif orientation == ORIENTATION.TOPRIGHT:
  8097. return image[..., ::-1, :]
  8098. elif orientation == ORIENTATION.BOTLEFT:
  8099. return image[..., ::-1, :, :]
  8100. elif orientation == ORIENTATION.BOTRIGHT:
  8101. return image[..., ::-1, ::-1, :]
  8102. elif orientation == ORIENTATION.LEFTTOP:
  8103. return numpy.swapaxes(image, -3, -2)
  8104. elif orientation == ORIENTATION.RIGHTTOP:
  8105. return numpy.swapaxes(image, -3, -2)[..., ::-1, :]
  8106. elif orientation == ORIENTATION.RIGHTBOT:
  8107. return numpy.swapaxes(image, -3, -2)[..., ::-1, :, :]
  8108. elif orientation == ORIENTATION.LEFTBOT:
  8109. return numpy.swapaxes(image, -3, -2)[..., ::-1, ::-1, :]
  8110. def repeat_nd(a, repeats):
  8111. """Return read-only view into input array with elements repeated.
  8112. Zoom nD image by integer factors using nearest neighbor interpolation
  8113. (box filter).
  8114. Parameters
  8115. ----------
  8116. a : array_like
  8117. Input array.
  8118. repeats : sequence of int
  8119. The number of repetitions to apply along each dimension of input array.
  8120. Example
  8121. -------
  8122. >>> repeat_nd([[1, 2], [3, 4]], (2, 2))
  8123. array([[1, 1, 2, 2],
  8124. [1, 1, 2, 2],
  8125. [3, 3, 4, 4],
  8126. [3, 3, 4, 4]])
  8127. """
  8128. a = numpy.asarray(a)
  8129. reshape = []
  8130. shape = []
  8131. strides = []
  8132. for i, j, k in zip(a.strides, a.shape, repeats):
  8133. shape.extend((j, k))
  8134. strides.extend((i, 0))
  8135. reshape.append(j * k)
  8136. return numpy.lib.stride_tricks.as_strided(
  8137. a, shape, strides, writeable=False
  8138. ).reshape(reshape)
  8139. def reshape_nd(data_or_shape, ndim):
  8140. """Return image array or shape with at least ndim dimensions.
  8141. Prepend 1s to image shape as necessary.
  8142. >>> reshape_nd(numpy.empty(0), 1).shape
  8143. (0,)
  8144. >>> reshape_nd(numpy.empty(1), 2).shape
  8145. (1, 1)
  8146. >>> reshape_nd(numpy.empty((2, 3)), 3).shape
  8147. (1, 2, 3)
  8148. >>> reshape_nd(numpy.empty((3, 4, 5)), 3).shape
  8149. (3, 4, 5)
  8150. >>> reshape_nd((2, 3), 3)
  8151. (1, 2, 3)
  8152. """
  8153. is_shape = isinstance(data_or_shape, tuple)
  8154. shape = data_or_shape if is_shape else data_or_shape.shape
  8155. if len(shape) >= ndim:
  8156. return data_or_shape
  8157. shape = (1,) * (ndim - len(shape)) + shape
  8158. return shape if is_shape else data_or_shape.reshape(shape)
  8159. def squeeze_axes(shape, axes, skip="XY"):
  8160. """Return shape and axes with single-dimensional entries removed.
  8161. Remove unused dimensions unless their axes are listed in 'skip'.
  8162. >>> squeeze_axes((5, 1, 2, 1, 1), 'TZYXC')
  8163. ((5, 2, 1), 'TYX')
  8164. """
  8165. if len(shape) != len(axes):
  8166. raise ValueError("dimensions of axes and shape do not match")
  8167. shape, axes = zip(*(i for i in zip(shape, axes) if i[0] > 1 or i[1] in skip))
  8168. return tuple(shape), "".join(axes)
  8169. def transpose_axes(image, axes, asaxes="CTZYX"):
  8170. """Return image with its axes permuted to match specified axes.
  8171. A view is returned if possible.
  8172. >>> transpose_axes(numpy.zeros((2, 3, 4, 5)), 'TYXC', asaxes='CTZYX').shape
  8173. (5, 2, 1, 3, 4)
  8174. """
  8175. for ax in axes:
  8176. if ax not in asaxes:
  8177. raise ValueError("unknown axis %s" % ax)
  8178. # add missing axes to image
  8179. shape = image.shape
  8180. for ax in reversed(asaxes):
  8181. if ax not in axes:
  8182. axes = ax + axes
  8183. shape = (1,) + shape
  8184. image = image.reshape(shape)
  8185. # transpose axes
  8186. image = image.transpose([axes.index(ax) for ax in asaxes])
  8187. return image
  8188. def reshape_axes(axes, shape, newshape, unknown="Q"):
  8189. """Return axes matching new shape.
  8190. Unknown dimensions are labelled 'Q'.
  8191. >>> reshape_axes('YXS', (219, 301, 1), (219, 301))
  8192. 'YX'
  8193. >>> reshape_axes('IYX', (12, 219, 301), (3, 4, 219, 1, 301, 1))
  8194. 'QQYQXQ'
  8195. """
  8196. shape = tuple(shape)
  8197. newshape = tuple(newshape)
  8198. if len(axes) != len(shape):
  8199. raise ValueError("axes do not match shape")
  8200. size = product(shape)
  8201. newsize = product(newshape)
  8202. if size != newsize:
  8203. raise ValueError("cannot reshape %s to %s" % (shape, newshape))
  8204. if not axes or not newshape:
  8205. return ""
  8206. lendiff = max(0, len(shape) - len(newshape))
  8207. if lendiff:
  8208. newshape = newshape + (1,) * lendiff
  8209. i = len(shape) - 1
  8210. prodns = 1
  8211. prods = 1
  8212. result = []
  8213. for ns in newshape[::-1]:
  8214. prodns *= ns
  8215. while i > 0 and shape[i] == 1 and ns != 1:
  8216. i -= 1
  8217. if ns == shape[i] and prodns == prods * shape[i]:
  8218. prods *= shape[i]
  8219. result.append(axes[i])
  8220. i -= 1
  8221. else:
  8222. result.append(unknown)
  8223. return "".join(reversed(result[lendiff:]))
  8224. def stack_pages(pages, out=None, maxworkers=1, *args, **kwargs):
  8225. """Read data from sequence of TiffPage and stack them vertically.
  8226. Additional parameters are passed to the TiffPage.asarray function.
  8227. """
  8228. npages = len(pages)
  8229. if npages == 0:
  8230. raise ValueError("no pages")
  8231. if npages == 1:
  8232. return pages[0].asarray(out=out, *args, **kwargs)
  8233. page0 = next(p for p in pages if p is not None)
  8234. page0.asarray(validate=None) # ThreadPoolExecutor swallows exceptions
  8235. shape = (npages,) + page0.keyframe.shape
  8236. dtype = page0.keyframe.dtype
  8237. out = create_output(out, shape, dtype)
  8238. if maxworkers is None:
  8239. maxworkers = multiprocessing.cpu_count() // 2
  8240. page0.parent.filehandle.lock = maxworkers > 1
  8241. filecache = OpenFileCache(
  8242. size=max(4, maxworkers), lock=page0.parent.filehandle.lock
  8243. )
  8244. def func(page, index, out=out, filecache=filecache, args=args, kwargs=kwargs):
  8245. """Read, decode, and copy page data."""
  8246. if page is not None:
  8247. filecache.open(page.parent.filehandle)
  8248. out[index] = page.asarray(
  8249. lock=filecache.lock, reopen=False, validate=False, *args, **kwargs
  8250. )
  8251. filecache.close(page.parent.filehandle)
  8252. if maxworkers < 2:
  8253. for i, page in enumerate(pages):
  8254. func(page, i)
  8255. else:
  8256. with concurrent.futures.ThreadPoolExecutor(maxworkers) as executor:
  8257. executor.map(func, pages, range(npages))
  8258. filecache.clear()
  8259. page0.parent.filehandle.lock = None
  8260. return out
  8261. def clean_offsets_counts(offsets, counts):
  8262. """Return cleaned offsets and byte counts.
  8263. Remove zero offsets and counts. Use to sanitize _offsets and _bytecounts
  8264. tag values for strips or tiles.
  8265. """
  8266. offsets = list(offsets)
  8267. counts = list(counts)
  8268. assert len(offsets) == len(counts)
  8269. j = 0
  8270. for i, (o, b) in enumerate(zip(offsets, counts)):
  8271. if o > 0 and b > 0:
  8272. if i > j:
  8273. offsets[j] = o
  8274. counts[j] = b
  8275. j += 1
  8276. elif b > 0 and o <= 0:
  8277. raise ValueError("invalid offset")
  8278. else:
  8279. warnings.warn("empty byte count")
  8280. if j == 0:
  8281. j = 1
  8282. return offsets[:j], counts[:j]
  8283. def buffered_read(fh, lock, offsets, bytecounts, buffersize=2**26):
  8284. """Return iterator over blocks read from file."""
  8285. length = len(offsets)
  8286. i = 0
  8287. while i < length:
  8288. data = []
  8289. with lock:
  8290. size = 0
  8291. while size < buffersize and i < length:
  8292. fh.seek(offsets[i])
  8293. bytecount = bytecounts[i]
  8294. data.append(fh.read(bytecount))
  8295. size += bytecount
  8296. i += 1
  8297. for block in data:
  8298. yield block
  8299. def create_output(out, shape, dtype, mode="w+", suffix=".memmap"):
  8300. """Return numpy array where image data of shape and dtype can be copied.
  8301. The 'out' parameter may have the following values or types:
  8302. None
  8303. An empty array of shape and dtype is created and returned.
  8304. numpy.ndarray
  8305. An existing writable array of compatible dtype and shape. A view of
  8306. the same array is returned after verification.
  8307. 'memmap' or 'memmap:tempdir'
  8308. A memory-map to an array stored in a temporary binary file on disk
  8309. is created and returned.
  8310. str or open file
  8311. The file name or file object used to create a memory-map to an array
  8312. stored in a binary file on disk. The created memory-mapped array is
  8313. returned.
  8314. """
  8315. if out is None:
  8316. return numpy.zeros(shape, dtype)
  8317. if isinstance(out, str) and out[:6] == "memmap":
  8318. tempdir = out[7:] if len(out) > 7 else None
  8319. with tempfile.NamedTemporaryFile(dir=tempdir, suffix=suffix) as fh:
  8320. return numpy.memmap(fh, shape=shape, dtype=dtype, mode=mode)
  8321. if isinstance(out, numpy.ndarray):
  8322. if product(shape) != product(out.shape):
  8323. raise ValueError("incompatible output shape")
  8324. if not numpy.can_cast(dtype, out.dtype):
  8325. raise ValueError("incompatible output dtype")
  8326. return out.reshape(shape)
  8327. if isinstance(out, pathlib.Path):
  8328. out = str(out)
  8329. return numpy.memmap(out, shape=shape, dtype=dtype, mode=mode)
  8330. def matlabstr2py(string):
  8331. """Return Python object from Matlab string representation.
  8332. Return str, bool, int, float, list (Matlab arrays or cells), or
  8333. dict (Matlab structures) types.
  8334. Use to access ScanImage metadata.
  8335. >>> matlabstr2py('1')
  8336. 1
  8337. >>> matlabstr2py("['x y z' true false; 1 2.0 -3e4; NaN Inf @class]")
  8338. [['x y z', True, False], [1, 2.0, -30000.0], [nan, inf, '@class']]
  8339. >>> d = matlabstr2py("SI.hChannels.channelType = {'stripe' 'stripe'}\\n"
  8340. ... "SI.hChannels.channelsActive = 2")
  8341. >>> d['SI.hChannels.channelType']
  8342. ['stripe', 'stripe']
  8343. """
  8344. # TODO: handle invalid input
  8345. # TODO: review unboxing of multidimensional arrays
  8346. def lex(s):
  8347. # return sequence of tokens from matlab string representation
  8348. tokens = ["["]
  8349. while True:
  8350. t, i = next_token(s)
  8351. if t is None:
  8352. break
  8353. if t == ";":
  8354. tokens.extend(("]", "["))
  8355. elif t == "[":
  8356. tokens.extend(("[", "["))
  8357. elif t == "]":
  8358. tokens.extend(("]", "]"))
  8359. else:
  8360. tokens.append(t)
  8361. s = s[i:]
  8362. tokens.append("]")
  8363. return tokens
  8364. def next_token(s):
  8365. # return next token in matlab string
  8366. length = len(s)
  8367. if length == 0:
  8368. return None, 0
  8369. i = 0
  8370. while i < length and s[i] == " ":
  8371. i += 1
  8372. if i == length:
  8373. return None, i
  8374. if s[i] in "{[;]}":
  8375. return s[i], i + 1
  8376. if s[i] == "'":
  8377. j = i + 1
  8378. while j < length and s[j] != "'":
  8379. j += 1
  8380. return s[i : j + 1], j + 1
  8381. if s[i] == "<":
  8382. j = i + 1
  8383. while j < length and s[j] != ">":
  8384. j += 1
  8385. return s[i : j + 1], j + 1
  8386. j = i
  8387. while j < length and s[j] not in " {[;]}":
  8388. j += 1
  8389. return s[i:j], j
  8390. def value(s, fail=False):
  8391. # return Python value of token
  8392. s = s.strip()
  8393. if not s:
  8394. return s
  8395. if len(s) == 1:
  8396. try:
  8397. return int(s)
  8398. except Exception:
  8399. if fail:
  8400. raise ValueError()
  8401. return s
  8402. if s[0] == "'":
  8403. if fail and s[-1] != "'" or "'" in s[1:-1]:
  8404. raise ValueError()
  8405. return s[1:-1]
  8406. if s[0] == "<":
  8407. if fail and s[-1] != ">" or "<" in s[1:-1]:
  8408. raise ValueError()
  8409. return s
  8410. if fail and any(i in s for i in " ';[]{}"):
  8411. raise ValueError()
  8412. if s[0] == "@":
  8413. return s
  8414. if s in ("true", "True"):
  8415. return True
  8416. if s in ("false", "False"):
  8417. return False
  8418. if s[:6] == "zeros(":
  8419. return numpy.zeros([int(i) for i in s[6:-1].split(",")]).tolist()
  8420. if s[:5] == "ones(":
  8421. return numpy.ones([int(i) for i in s[5:-1].split(",")]).tolist()
  8422. if "." in s or "e" in s:
  8423. try:
  8424. return float(s)
  8425. except Exception:
  8426. pass
  8427. try:
  8428. return int(s)
  8429. except Exception:
  8430. pass
  8431. try:
  8432. return float(s) # nan, inf
  8433. except Exception:
  8434. if fail:
  8435. raise ValueError()
  8436. return s
  8437. def parse(s):
  8438. # return Python value from string representation of Matlab value
  8439. s = s.strip()
  8440. try:
  8441. return value(s, fail=True)
  8442. except ValueError:
  8443. pass
  8444. result = add2 = []
  8445. levels = [add2]
  8446. for t in lex(s):
  8447. if t in "[{":
  8448. add2 = []
  8449. levels.append(add2)
  8450. elif t in "]}":
  8451. x = levels.pop()
  8452. if len(x) == 1 and isinstance(x[0], (list, str)):
  8453. x = x[0]
  8454. add2 = levels[-1]
  8455. add2.append(x)
  8456. else:
  8457. add2.append(value(t))
  8458. if len(result) == 1 and isinstance(result[0], (list, str)):
  8459. result = result[0]
  8460. return result
  8461. if "\r" in string or "\n" in string:
  8462. # structure
  8463. d = {}
  8464. for line in string.splitlines():
  8465. line = line.strip()
  8466. if not line or line[0] == "%":
  8467. continue
  8468. k, v = line.split("=", 1)
  8469. k = k.strip()
  8470. if any(c in k for c in " ';[]{}<>"):
  8471. continue
  8472. d[k] = parse(v)
  8473. return d
  8474. return parse(string)
  8475. def stripnull(string, null=b"\x00"):
  8476. """Return string truncated at first null character.
  8477. Clean NULL terminated C strings. For unicode strings use null='\\0'.
  8478. >>> stripnull(b'string\\x00')
  8479. b'string'
  8480. >>> stripnull('string\\x00', null='\\0')
  8481. 'string'
  8482. """
  8483. i = string.find(null)
  8484. return string if (i < 0) else string[:i]
  8485. def stripascii(string):
  8486. """Return string truncated at last byte that is 7-bit ASCII.
  8487. Clean NULL separated and terminated TIFF strings.
  8488. >>> stripascii(b'string\\x00string\\n\\x01\\x00')
  8489. b'string\\x00string\\n'
  8490. >>> stripascii(b'\\x00')
  8491. b''
  8492. """
  8493. # TODO: pythonize this
  8494. i = len(string)
  8495. while i:
  8496. i -= 1
  8497. if 8 < byte2int(string[i]) < 127:
  8498. break
  8499. else:
  8500. i = -1
  8501. return string[: i + 1]
  8502. def asbool(value, true=(b"true", "true"), false=(b"false", "false")):
  8503. """Return string as bool if possible, else raise TypeError.
  8504. >>> asbool(b' False ')
  8505. False
  8506. """
  8507. value = value.strip().lower()
  8508. if value in true: # might raise UnicodeWarning/BytesWarning
  8509. return True
  8510. if value in false:
  8511. return False
  8512. raise TypeError()
  8513. def astype(value, types=None):
  8514. """Return argument as one of types if possible.
  8515. >>> astype('42')
  8516. 42
  8517. >>> astype('3.14')
  8518. 3.14
  8519. >>> astype('True')
  8520. True
  8521. >>> astype(b'Neee-Wom')
  8522. 'Neee-Wom'
  8523. """
  8524. if types is None:
  8525. types = int, float, asbool, bytes2str
  8526. for typ in types:
  8527. try:
  8528. return typ(value)
  8529. except (ValueError, AttributeError, TypeError, UnicodeEncodeError):
  8530. pass
  8531. return value
  8532. def format_size(size, threshold=1536):
  8533. """Return file size as string from byte size.
  8534. >>> format_size(1234)
  8535. '1234 B'
  8536. >>> format_size(12345678901)
  8537. '11.50 GiB'
  8538. """
  8539. if size < threshold:
  8540. return "%i B" % size
  8541. for unit in ("KiB", "MiB", "GiB", "TiB", "PiB"):
  8542. size /= 1024.0
  8543. if size < threshold:
  8544. return "%.2f %s" % (size, unit)
  8545. def identityfunc(arg):
  8546. """Single argument identity function.
  8547. >>> identityfunc('arg')
  8548. 'arg'
  8549. """
  8550. return arg
  8551. def nullfunc(*args, **kwargs):
  8552. """Null function.
  8553. >>> nullfunc('arg', kwarg='kwarg')
  8554. """
  8555. return
  8556. def sequence(value):
  8557. """Return tuple containing value if value is not a sequence.
  8558. >>> sequence(1)
  8559. (1,)
  8560. >>> sequence([1])
  8561. [1]
  8562. """
  8563. try:
  8564. len(value)
  8565. return value
  8566. except TypeError:
  8567. return (value,)
  8568. def product(iterable):
  8569. """Return product of sequence of numbers.
  8570. Equivalent of functools.reduce(operator.mul, iterable, 1).
  8571. Multiplying numpy integers might overflow.
  8572. >>> product([2**8, 2**30])
  8573. 274877906944
  8574. >>> product([])
  8575. 1
  8576. """
  8577. prod = 1
  8578. for i in iterable:
  8579. prod *= i
  8580. return prod
  8581. def natural_sorted(iterable):
  8582. """Return human sorted list of strings.
  8583. E.g. for sorting file names.
  8584. >>> natural_sorted(['f1', 'f2', 'f10'])
  8585. ['f1', 'f2', 'f10']
  8586. """
  8587. def sortkey(x):
  8588. return [(int(c) if c.isdigit() else c) for c in re.split(numbers, x)]
  8589. numbers = re.compile(r"(\d+)")
  8590. return sorted(iterable, key=sortkey)
  8591. def excel_datetime(timestamp, epoch=datetime.datetime.fromordinal(693594)):
  8592. """Return datetime object from timestamp in Excel serial format.
  8593. Convert LSM time stamps.
  8594. >>> excel_datetime(40237.029999999795)
  8595. datetime.datetime(2010, 2, 28, 0, 43, 11, 999982)
  8596. """
  8597. return epoch + datetime.timedelta(timestamp)
  8598. def julian_datetime(julianday, milisecond=0):
  8599. """Return datetime from days since 1/1/4713 BC and ms since midnight.
  8600. Convert Julian dates according to MetaMorph.
  8601. >>> julian_datetime(2451576, 54362783)
  8602. datetime.datetime(2000, 2, 2, 15, 6, 2, 783)
  8603. """
  8604. if julianday <= 1721423:
  8605. # no datetime before year 1
  8606. return None
  8607. a = julianday + 1
  8608. if a > 2299160:
  8609. alpha = math.trunc((a - 1867216.25) / 36524.25)
  8610. a += 1 + alpha - alpha // 4
  8611. b = a + (1524 if a > 1721423 else 1158)
  8612. c = math.trunc((b - 122.1) / 365.25)
  8613. d = math.trunc(365.25 * c)
  8614. e = math.trunc((b - d) / 30.6001)
  8615. day = b - d - math.trunc(30.6001 * e)
  8616. month = e - (1 if e < 13.5 else 13)
  8617. year = c - (4716 if month > 2.5 else 4715)
  8618. hour, milisecond = divmod(milisecond, 1000 * 60 * 60)
  8619. minute, milisecond = divmod(milisecond, 1000 * 60)
  8620. second, milisecond = divmod(milisecond, 1000)
  8621. return datetime.datetime(year, month, day, hour, minute, second, milisecond)
  8622. def byteorder_isnative(byteorder):
  8623. """Return if byteorder matches the system's byteorder.
  8624. >>> byteorder_isnative('=')
  8625. True
  8626. """
  8627. if byteorder == "=" or byteorder == sys.byteorder:
  8628. return True
  8629. keys = {"big": ">", "little": "<"}
  8630. return keys.get(byteorder, byteorder) == keys[sys.byteorder]
  8631. def recarray2dict(recarray):
  8632. """Return numpy.recarray as dict."""
  8633. # TODO: subarrays
  8634. result = {}
  8635. for descr, value in zip(recarray.dtype.descr, recarray):
  8636. name, dtype = descr[:2]
  8637. if dtype[1] == "S":
  8638. value = bytes2str(stripnull(value))
  8639. elif value.ndim < 2:
  8640. value = value.tolist()
  8641. result[name] = value
  8642. return result
  8643. def xml2dict(xml, sanitize=True, prefix=None):
  8644. """Return XML as dict.
  8645. >>> xml2dict('<?xml version="1.0" ?><root attr="name"><key>1</key></root>')
  8646. {'root': {'key': 1, 'attr': 'name'}}
  8647. """
  8648. from xml.etree import cElementTree as etree # delayed import
  8649. at = tx = ""
  8650. if prefix:
  8651. at, tx = prefix
  8652. def astype(value):
  8653. # return value as int, float, bool, or str
  8654. for t in (int, float, asbool):
  8655. try:
  8656. return t(value)
  8657. except Exception:
  8658. pass
  8659. return value
  8660. def etree2dict(t):
  8661. # adapted from https://stackoverflow.com/a/10077069/453463
  8662. key = t.tag
  8663. if sanitize:
  8664. key = key.rsplit("}", 1)[-1]
  8665. d = {key: {} if t.attrib else None}
  8666. children = list(t)
  8667. if children:
  8668. dd = collections.defaultdict(list)
  8669. for dc in map(etree2dict, children):
  8670. for k, v in dc.items():
  8671. dd[k].append(astype(v))
  8672. d = {
  8673. key: {
  8674. k: astype(v[0]) if len(v) == 1 else astype(v) for k, v in dd.items()
  8675. }
  8676. }
  8677. if t.attrib:
  8678. d[key].update((at + k, astype(v)) for k, v in t.attrib.items())
  8679. if t.text:
  8680. text = t.text.strip()
  8681. if children or t.attrib:
  8682. if text:
  8683. d[key][tx + "value"] = astype(text)
  8684. else:
  8685. d[key] = astype(text)
  8686. return d
  8687. return etree2dict(etree.fromstring(xml))
  8688. def hexdump(bytestr, width=75, height=24, snipat=-2, modulo=2, ellipsis="..."):
  8689. """Return hexdump representation of byte string.
  8690. >>> hexdump(binascii.unhexlify('49492a00080000000e00fe0004000100'))
  8691. '49 49 2a 00 08 00 00 00 0e 00 fe 00 04 00 01 00 II*.............'
  8692. """
  8693. size = len(bytestr)
  8694. if size < 1 or width < 2 or height < 1:
  8695. return ""
  8696. if height == 1:
  8697. addr = b""
  8698. bytesperline = min(modulo * (((width - len(addr)) // 4) // modulo), size)
  8699. if bytesperline < 1:
  8700. return ""
  8701. nlines = 1
  8702. else:
  8703. addr = b"%%0%ix: " % len(b"%x" % size)
  8704. bytesperline = min(modulo * (((width - len(addr % 1)) // 4) // modulo), size)
  8705. if bytesperline < 1:
  8706. return ""
  8707. width = 3 * bytesperline + len(addr % 1)
  8708. nlines = (size - 1) // bytesperline + 1
  8709. if snipat is None or snipat == 1:
  8710. snipat = height
  8711. elif 0 < abs(snipat) < 1:
  8712. snipat = int(math.floor(height * snipat))
  8713. if snipat < 0:
  8714. snipat += height
  8715. if height == 1 or nlines == 1:
  8716. blocks = [(0, bytestr[:bytesperline])]
  8717. addr = b""
  8718. height = 1
  8719. width = 3 * bytesperline
  8720. elif height is None or nlines <= height:
  8721. blocks = [(0, bytestr)]
  8722. elif snipat <= 0:
  8723. start = bytesperline * (nlines - height)
  8724. blocks = [(start, bytestr[start:])] # (start, None)
  8725. elif snipat >= height or height < 3:
  8726. end = bytesperline * height
  8727. blocks = [(0, bytestr[:end])] # (end, None)
  8728. else:
  8729. end1 = bytesperline * snipat
  8730. end2 = bytesperline * (height - snipat - 1)
  8731. blocks = [
  8732. (0, bytestr[:end1]),
  8733. (size - end1 - end2, None),
  8734. (size - end2, bytestr[size - end2 :]),
  8735. ]
  8736. ellipsis = str2bytes(ellipsis)
  8737. result = []
  8738. for start, bytestr in blocks:
  8739. if bytestr is None:
  8740. result.append(ellipsis) # 'skip %i bytes' % start)
  8741. continue
  8742. hexstr = binascii.hexlify(bytestr)
  8743. strstr = re.sub(rb"[^\x20-\x7f]", b".", bytestr)
  8744. for i in range(0, len(bytestr), bytesperline):
  8745. h = hexstr[2 * i : 2 * i + bytesperline * 2]
  8746. r = (addr % (i + start)) if height > 1 else addr
  8747. r += b" ".join(h[i : i + 2] for i in range(0, 2 * bytesperline, 2))
  8748. r += b" " * (width - len(r))
  8749. r += strstr[i : i + bytesperline]
  8750. result.append(r)
  8751. result = b"\n".join(result)
  8752. if sys.version_info[0] == 3:
  8753. result = result.decode("ascii")
  8754. return result
  8755. def isprintable(string):
  8756. """Return if all characters in string are printable.
  8757. >>> isprintable('abc')
  8758. True
  8759. >>> isprintable(b'\01')
  8760. False
  8761. """
  8762. string = string.strip()
  8763. if len(string) < 1:
  8764. return True
  8765. if sys.version_info[0] == 3:
  8766. try:
  8767. return string.isprintable()
  8768. except Exception:
  8769. pass
  8770. try:
  8771. return string.decode("utf-8").isprintable()
  8772. except Exception:
  8773. pass
  8774. else:
  8775. if string.isalnum():
  8776. return True
  8777. printable = (
  8778. "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST"
  8779. "UVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c"
  8780. )
  8781. return all(c in printable for c in string)
  8782. def clean_whitespace(string, compact=False):
  8783. """Return string with compressed whitespace."""
  8784. for a, b in (
  8785. ("\r\n", "\n"),
  8786. ("\r", "\n"),
  8787. ("\n\n", "\n"),
  8788. ("\t", " "),
  8789. (" ", " "),
  8790. ):
  8791. string = string.replace(a, b)
  8792. if compact:
  8793. for a, b in (("\n", " "), ("[ ", "["), (" ", " "), (" ", " "), (" ", " ")):
  8794. string = string.replace(a, b)
  8795. return string.strip()
  8796. def pformat_xml(xml):
  8797. """Return pretty formatted XML."""
  8798. try:
  8799. import lxml.etree as etree # delayed import
  8800. if not isinstance(xml, bytes):
  8801. xml = xml.encode("utf-8")
  8802. xml = etree.parse(io.BytesIO(xml))
  8803. xml = etree.tostring(
  8804. xml, pretty_print=True, xml_declaration=True, encoding=xml.docinfo.encoding
  8805. )
  8806. xml = bytes2str(xml)
  8807. except Exception:
  8808. if isinstance(xml, bytes):
  8809. xml = bytes2str(xml)
  8810. xml = xml.replace("><", ">\n<")
  8811. return xml.replace(" ", " ").replace("\t", " ")
  8812. def pformat(arg, width=79, height=24, compact=True):
  8813. """Return pretty formatted representation of object as string.
  8814. Whitespace might be altered.
  8815. """
  8816. if height is None or height < 1:
  8817. height = 1024
  8818. if width is None or width < 1:
  8819. width = 256
  8820. npopt = numpy.get_printoptions()
  8821. numpy.set_printoptions(threshold=100, linewidth=width)
  8822. if isinstance(arg, basestring):
  8823. if arg[:5].lower() in ("<?xml", b"<?xml"):
  8824. if height == 1:
  8825. arg = arg[: 4 * width]
  8826. else:
  8827. arg = pformat_xml(arg)
  8828. elif isinstance(arg, bytes):
  8829. if isprintable(arg):
  8830. arg = bytes2str(arg)
  8831. arg = clean_whitespace(arg)
  8832. else:
  8833. numpy.set_printoptions(**npopt)
  8834. return hexdump(arg, width=width, height=height, modulo=1)
  8835. arg = arg.rstrip()
  8836. elif isinstance(arg, numpy.record):
  8837. arg = arg.pprint()
  8838. else:
  8839. import pprint # delayed import
  8840. compact = {} if sys.version_info[0] == 2 else dict(compact=compact)
  8841. arg = pprint.pformat(arg, width=width, **compact)
  8842. numpy.set_printoptions(**npopt)
  8843. if height == 1:
  8844. arg = clean_whitespace(arg, compact=True)
  8845. return arg[:width]
  8846. argl = list(arg.splitlines())
  8847. if len(argl) > height:
  8848. arg = "\n".join(argl[: height // 2] + ["..."] + argl[-height // 2 :])
  8849. return arg
  8850. def snipstr(string, width=79, snipat=0.5, ellipsis="..."):
  8851. """Return string cut to specified length.
  8852. >>> snipstr('abcdefghijklmnop', 8)
  8853. 'abc...op'
  8854. """
  8855. if ellipsis is None:
  8856. if isinstance(string, bytes):
  8857. ellipsis = b"..."
  8858. else:
  8859. ellipsis = "\u2026" # does not print on win-py3.5
  8860. esize = len(ellipsis)
  8861. splitlines = string.splitlines()
  8862. # TODO: finish and test multiline snip
  8863. result = []
  8864. for line in splitlines:
  8865. if line is None:
  8866. result.append(ellipsis)
  8867. continue
  8868. linelen = len(line)
  8869. if linelen <= width:
  8870. result.append(string)
  8871. continue
  8872. split = snipat
  8873. if split is None or split == 1:
  8874. split = linelen
  8875. elif 0 < abs(split) < 1:
  8876. split = int(math.floor(linelen * split))
  8877. if split < 0:
  8878. split += linelen
  8879. if split < 0:
  8880. split = 0
  8881. if esize == 0 or width < esize + 1:
  8882. if split <= 0:
  8883. result.append(string[-width:])
  8884. else:
  8885. result.append(string[:width])
  8886. elif split <= 0:
  8887. result.append(ellipsis + string[esize - width :])
  8888. elif split >= linelen or width < esize + 4:
  8889. result.append(string[: width - esize] + ellipsis)
  8890. else:
  8891. splitlen = linelen - width + esize
  8892. end1 = split - splitlen // 2
  8893. end2 = end1 + splitlen
  8894. result.append(string[:end1] + ellipsis + string[end2:])
  8895. if isinstance(string, bytes):
  8896. return b"\n".join(result)
  8897. else:
  8898. return "\n".join(result)
  8899. def enumarg(enum, arg):
  8900. """Return enum member from its name or value.
  8901. >>> enumarg(TIFF.PHOTOMETRIC, 2)
  8902. <PHOTOMETRIC.RGB: 2>
  8903. >>> enumarg(TIFF.PHOTOMETRIC, 'RGB')
  8904. <PHOTOMETRIC.RGB: 2>
  8905. """
  8906. try:
  8907. return enum(arg)
  8908. except Exception:
  8909. try:
  8910. return enum[arg.upper()]
  8911. except Exception:
  8912. raise ValueError("invalid argument %s" % arg)
  8913. def parse_kwargs(kwargs, *keys, **keyvalues):
  8914. """Return dict with keys from keys|keyvals and values from kwargs|keyvals.
  8915. Existing keys are deleted from kwargs.
  8916. >>> kwargs = {'one': 1, 'two': 2, 'four': 4}
  8917. >>> kwargs2 = parse_kwargs(kwargs, 'two', 'three', four=None, five=5)
  8918. >>> kwargs == {'one': 1}
  8919. True
  8920. >>> kwargs2 == {'two': 2, 'four': 4, 'five': 5}
  8921. True
  8922. """
  8923. result = {}
  8924. for key in keys:
  8925. if key in kwargs:
  8926. result[key] = kwargs[key]
  8927. del kwargs[key]
  8928. for key, value in keyvalues.items():
  8929. if key in kwargs:
  8930. result[key] = kwargs[key]
  8931. del kwargs[key]
  8932. else:
  8933. result[key] = value
  8934. return result
  8935. def update_kwargs(kwargs, **keyvalues):
  8936. """Update dict with keys and values if keys do not already exist.
  8937. >>> kwargs = {'one': 1, }
  8938. >>> update_kwargs(kwargs, one=None, two=2)
  8939. >>> kwargs == {'one': 1, 'two': 2}
  8940. True
  8941. """
  8942. for key, value in keyvalues.items():
  8943. if key not in kwargs:
  8944. kwargs[key] = value
  8945. def validate_jhove(filename, jhove="jhove", ignore=("More than 50 IFDs",)):
  8946. """Validate TIFF file using jhove -m TIFF-hul.
  8947. Raise ValueError if jhove outputs an error message unless the message
  8948. contains one of the strings in 'ignore'.
  8949. JHOVE does not support bigtiff or more than 50 IFDs.
  8950. See `JHOVE TIFF-hul Module <http://jhove.sourceforge.net/tiff-hul.html>`_
  8951. """
  8952. import subprocess # noqa: delayed import
  8953. out = subprocess.check_output([jhove, filename, "-m", "TIFF-hul"])
  8954. if b"ErrorMessage: " in out:
  8955. for line in out.splitlines():
  8956. line = line.strip()
  8957. if line.startswith(b"ErrorMessage: "):
  8958. error = line[14:].decode("utf8")
  8959. for i in ignore:
  8960. if i in error:
  8961. break
  8962. else:
  8963. raise ValueError(error)
  8964. break
  8965. def lsm2bin(lsmfile, binfile=None, tile=(256, 256), verbose=True):
  8966. """Convert [MP]TZCYX LSM file to series of BIN files.
  8967. One BIN file containing 'ZCYX' data are created for each position, time,
  8968. and tile. The position, time, and tile indices are encoded at the end
  8969. of the filenames.
  8970. """
  8971. verbose = print_ if verbose else nullfunc
  8972. if binfile is None:
  8973. binfile = lsmfile
  8974. elif binfile.lower() == "none":
  8975. binfile = None
  8976. if binfile:
  8977. binfile += "_(z%ic%iy%ix%i)_m%%ip%%it%%03iy%%ix%%i.bin"
  8978. verbose("\nOpening LSM file... ", end="", flush=True)
  8979. start_time = time.time()
  8980. with TiffFile(lsmfile) as lsm:
  8981. if not lsm.is_lsm:
  8982. verbose("\n", lsm, flush=True)
  8983. raise ValueError("not a LSM file")
  8984. series = lsm.series[0] # first series contains the image data
  8985. shape = series.shape
  8986. axes = series.axes
  8987. dtype = series.dtype
  8988. size = product(shape) * dtype.itemsize
  8989. verbose("%.3f s" % (time.time() - start_time))
  8990. # verbose(lsm, flush=True)
  8991. verbose(
  8992. "Image\n axes: %s\n shape: %s\n dtype: %s\n size: %s"
  8993. % (axes, shape, dtype, format_size(size)),
  8994. flush=True,
  8995. )
  8996. if not series.axes.endswith("TZCYX"):
  8997. raise ValueError("not a *TZCYX LSM file")
  8998. verbose("Copying image from LSM to BIN files", end="", flush=True)
  8999. start_time = time.time()
  9000. tiles = shape[-2] // tile[-2], shape[-1] // tile[-1]
  9001. if binfile:
  9002. binfile = binfile % (shape[-4], shape[-3], tile[0], tile[1])
  9003. shape = (1,) * (7 - len(shape)) + shape
  9004. # cache for ZCYX stacks and output files
  9005. data = numpy.empty(shape[3:], dtype=dtype)
  9006. out = numpy.empty((shape[-4], shape[-3], tile[0], tile[1]), dtype=dtype)
  9007. # iterate over Tiff pages containing data
  9008. pages = iter(series.pages)
  9009. for m in range(shape[0]): # mosaic axis
  9010. for p in range(shape[1]): # position axis
  9011. for t in range(shape[2]): # time axis
  9012. for z in range(shape[3]): # z slices
  9013. data[z] = next(pages).asarray()
  9014. for y in range(tiles[0]): # tile y
  9015. for x in range(tiles[1]): # tile x
  9016. out[:] = data[
  9017. ...,
  9018. y * tile[0] : (y + 1) * tile[0],
  9019. x * tile[1] : (x + 1) * tile[1],
  9020. ]
  9021. if binfile:
  9022. out.tofile(binfile % (m, p, t, y, x))
  9023. verbose(".", end="", flush=True)
  9024. verbose(" %.3f s" % (time.time() - start_time))
  9025. def imshow(
  9026. data,
  9027. title=None,
  9028. vmin=0,
  9029. vmax=None,
  9030. cmap=None,
  9031. bitspersample=None,
  9032. photometric="RGB",
  9033. interpolation=None,
  9034. dpi=96,
  9035. figure=None,
  9036. subplot=111,
  9037. maxdim=32768,
  9038. **kwargs,
  9039. ):
  9040. """Plot n-dimensional images using matplotlib.pyplot.
  9041. Return figure, subplot and plot axis.
  9042. Requires pyplot already imported C{from matplotlib import pyplot}.
  9043. Parameters
  9044. ----------
  9045. bitspersample : int or None
  9046. Number of bits per channel in integer RGB images.
  9047. photometric : {'MINISWHITE', 'MINISBLACK', 'RGB', or 'PALETTE'}
  9048. The color space of the image data.
  9049. title : str
  9050. Window and subplot title.
  9051. figure : matplotlib.figure.Figure (optional).
  9052. Matplotlib to use for plotting.
  9053. subplot : int
  9054. A matplotlib.pyplot.subplot axis.
  9055. maxdim : int
  9056. maximum image width and length.
  9057. kwargs : optional
  9058. Arguments for matplotlib.pyplot.imshow.
  9059. """
  9060. isrgb = photometric in ("RGB",) # 'PALETTE', 'YCBCR'
  9061. if data.dtype.kind == "b":
  9062. isrgb = False
  9063. if isrgb and not (
  9064. data.shape[-1] in (3, 4) or (data.ndim > 2 and data.shape[-3] in (3, 4))
  9065. ):
  9066. isrgb = False
  9067. photometric = "MINISBLACK"
  9068. data = data.squeeze()
  9069. if photometric in ("MINISWHITE", "MINISBLACK", None):
  9070. data = reshape_nd(data, 2)
  9071. else:
  9072. data = reshape_nd(data, 3)
  9073. dims = data.ndim
  9074. if dims < 2:
  9075. raise ValueError("not an image")
  9076. elif dims == 2:
  9077. dims = 0
  9078. isrgb = False
  9079. else:
  9080. if isrgb and data.shape[-3] in (3, 4):
  9081. data = numpy.swapaxes(data, -3, -2)
  9082. data = numpy.swapaxes(data, -2, -1)
  9083. elif not isrgb and (
  9084. data.shape[-1] < data.shape[-2] // 8
  9085. and data.shape[-1] < data.shape[-3] // 8
  9086. and data.shape[-1] < 5
  9087. ):
  9088. data = numpy.swapaxes(data, -3, -1)
  9089. data = numpy.swapaxes(data, -2, -1)
  9090. isrgb = isrgb and data.shape[-1] in (3, 4)
  9091. dims -= 3 if isrgb else 2
  9092. if isrgb:
  9093. data = data[..., :maxdim, :maxdim, :maxdim]
  9094. else:
  9095. data = data[..., :maxdim, :maxdim]
  9096. if photometric == "PALETTE" and isrgb:
  9097. datamax = data.max()
  9098. if datamax > 255:
  9099. data = data >> 8 # possible precision loss
  9100. data = data.astype("B")
  9101. elif data.dtype.kind in "ui":
  9102. if not (isrgb and data.dtype.itemsize <= 1) or bitspersample is None:
  9103. try:
  9104. bitspersample = int(math.ceil(math.log(data.max(), 2)))
  9105. except Exception:
  9106. bitspersample = data.dtype.itemsize * 8
  9107. elif not isinstance(bitspersample, inttypes):
  9108. # bitspersample can be tuple, e.g. (5, 6, 5)
  9109. bitspersample = data.dtype.itemsize * 8
  9110. datamax = 2**bitspersample
  9111. if isrgb:
  9112. if bitspersample < 8:
  9113. data = data << (8 - bitspersample)
  9114. elif bitspersample > 8:
  9115. data = data >> (bitspersample - 8) # precision loss
  9116. data = data.astype("B")
  9117. elif data.dtype.kind == "f":
  9118. datamax = data.max()
  9119. if isrgb and datamax > 1.0:
  9120. if data.dtype.char == "d":
  9121. data = data.astype("f")
  9122. data /= datamax
  9123. else:
  9124. data = data / datamax
  9125. elif data.dtype.kind == "b":
  9126. datamax = 1
  9127. elif data.dtype.kind == "c":
  9128. data = numpy.absolute(data)
  9129. datamax = data.max()
  9130. if not isrgb:
  9131. if vmax is None:
  9132. vmax = datamax
  9133. if vmin is None:
  9134. if data.dtype.kind == "i":
  9135. dtmin = numpy.iinfo(data.dtype).min
  9136. vmin = numpy.min(data)
  9137. if vmin == dtmin:
  9138. vmin = numpy.min(data > dtmin)
  9139. if data.dtype.kind == "f":
  9140. dtmin = numpy.finfo(data.dtype).min
  9141. vmin = numpy.min(data)
  9142. if vmin == dtmin:
  9143. vmin = numpy.min(data > dtmin)
  9144. else:
  9145. vmin = 0
  9146. pyplot = sys.modules["matplotlib.pyplot"]
  9147. if figure is None:
  9148. pyplot.rc("font", family="sans-serif", weight="normal", size=8)
  9149. figure = pyplot.figure(
  9150. dpi=dpi, figsize=(10.3, 6.3), frameon=True, facecolor="1.0", edgecolor="w"
  9151. )
  9152. try:
  9153. figure.canvas.manager.window.title(title)
  9154. except Exception:
  9155. pass
  9156. size = len(title.splitlines()) if title else 1
  9157. pyplot.subplots_adjust(
  9158. bottom=0.03 * (dims + 2),
  9159. top=0.98 - size * 0.03,
  9160. left=0.1,
  9161. right=0.95,
  9162. hspace=0.05,
  9163. wspace=0.0,
  9164. )
  9165. subplot = pyplot.subplot(subplot)
  9166. if title:
  9167. try:
  9168. title = unicode(title, "Windows-1252")
  9169. except TypeError:
  9170. pass
  9171. pyplot.title(title, size=11)
  9172. if cmap is None:
  9173. if data.dtype.char == "?":
  9174. cmap = "gray"
  9175. elif data.dtype.kind in "buf" or vmin == 0:
  9176. cmap = "viridis"
  9177. else:
  9178. cmap = "coolwarm"
  9179. if photometric == "MINISWHITE":
  9180. cmap += "_r"
  9181. image = pyplot.imshow(
  9182. numpy.atleast_2d(data[(0,) * dims].squeeze()),
  9183. vmin=vmin,
  9184. vmax=vmax,
  9185. cmap=cmap,
  9186. interpolation=interpolation,
  9187. **kwargs,
  9188. )
  9189. if not isrgb:
  9190. pyplot.colorbar() # panchor=(0.55, 0.5), fraction=0.05
  9191. def format_coord(x, y):
  9192. # callback function to format coordinate display in toolbar
  9193. x = int(x + 0.5)
  9194. y = int(y + 0.5)
  9195. try:
  9196. if dims:
  9197. return "%s @ %s [%4i, %4i]" % (curaxdat[1][y, x], current, y, x)
  9198. return "%s @ [%4i, %4i]" % (data[y, x], y, x)
  9199. except IndexError:
  9200. return ""
  9201. def none(event):
  9202. return ""
  9203. subplot.format_coord = format_coord
  9204. image.get_cursor_data = none
  9205. image.format_cursor_data = none
  9206. if dims:
  9207. current = list((0,) * dims)
  9208. curaxdat = [0, data[tuple(current)].squeeze()]
  9209. sliders = [
  9210. pyplot.Slider(
  9211. pyplot.axes([0.125, 0.03 * (axis + 1), 0.725, 0.025]),
  9212. "Dimension %i" % axis,
  9213. 0,
  9214. data.shape[axis] - 1,
  9215. 0,
  9216. facecolor="0.5",
  9217. valfmt="%%.0f [%i]" % data.shape[axis],
  9218. )
  9219. for axis in range(dims)
  9220. ]
  9221. for slider in sliders:
  9222. slider.drawon = False
  9223. def set_image(current, sliders=sliders, data=data):
  9224. # change image and redraw canvas
  9225. curaxdat[1] = data[tuple(current)].squeeze()
  9226. image.set_data(curaxdat[1])
  9227. for ctrl, index in zip(sliders, current):
  9228. ctrl.eventson = False
  9229. ctrl.set_val(index)
  9230. ctrl.eventson = True
  9231. figure.canvas.draw()
  9232. def on_changed(index, axis, data=data, current=current):
  9233. # callback function for slider change event
  9234. index = int(round(index))
  9235. curaxdat[0] = axis
  9236. if index == current[axis]:
  9237. return
  9238. if index >= data.shape[axis]:
  9239. index = 0
  9240. elif index < 0:
  9241. index = data.shape[axis] - 1
  9242. current[axis] = index
  9243. set_image(current)
  9244. def on_keypressed(event, data=data, current=current):
  9245. # callback function for key press event
  9246. key = event.key
  9247. axis = curaxdat[0]
  9248. if str(key) in "0123456789":
  9249. on_changed(key, axis)
  9250. elif key == "right":
  9251. on_changed(current[axis] + 1, axis)
  9252. elif key == "left":
  9253. on_changed(current[axis] - 1, axis)
  9254. elif key == "up":
  9255. curaxdat[0] = 0 if axis == len(data.shape) - 1 else axis + 1
  9256. elif key == "down":
  9257. curaxdat[0] = len(data.shape) - 1 if axis == 0 else axis - 1
  9258. elif key == "end":
  9259. on_changed(data.shape[axis] - 1, axis)
  9260. elif key == "home":
  9261. on_changed(0, axis)
  9262. figure.canvas.mpl_connect("key_press_event", on_keypressed)
  9263. for axis, ctrl in enumerate(sliders):
  9264. ctrl.on_changed(lambda k, a=axis: on_changed(k, a))
  9265. return figure, subplot, image
  9266. def _app_show():
  9267. """Block the GUI. For use as skimage plugin."""
  9268. pyplot = sys.modules["matplotlib.pyplot"]
  9269. pyplot.show()
  9270. def askopenfilename(**kwargs):
  9271. """Return file name(s) from Tkinter's file open dialog."""
  9272. try:
  9273. from Tkinter import Tk
  9274. import tkFileDialog as filedialog
  9275. except ImportError:
  9276. from tkinter import Tk, filedialog
  9277. root = Tk()
  9278. root.withdraw()
  9279. root.update()
  9280. filenames = filedialog.askopenfilename(**kwargs)
  9281. root.destroy()
  9282. return filenames
  9283. def main(argv=None):
  9284. """Command line usage main function."""
  9285. if float(sys.version[0:3]) < 2.7:
  9286. print("This script requires Python version 2.7 or better.")
  9287. print("This is Python version %s" % sys.version)
  9288. return 0
  9289. if argv is None:
  9290. argv = sys.argv
  9291. import optparse # TODO: use argparse
  9292. parser = optparse.OptionParser(
  9293. usage="usage: %prog [options] path",
  9294. description="Display image data in TIFF files.",
  9295. version="%%prog %s" % __version__,
  9296. )
  9297. opt = parser.add_option
  9298. opt("-p", "--page", dest="page", type="int", default=-1, help="display single page")
  9299. opt(
  9300. "-s",
  9301. "--series",
  9302. dest="series",
  9303. type="int",
  9304. default=-1,
  9305. help="display series of pages of same shape",
  9306. )
  9307. opt(
  9308. "--nomultifile",
  9309. dest="nomultifile",
  9310. action="store_true",
  9311. default=False,
  9312. help="do not read OME series from multiple files",
  9313. )
  9314. opt(
  9315. "--noplots",
  9316. dest="noplots",
  9317. type="int",
  9318. default=8,
  9319. help="maximum number of plots",
  9320. )
  9321. opt(
  9322. "--interpol",
  9323. dest="interpol",
  9324. metavar="INTERPOL",
  9325. default="bilinear",
  9326. help="image interpolation method",
  9327. )
  9328. opt("--dpi", dest="dpi", type="int", default=96, help="plot resolution")
  9329. opt(
  9330. "--vmin",
  9331. dest="vmin",
  9332. type="int",
  9333. default=None,
  9334. help="minimum value for colormapping",
  9335. )
  9336. opt(
  9337. "--vmax",
  9338. dest="vmax",
  9339. type="int",
  9340. default=None,
  9341. help="maximum value for colormapping",
  9342. )
  9343. opt(
  9344. "--debug",
  9345. dest="debug",
  9346. action="store_true",
  9347. default=False,
  9348. help="raise exception on failures",
  9349. )
  9350. opt(
  9351. "--doctest",
  9352. dest="doctest",
  9353. action="store_true",
  9354. default=False,
  9355. help="runs the docstring examples",
  9356. )
  9357. opt("-v", "--detail", dest="detail", type="int", default=2)
  9358. opt("-q", "--quiet", dest="quiet", action="store_true")
  9359. settings, path = parser.parse_args()
  9360. path = " ".join(path)
  9361. if settings.doctest:
  9362. import doctest
  9363. doctest.testmod(optionflags=doctest.ELLIPSIS)
  9364. return 0
  9365. if not path:
  9366. path = askopenfilename(
  9367. title="Select a TIFF file", filetypes=TIFF.FILEOPEN_FILTER
  9368. )
  9369. if not path:
  9370. parser.error("No file specified")
  9371. if any(i in path for i in "?*"):
  9372. path = glob.glob(path)
  9373. if not path:
  9374. print("no files match the pattern")
  9375. return 0
  9376. # TODO: handle image sequences
  9377. path = path[0]
  9378. if not settings.quiet:
  9379. print("\nReading file structure...", end=" ")
  9380. start = time.time()
  9381. try:
  9382. tif = TiffFile(path, multifile=not settings.nomultifile)
  9383. except Exception as e:
  9384. if settings.debug:
  9385. raise
  9386. else:
  9387. print("\n", e)
  9388. sys.exit(0)
  9389. if not settings.quiet:
  9390. print("%.3f ms" % ((time.time() - start) * 1e3))
  9391. if tif.is_ome:
  9392. settings.norgb = True
  9393. images = []
  9394. if settings.noplots > 0:
  9395. if not settings.quiet:
  9396. print("Reading image data... ", end=" ")
  9397. def notnone(x):
  9398. return next(i for i in x if i is not None)
  9399. start = time.time()
  9400. try:
  9401. if settings.page >= 0:
  9402. images = [(tif.asarray(key=settings.page), tif[settings.page], None)]
  9403. elif settings.series >= 0:
  9404. images = [
  9405. (
  9406. tif.asarray(series=settings.series),
  9407. notnone(tif.series[settings.series]._pages),
  9408. tif.series[settings.series],
  9409. )
  9410. ]
  9411. else:
  9412. images = []
  9413. for i, s in enumerate(tif.series[: settings.noplots]):
  9414. try:
  9415. images.append(
  9416. (tif.asarray(series=i), notnone(s._pages), tif.series[i])
  9417. )
  9418. except ValueError as e:
  9419. images.append((None, notnone(s.pages), None))
  9420. if settings.debug:
  9421. raise
  9422. else:
  9423. print("\nSeries %i failed: %s... " % (i, e), end="")
  9424. if not settings.quiet:
  9425. print("%.3f ms" % ((time.time() - start) * 1e3))
  9426. except Exception as e:
  9427. if settings.debug:
  9428. raise
  9429. else:
  9430. print(e)
  9431. if not settings.quiet:
  9432. print()
  9433. print(TiffFile.__str__(tif, detail=int(settings.detail)))
  9434. print()
  9435. tif.close()
  9436. if images and settings.noplots > 0:
  9437. try:
  9438. import matplotlib
  9439. matplotlib.use("TkAgg")
  9440. from matplotlib import pyplot
  9441. except ImportError as e:
  9442. warnings.warn("failed to import matplotlib.\n%s" % e)
  9443. else:
  9444. for img, page, series in images:
  9445. if img is None:
  9446. continue
  9447. vmin, vmax = settings.vmin, settings.vmax
  9448. if "GDAL_NODATA" in page.tags:
  9449. try:
  9450. vmin = numpy.min(
  9451. img[img > float(page.tags["GDAL_NODATA"].value)]
  9452. )
  9453. except ValueError:
  9454. pass
  9455. if tif.is_stk:
  9456. try:
  9457. vmin = tif.stk_metadata["MinScale"]
  9458. vmax = tif.stk_metadata["MaxScale"]
  9459. except KeyError:
  9460. pass
  9461. else:
  9462. if vmax <= vmin:
  9463. vmin, vmax = settings.vmin, settings.vmax
  9464. if series:
  9465. title = "%s\n%s\n%s" % (str(tif), str(page), str(series))
  9466. else:
  9467. title = "%s\n %s" % (str(tif), str(page))
  9468. photometric = "MINISBLACK"
  9469. if page.photometric not in (3,):
  9470. photometric = TIFF.PHOTOMETRIC(page.photometric).name
  9471. imshow(
  9472. img,
  9473. title=title,
  9474. vmin=vmin,
  9475. vmax=vmax,
  9476. bitspersample=page.bitspersample,
  9477. photometric=photometric,
  9478. interpolation=settings.interpol,
  9479. dpi=settings.dpi,
  9480. )
  9481. pyplot.show()
  9482. if sys.version_info[0] == 2:
  9483. inttypes = int, long # noqa
  9484. def print_(*args, **kwargs):
  9485. """Print function with flush support."""
  9486. flush = kwargs.pop("flush", False)
  9487. print(*args, **kwargs)
  9488. if flush:
  9489. sys.stdout.flush()
  9490. def bytes2str(b, encoding=None, errors=None):
  9491. """Return string from bytes."""
  9492. return b
  9493. def str2bytes(s, encoding=None):
  9494. """Return bytes from string."""
  9495. return s
  9496. def byte2int(b):
  9497. """Return value of byte as int."""
  9498. return ord(b)
  9499. class FileNotFoundError(IOError):
  9500. pass
  9501. TiffFrame = TiffPage # noqa
  9502. else:
  9503. inttypes = int
  9504. basestring = str, bytes
  9505. unicode = str
  9506. print_ = print
  9507. def bytes2str(b, encoding=None, errors="strict"):
  9508. """Return unicode string from encoded bytes."""
  9509. if encoding is not None:
  9510. return b.decode(encoding, errors)
  9511. try:
  9512. return b.decode("utf-8", errors)
  9513. except UnicodeDecodeError:
  9514. return b.decode("cp1252", errors)
  9515. def str2bytes(s, encoding="cp1252"):
  9516. """Return bytes from unicode string."""
  9517. return s.encode(encoding)
  9518. def byte2int(b):
  9519. """Return value of byte as int."""
  9520. return b
  9521. if __name__ == "__main__":
  9522. sys.exit(main())