tomobase-v0.0.0.dev1/0000775000175100017520000000000015237630637015353 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/.gitlab-ci.yml0000664000175100017520000000167015237630637020013 0ustar blagandzigblagandziginclude: - project: dau/ci/pyci file: .gitlab-ci-template.yml variables: DISABLE_BLACK_FLAKE8: "yes" # style/linting ruff: extends: .ruff # tests with dev deps test_python3_12_dev: extends: .test-3.12_glx variables: COVERAGE_SOURCE: "./$INTERNAL_PROJECT_NAME" PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}" PYTEST_COV: "true" # tests with PyPI deps test_sdist-3.10_glx: extends: .test_sdist-3.10_glx variables: PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}" test_sdist-3.12_glx: extends: .test_sdist-3.12_glx variables: PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}" PYTEST_COV: "true" test_sdist-3.13_glx: extends: .test_sdist-3.13_glx variables: PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}" # doc build_doc: extends: .build_doc build_sdist: extends: .build_sdist # deploy assets: extends: .assets pages: extends: .pages testpypi: extends: .testpypi pypi: extends: .pypi tomobase-v0.0.0.dev1/.readthedocs.yaml0000664000175100017520000000114415237630637020602 0ustar blagandzigblagandzig# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-24.04 tools: python: "3.12" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: doc/conf.py # If using Sphinx, optionally build your docs in additional formats such as PDF # formats: # - pdf # Optionally declare the Python requirements required to build your docs python: install: - method: pip path: . extra_requirements: - doc tomobase-v0.0.0.dev1/doc/0000775000175100017520000000000015237630637016120 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/doc/_templates/0000775000175100017520000000000015237630637020255 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/doc/_templates/version.html0000664000175100017520000000010015237630637022617 0ustar blagandzigblagandzig {{ version }}tomobase-v0.0.0.dev1/doc/_templates/autosummary/0000775000175100017520000000000015237630637022643 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/doc/_templates/autosummary/class.rst0000664000175100017520000000114215237630637024500 0ustar blagandzigblagandzig{{ fullname | escape | underline}} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} :members: :show-inheritance: :inherited-members: {% block methods %} .. automethod:: __init__ {% if methods %} .. rubric:: {{ _('Methods') }} .. autosummary:: {% for item in methods %} ~{{ name }}.{{ item }} {%- endfor %} {% endif %} {% endblock %} {% block attributes %} {% if attributes %} .. rubric:: {{ _('Attributes') }} .. autosummary:: {% for item in attributes %} ~{{ name }}.{{ item }} {%- endfor %} {% endif %} {% endblock %}tomobase-v0.0.0.dev1/doc/_templates/autosummary/module.rst0000664000175100017520000000214515237630637024664 0ustar blagandzigblagandzig{{ fullname | escape | underline}} .. automodule:: {{ fullname }} {% block attributes %} {% if attributes %} .. rubric:: Module Attributes .. autosummary:: :toctree: {% for item in attributes %} {{ item }} {%- endfor %} {% endif %} {% endblock %} {% block functions %} {% if functions %} .. rubric:: {{ _('Functions') }} .. autosummary:: :toctree: {% for item in functions %} {{ item }} {%- endfor %} {% endif %} {% endblock %} {% block classes %} {% if classes %} .. rubric:: {{ _('Classes') }} .. autosummary:: :toctree: {% for item in classes %} {{ item }} {%- endfor %} {% endif %} {% endblock %} {% block exceptions %} {% if exceptions %} .. rubric:: {{ _('Exceptions') }} .. autosummary:: :toctree: {% for item in exceptions %} {{ item }} {%- endfor %} {% endif %} {% endblock %} {% block modules %} {% if modules %} .. rubric:: Modules .. autosummary:: :toctree: :recursive: {% for item in modules %} {{ item }} {%- endfor %} {% endif %} {% endblock %}tomobase-v0.0.0.dev1/doc/api.rst0000664000175100017520000000014115237630637017417 0ustar blagandzigblagandzigAPI reference ============= .. autosummary:: :toctree: generated :recursive: tomobase tomobase-v0.0.0.dev1/doc/img/0000775000175100017520000000000015237630637016674 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/doc/img/tomobase-lockup.svg0000664000175100017520000000150215237630637022517 0ustar blagandzigblagandzig tomobase tomobase-v0.0.0.dev1/doc/img/tomobase.png0000664000175100017520000002236115237630637021217 0ustar blagandzigblagandzig‰PNG  IHDR\r¨f IDATx^íÝ ”$Eµ0à{³ª§§gº*#ž²É&² ¨�å�ì(¾ãsgq.¸à†ëÿEqWEåwå¨(²¸‚•EE”M•Å'‘U=ÓÓÓ•qÿsçUÇiz*3«{º«#³îwNŸê¼9=™Yy«*3â‚bháÐn¹B€ÃL>1Ä$1Ä$1Ä$1Ä$1Ä$1Ä$ ¥Ô!ˆx LDÛ"â#€Ø?‰èADü\Í?Æ~'  ºVÅq|\EÏ€ã`Õ<7u \êœûF’$߀u~�¨ IÕ3Çñëñmˆ¨cóˆè!"ú@’$çJ"¨IÒl6�¯ÕjgÀ–K´Y÷;ç^Ÿ$É7|D”š$€jUJ�‹ˆ/ÐæœoŒy=L èùÄ‘Prccc[�ŽŽþ÷ä¦Ño§§§�]³fÍßù¼bqI(1­õ£‰è爸Ýrlݹ~ýú[»ví}ËñübÓI()¥Ôöˆø Øf6áæîãÝÇy#¢{àPk-?Š’‘PBZ똈n@ÄçÑü„ˆ.pÎ]W«Õn6ÆÜä×ðÿù¸4M÷ˆ¢è‰ðDT~e"º÷7Æ$>(JA@ù Rê D<¢Ï¦ßìœûD’$ÎãßJ|AEo€Ý|4]i­=ÊD)H(­õ‡à­ý4;MÓZ­ÖW}`´Ö'À—} ‡sîýI’ü—ˆàI(‘F£qP½^ï§‹n’¦é±­VëZÙ�FãÀz½~ÿêƒÙ1Æðµ Q’Êc¥Rê–>®ø?Ðétm·Û·ùÈ"ßsddäò¢NFDt·µö±0íƒ"X’JB)u"¾1¯¹Dôç©©©C'''ïõÁE466¶ÍèèèOñ1>ØÛGŒ1}}MËK@ (¥¶CÄ»‹šJDÛ/õí¸n[n€•>ØÃ Ú"6�$€ÐZŸ'4õµÆ˜Où¥%¤µ~-|Òz;Ïój¿$‚$ p�FãµZí>D\‘ÓÔkŒ1û¥ÐZó…¾Ìç$¢õišnÝn·ôAI�ÓZÞœÓL·¿»1æ/>2Ü ø«ÀXÎÓ}Øó6¿$‚# `Üã�‡àΜdDä[‹Ö¡Ëh[›ˆm­µ3–°^Eâa”RïFÄwù@ ÑéÖÚ÷–°éCA@¸ø¾ÿý3}òù6´wý~‘±Ön5�nÈb€$J)õBDüâLóÊ”z´õ$cÌÅ~IC@ ”Rß@ÄgÏ4¯ÇI¬mýŠ1æD¿$‚QŽWÔð‰´ÖW؃Õ#$ƘáñB>"‚ @qEѳ›Æ' ýS@V;‰è0kíO}@A@€”Rç âëz5­{‚]¿›‰b_ø÷¹'þ ":ËZ{ªˆ ô>ZbYi­¹Æ^樻ééé='&&fÊyA)µ7"þ6«1Dt—µv>ŒÄHÌøøø###ÈiÖ}Ƙ­ýR@´ÖÍ«QØétvm·Û·ú€Xv’£µ~;| «YDô)k-Æ ŽÖú3ðʬ†9çÞ’$ÉG}@,;I�QJ}Ÿ–Õ¬4MŸÒjµ¸:Op´ÖÜîïe5Œˆ.±Ö>ÃIJ“¥WØÝin³º·ÖÖ9瞆ˆ©_"ª#⥰"ãbà-Ƙ¾ŠŒŠÁèy”IJÑZ¯ŸýìY·ÕB׫ÝD”ZkyX³ôD¹^Uǵùà÷UÞÌéééÝ'&&þTåm,I‰ãø9Q}}¦IÝ�ýµpþæn=ÓZû˪ܯ®ŠQJ�ŽˆgÌlÖÜ“§ŒzlÃcÞï—IJ*÷««b´ÖÈ÷Ú}0@ˆØIÓô´Z­vLVóŒ1M®èbÙHÄØØØ¶+W®Ì¬ìKDçZkOñ�€Íc055µÕÚµk¹Ø©Xf’Ñh4v­×ëy÷Ç?hŒy‡_ XQ)ó4MwnµZwø€X6’Çñ~Q]—Õ"z§µöLXQ5c"ÚÇZZ=ƒ¡$ J©CñgYÍ!¢7YkÏö�€Åqü–(Š>œÕÄN§sP»Ýþ¥ˆe# Ífó)µZí9͹”o¡ù¥€!âà)YMtÎ�$ÉÃJž‰å! s»W™t‡$€@(¥^ŒˆŸ¤9KíƘ/û%±l$Bký87�æ,)çÜÉI’œïbÙHDǯˆ¢h(N çÜ+’$ù\ »~¨IDÇGGQô£@š³¤œsG&Ir•ˆe# +´ÖwO¤Ye1Æì «|$–‡$€€h­÷€Ó€‹k–rZ°k»CßgŒ¹ÉGŲ’¨F£qPEÏ@ÄRϦCDuÎ]Òn·¯ tW5I�ŠãxÇ(ŠxöŸ•�6±_\Éx�$Iîô IaB¥Ôoq¿0›7?Dôkí“øWA� ­õÁð‹›¶`�Nç`ùIŠãøÍQñ�ÚÊ ¢S­µgUfƒ*B@€”Rß@Ägç4íµDÔìÀ°"ž3ÓÀ¾fŒ9Þ/‰ H�RêvD|LFÓþdŒÙÝ/Dk} ìÒ«IDt›µ¶ç:±|$Hk}<ºWÓˆèkm�•R×#â¾¾±Ç€¶óK"’$ @ Š$€Iƒ" @’Ä H�$1(’$ @ Š$€Iƒ" @’Ä HЦ$­õ¿;çvGÄE­'@D“QýÑÃcú{’~å# @J©;q‡^M#¢­µu¶‰ãx'D¼ —´³ ñ”^GYkïöÁ.¥Ô ˆøx˜…ˆî²Öîè"’¤”º�ÌjÚôôôž  ”ºŸîKˆˆ¾e­}ØX¥Ô>ˆx£ÌADWZk�òI*š\³¤>jŒyKIÛ^Y’¤µ>. °i›â$cÌÅ~IA@˜ÆµÖ·À£ÂlÞ¼=`ŒáÑ�k|DA@ ª4W sî9I’|3Ð]=Ô$L)õcD<,à&öãGƘcý’Š$€€­Zµj«ÑÑQž1ø €›™çê©©©ç®]»ö~A‘¾(ŽãSñLD\~s7Üò›"¢Ó’$ီ mV’J¢ÛÑç"Úyj­GÎjúè—6ÆïÀ·ù¥þ<¶ôKû%wIðKÿà²_ˆx;ý\æ(IÐh46«×ëÿÈÙ”Ïc^á—ú µþ,¼Üæèt:�l·Ûú€(%I @,”$€ � J@H %  $ˆ…’P’ÄBI¨Ib¡$T€$±P’*@€X(I @,”$€°5ÆÆÆâZ­¦1®×ëŠËÿq®Ù�D´9">#kSˆh[DÄ]†G‘WûðÖwí�FÄi"j"â¸_;}¹÷¡€dÖcÒét,%išÚÉÉI޵ýŠ HX£ÍfsÛ(жrÎm�ˆþ¶"¢ �ýþ!"@Ì>”Eë{)ú›¢õ³Ñz¸ï€{‰hãsîÞ(Šîãå$IþSþ�Ä@ôwż­ZµjË+VìBD»DQÄÕpö€mùäFD~ç^4E'cÑú^Šþ¦hýB‘é& N79çþŒˆ·LOOߺfÍšü?‹fq�àðY¡µÞÅ9·+"î~º¿7µ;ŠNÆ¢õ½ýMÑú%�Ñ­p+"Þꜻ%MÓ['&&x”# 0Ð#Xrü±ýñµZm"Úù‘OüÚroWÑÉX´¾—¢¿)Z?(D”ò§"º¯KÓôºV«ÅåÉåëD–ÿ†iE³ÙÜ»{²ó,<ûÀˆX±¹E'cÑú^Šþ¦hýr"¢ð¼ ×qbàÇ$I~/Ÿ6漨Ùl>¡V«ADGp .D|3¦èd,ZßKÑß­Ð:"º†gOJÓôÊV«u½T+â0>>¾ûÈÈÈáDt8"òI?°ïì‹­èd$¢µð ¿3"bÚý؜Ϊè3BD5þ:Ó}äO:›!âªîú�=g ðmQžJíªN§sU»Ý¾¥m^t¥>‚ó1>>¾ùÈÈÈ‘DtlwÚ­¼rW‹ŠO–,|õXÏ''‡å‹[DD7ü£î‰u�]Ä'­s®EÑÊ™?žk!'kÑß8çÖEQÔádÒ}'�iç†õD„³ÚÉ·3ùÕî—dl»—÷üKà~ž¾ /›žž¾rbbâýš è4­uìœ;>Š¢,EeݼˆMQ”™}<ÈdÁ™ ¯qÎ]EÑ×�1Ü™©’–ÿ/¾¥ÔqÀ'ýÓúíL3[Oì¼6o]–¼¿É[·\–ê˜q…cà)Ñ¿d­ýáœB¨¥7ÿ=¨F£q`­V;žßoG›¬ÍB^(Ã$ÄÐ�M=ÞDÄÝ�¿ìœ»¨Õj]ëW”X[.~·?O€Çe5sö�çƒ]Öp‘M}�Ï5óÿÍì³ ýÿBÖkßåm'ý>f­ý Iö+J&{ ÃÖŒãøUQ½ŽûÌsSgNê¹2ï QdîëiƬ×�i8'I’ó 5,‹R�cccÛŒŽŽò»ýËøÂþì�,'º¤‰a>;55uöää$�e(…R$€Õ«W522òØ›÷jUOöî`~!ñÐ\žJ›Öw‡çÎ Õ]?gØ.ß*œù�/Xñ}þ Ãqþ·ü7ü»snÿãçÜú(ŠÖñþtέŒ¢hæŸ(Š6 &¢Ý¡ÃâÝ ªÜO`´;´xÃúÙÃŒùofý¾áßðÐcDäáÇ«‰hu)ÎŽyš•nœžž~ëš5k®œ „*øÐl6Ï©ÕjüQ¿,&‰èïˆø=Ð}ä{ÌÿË¿#"wÈY“¦éšN§³fåÊ•U¾Í”G)¥¦¦¦V×ëõÕµZmC’ ¢ÍˆèQˆÈ5xhôˆø¨nìQ�Ùß!4išžÝjµÞZ»f :¬^½úQ+V¬bfÙî»3ŸÈÜžûyØ*Ÿà¼ÌcÚ;�ÎßÛí6Y-Ý÷À2á¾�Ng‹z½¾…sŽk&ðÏÝ [v“?öu'h©…>…ZÐ ŽãWFQô¿7—Ý wñ"þîît:wGQt’$%Çñœ$êõúv°=í;ÀŽˆÈ±%çœ{y’$,ù-PÐ  ÑhœZ¯×?ê[»‰ˆˆOä›ñf.6Ñ=áï”™l‡ϸ<“¢(Ú‘‹¶�úÜ~±öHš¦olµZ÷�À„žª×ëWÏgŸq7N¸‡Or>Ù‰hÃO’$€Iÿ…ȶ*ŽãÝ‘“Á<œlÇü¿êC§Ó9°Ýnÿ�f^³ V*¥&ûØçÜ+ë§pµ1æò=\,¾þÀ¡p<ɯì�ß‹¬µ…/ÞåtãXÇwó÷µ™$0çþëƒÖÚm`Ãí,!Œß øžÿ#øyg¿Qu_§w[kù+F°‚OJ©Ëñ¨¬=hŒáa¦RN,‡ÕZkî·‘åGƘcýR€Ê�ÎEÄ×dí;çÜ~I’ÜàB H·ŠÔ¯³žŽˆ>i­ ºKÀ)ˆø ¿WçHÓôÄV«Å2„(­õóy˜pΓ¾Öó)¿ à@³Ù<¦V«]–µïˆèÝÖÚ3|@ˆQJ½OËz:çÜQI’Ý8ø ”Ú3;âÑ%ÖÚÌi±„X*J©ï!âÓ|`ŽuëÖ=zrrò¯> àÓZs!¾³"â;�ô!DkÍݾO×2Æô|͆¤, àGptÖŽsÎí(ÝuÅ 5›Í�kµÏJ”åÆ.M´R$¥ÔˆxzΞ<És±_b‰)¥^ˆˆ_ô�9ˆètkí{} P¥HÍfó©µZíÒ¬}HDçZkOñ!–˜Öú<8ÙæpΓ$Éå>¨R$ž´Ck�Yš™ˆ®·Öòô]B „Rê·ˆ¸¡@M/Æžh¦í�*Kà~"îÜk?ò„ÖZ®8#]‚Å Œ*¥Öv'>éåOƘÝýRÀÊ”.DD®õßj­ý¹±Dâ8><Š¢«|`"ú¼µö¥>°Ò$­õ« ³W•tƒÇñ™QýWÖó9ç^™$Ég} `¥IqïEÏèÚýÆZûDb‰(¥n@ÄÇûÀÆö6ÆðtäÁ+M`‚(MÓÍÛíöƒ>(Ä"k4�¨×ëy¯±ÄD=Â~”-|žç[?½ÈZ{¡±È”R/EÄÿö�9ˆè"k-*…R%€¢Îð5cÌñ~IˆE¦”ú&">Ë6VªNi¥J�Fc³z½þ¿«7Æ¿þ…¯Ãøˆ‹'ÒZó½ýU>2  µÖòÇÿÒ”†/U`J©ß b^§ŸCºu…XTJ©ÃñÇ>°±k�1ø¥(c(ðAcÌ;ü’‹DkÍÓÓ½Ùæ(KÿÿÙJ—šÍæ“jµZæÜìDt‡µ¶g�A!6…ÖšÇöoãs8çöO’$óVuˆJ—XÞí@VÆ!ÂÖÇO)ëR”2(¥.@Äÿô{":ËZËÓˆ ±(”Rç b^�Ïó�1™£CUÊÇñQeÖZãÙy­µ<“¬‹!RJñ„°›ûÈDôdkíÏ| $J™˜RЧÚÞ"k?ÑáÖÚŸø€ ÇñQQeŽíç©ß­µ™¯Å�•9|_Ÿ³s?gŒy…_b�”RŸGÄûÀDô1kmæÝ��•6ôqQÆXkù#[Ç…˜¿ºÖÚÀ¸�ÌQæ‹Î¥MLkýà¹{"¢§[k¿ëBÌSÇÏŠ¢è›>0Ýi­åiÆK©Ô  h\6}×Zûtbž´Ö\‹ò©>0�a­}·”LÙÀNQÝ‘µÏ¹TØÔÔÔö¡OÎ Â466¶Íèèè_0g~ú²—¤Ïܰ²PJý÷Êj¯sîI’ü?¢OZëÀ[|`"ºÁZ»Ÿ”PÀëñãYûžˆþÙ½E“ú ÅVtïýóèÒ,ÁOþY¤ô €‹´h­È¢Ùõ|cÌE~IˆJ©!â|`ck�1›À¤�”PT;òîùÿÒs�_¢€Rê׈ø˜Ã9÷é$I^ã%U‰0>>¾ûÈÈÈÍyÇ`zzz�‰‰‰?ú€”Rû â�>Ѓsn§$Iîô�’ªD`Zk.rpÖq ¢ ¬µ/÷!2ôÑóï§ÖÚÃ| Ä*“šÍæñµZí+YÇ‚ˆÖ§iºµT y¸êo­V»Wøàιç$I’Ù9¨L*“ ¦µþä�”jA"WUx¤é–ü«–X•t{"¾3çx´�1[À„�Ñ¥µæ"3÷ÀXÖN!¢wYkßã%W©ÀUƒkµÚ½yßà4cÌûü’]J©w!b^·ÞÉN§³m»Ýþ§�”\¥ÓZ^•u\º£ùS€Ì$,f[Ùíø“9«}ÒZ›W¨t*—”RÛÀ�9S7ó�|ƒµöCO)u*"~4kGQgzzzÛ5kÖp§³Ê¨\`J©/!bÞôL÷c-݃E׈RНüsϾžˆè ÖÚ—ø@ET24›Í�£(ºµ`×Ë’$ÉœãM �8ŽOŽ¢è¼¬-æœs�mµZ™#OË*ó);¥Ô%ˆ˜Y €ˆî¶ÖîÈ¿ú F¨”º ù«cODô-kí³} B*›â8Þ/Š¢ëò޽ÄZ›7àCT\Ç/‹¢èsy›Yånä•ML)u"Þ=޽üÍó˜ò1LF•R\ð#³Ü7\nŒ9Æ/UL¥@ÇGFQtEÞ1sν=I’ù€Zk.'wfÞÑaÖÚŸú@ÅT:°¢AB<¥¸sn‡$I¸ò«që(Šx®¿Õ9›\ùaä•O}^ 8ÛZû&•§”ú"ž’·¡D´¯µ6wXpÙU>0¥Ô·ñ™Y‹ˆ¦§¦¦v˜œœ¼×Eeu;‹ýk9ùucÌóüRE Eh4�­Õj,8àcNòK¢²´Ö_€çfm qýÈ�¬µ÷ø`E E`ZëÏÀ+óŽãôôôã&&&þà¢r”R�GÄò6Œˆ>e­}­TØÐ$€ñññÍGFF8£¯Ì9ž×cò.Š’SJý÷ÏÙŒ5ιm‡å¢ðÐ$¦”z/"žÖ=ÐY¤‚pEõQé—ßýßm­=Ã*n¨—WJý5¯Ö;=h­ÝAІTÎx·ËoÞ€žæ›�ýZ¬¸aKü.ð:DÌ LDgYkOõQzJ©³ñ òc דC—ºÓ=ó…¾]²Ž2_NÓt�v»}«ŠÒêó.ЭƘݹs¨� �aLü‚8 ^¯ÿ²àøÊÁŠÐZ_æmNš¦´Z­k}`H e`J© ñyÇÙ9w|’$|ÏX”TQ¹xFDŸ·Ö¾Ô†ÈÐ&®ÿ^¯×ïâRð9Çû~cÌNeŸÿmˆ­ÔZó1Î,OD–gùiµZùàÚÀ´Ö\<´è¢Ï‡Œ1o÷K¢4´Ö€·å5Ø9÷ò$I.ð�!3Ô €u év ÝÏZû;ÁÓZïED7‡½ÞZ›yì‡ÁÐ'€ñññ=ëõúïóêÀ�1{IÑÒàY¢n€Ý²Z̉½Óéì=11‘;©lÕå½è‡FÇŸŠ¢èÕyLDïµÖžî"XqŸEûÈDDŸ°Ö¾Þ†”$€î”PDt[Ai(~ÑT~|xÙÅq¼oE×lÇƘ�¥·'H˜Ñl6ŸZ«Õ.õ�Þä«@Ø ?ú³4MŸÒjµ.ó�!&ŸfQJ}skÑ{¬µïòŒ>?úÙZ›Ûÿc˜Hx¸¦Rêö¼¯8ä$Ì!_JI>ú/�$€´ÖÀ >ÐÛ�1ïðKbÙh­?oÎk]d­Í›/r(Iè�KF#âu5ÆpÙq±Lâ8><Š¢«òž^>úg“�A)õ Dü¶ôöíf­µ>"†ÇsÔjµ[òŠ|0¹êŸM@Ž¢ê±]—cžæ—ÄÀ(¥®DÄ# žPª=ç��£Ÿ¯]§cÎõKbÉ)¥Þ€ˆgç=ô'¢]‡¥ÀçBH(ÐÏ]"Zßétöö~åƒ2>>¾{½^ç[~+òžÓ9wD’$?ö±I}PJ�ƒˆ¯ó�¸+±µ– ÉLÃK‹gô½ ¹+o&©ëØIýÑZód{úHoçcNöKbÑ)¥>‡ˆ/ó�ˆè·Ýa¾<”[ä�Чf³¹s­Vã‰"óf“å+ÎǵZ­ø€X4J©ÿ@Äïø@ok‰h÷a˜Ök1H˜¥Ô‹ñó>ÐO7¾o’$wúˆØdqïEe÷Á¤ŽãüH˜'­õEp¢ôv‹1f_©%¸hVk­yŠ÷]}¤"ºÐZû"…$ÌÏ.ôDäd2Ñ·­µÏò±`ZëïÀq>ÐÝm­åºþ“>( IX¥Ô>Àµë>؃sî­I’p?u±@ZkÞ{¦ôÀ·aà‰2Bsþ$,�Öš§�þ¤ô@DŸ,ã&Žã#ñŠ‚z�œh_•$ Oÿ.æ)wÇŠ|ý\ ¢‡¦¦¦ö™œœü«ŠBÝ‹~|ë5öÁÞ¤«ï&�°iVt/N=ÎGz»ÉÃ÷¥ù£ª(6ÖíwQtÑïkí²_NÀ&ÛvåÊ•\‡NÞ©‰Rê[ˆøLè�?Y­_¿~ϵk×ÞïƒbÞ$,‚îwÕËóÊQ1çÜÛ“$ù�ˆ�(¥NGÄ3| ¹¶²x$,­5Wz¿ôÀEDàÙÖÚ¢:C©ÙlžP«Õ¸S.¹»²x$,¢>ïWOÑAI’Õ®*Ífó_£(ú9"Žäm¸ô¯X\’W¿=ÖìV¾Ç‡˜Rj;àὺ`7HËE& `‘õÛg�ˆn·Öî7ì%ª•R ~]4¼—gñqÎí-c,—$€%Çñ‘ˆø£¢‹‚Dô3kí‘Às £ºÖúçÀ·ò2ñDžDtL’$¹Å?ÅüIX"qŸEÑy>�mh;²ôÓ‘ŠÑ‹¬µú€X4’–�Rê,D|£d;Íó>¿4”RïFÄ~¦Xûˆ1æ­~I,*IK •RßEĪÁišžÐjµ¾ê¦”z"~¡�Mü�1&÷®ŠØ4’–ÞJ¥Ô¯‘ëfâ﹈øtcLnÒ²SJq¿oôq}„»ùë|P,:I°zõê-V¬XÁ··É{ºî°Ö£­µ?óÁ QJ \Ë?w5Ý“¦é~ívûŸ>(–„$€i4»Öëõ_qyÁ‚§\ËWÅ�1¿÷‘ ˆãxÿ(Š8±­*ØœV·¤ÚŸ}D,I¤”:~Œˆµ¼§%¢:çhµZ·û`‰uëø_ƒˆ|Ï?MóL?R?ap$ X³Ù<>Š¢‹‹Š\À}ëÖ­{Òäääß|¤6ø�IDAT„ÆÆÆ¶½7Ïk¾Œ“XE/B±â8~eE…lˆèNçÜZ­ÖC>X"ããã�¬×ë×"âŽEÍvÎ�œ$Éù> BÀ2é÷>8ýÎZ{0w…õÁÐZs}„k`�¢æÑ;­µ¹uÿÄÒ�°Œ´ÖÜS°Ÿ™„~eŒáYp×øHØV+¥xd—FÏEDçXkßàb $,3­5�?¡�f”% ðˆHî³ÿ$ßòl_1ÆvKGÀò‹´Ö?äûÿ}4%ô$Ð÷ÉODß·Ö>�¿þû 8IaÕZó4ÖöÑœP“À|NþŸZk9áMû X’Â1®”º÷î£Iœ hœùœü|Q“‡ÿÊ >��8Žu··\Q™qvµ1†ßE—ûDêûäçòèιC“$1>"–•$€Àt“À/‹ÊŠu-w�“¿ä$¨ÑhlV¯×x�“¿$jžIàÚî×�¶�,!îäCD?AÄÇû`6ùØ0I›O ¢ßw:�#'&&þáƒK€»÷ŽŒŒp¿Â6q_çÜ�ò�?\’7Ï$p×ÔÔÔ¡K5iw`Ï/q{ÌvK§Óù·v»Í%ÐE $”À|’�"LÓôÐV«u‡�,‚F£±K½^ÿ léƒÙää/ I%ÁI V«ý">¦¨É\O€ˆŽN’„gØÝdJ©}€¿óçŽçï’“¿D$”Hwx-—ÔÊ­/ØÅ‡Ž5Æ\í# Ðh4¬×ë—ó¬G>˜í¦4MŸ\ÖáËÃH@ù4´Ö|BþkQÓyBçÜ´Z­Ë|p´ÖÇOÕ=ꃈè×ÖZ±XªaËÃN@9q¥áïsù¬¢æQ‡ˆNH’ä›>؇8ŽŸ‡ˆ\¹(·zo×寨#|KF@yÕ•RßäRâE›Ð-·õkíÇ|0‡ÖúíDôþ>Ê–ñÿýmkísyj¥Qx€EÐxâ‘/!âI}¶òòÆ´Ö߀c|$�k­=ÅD)I¨¥Ô;ñ=ýl]—¦éSf:éŒ��o^¯×¯èó «J@…h­_Ÿîg“º³ïƒˆ®V«qŸ­ýÊ |-�ˆ^‘$É>(JM@ÅÄqüÜ(оÖçfÍ jtsѳ¬µßöQz’*ˆg BÄïòÝ¼ÅØ<"zÈ9w\«ÕºÖE%H¨¨ñññÝFFF®èç£}�¿t:�£Ûíö­>"*C@…ñ¬Ä###—!"÷åŸ7"úmš¦GɈ¾ê’P}cJ©ï"â‘óÜT¾]ø¬jŠ%$ `Hh­? /ïss?cŒá; ¢â$ ­õ;àýY›Ü½Í÷Ž$I>䃢Ò$ ­õ‰DÄÝ}Gfo:Mщó4$ÊMÀj4Ôëõÿ€Ýº›ÿ§4M_Òjµ~5„»c¨Ibããã»#b§Ýnß6Ä»a¨IbˆIbˆIbˆýª±ââŸcdeBG7A1C3C5559EA248FMõÊIEND®B`‚tomobase-v0.0.0.dev1/doc/img/tomobase.svg0000664000175100017520000000134015237630637021224 0ustar blagandzigblagandzig tomobase-v0.0.0.dev1/doc/index.rst0000664000175100017520000000064015237630637017761 0ustar blagandzigblagandzigtomobase ======== Common base classes and utilities shared by `tomoscan `_ (tomography scan reading) and `volio `_ (volume load/save): the ``TomoObject``/``BaseIdentifier`` hierarchy, ``Series``, ``Source``, ``Progress``, ``CoordinateSystem``, and shared IO/geometry/deprecation utilities. .. toctree:: :maxdepth: 2 api.rst tomobase-v0.0.0.dev1/doc/conf.py0000664000175100017520000000313315237630637017417 0ustar blagandzigblagandzigfrom importlib.metadata import version as get_version extensions = [ "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.autosummary", "sphinx_design", "sphinx_autodoc_typehints", "myst_parser", ] templates_path = ["_templates"] source_suffix = { ".rst": "restructuredtext", ".md": "markdown", } master_doc = "index" project = "tomobase" copyright = "2026, ESRF" author = "ESRF" release = get_version(project) version = ".".join(release.split(".")[:2]) language = "en" exclude_patterns = [ "build", ] pygments_style = "sphinx" todo_include_todos = False html_theme = "pydata_sphinx_theme" html_logo = "img/tomobase.svg" html_static_path = ["_static"] htmlhelp_basename = "tomobase" man_pages = [(master_doc, "tomobase", "tomobase documentation", [author], 1)] autosummary_generate = True autodoc_default_flags = [ "members", "undoc-members", "show-inheritance", ] html_theme_options = { "icon_links": [ { "name": "pypi", "url": "https://pypi.org/project/tomobase", "icon": "_static/navbar_icons/pypi.svg", "type": "local", }, { "name": "gitlab", "url": "https://gitlab.esrf.fr/tomotools/tomobase", "icon": "_static/navbar_icons/gitlab.svg", "type": "local", }, ], "show_toc_level": 1, "navbar_align": "left", "show_version_warning_banner": True, "navbar_start": ["navbar-logo", "version"], "navbar_center": ["navbar-nav"], "footer_start": ["copyright"], "footer_center": ["sphinx-version"], } tomobase-v0.0.0.dev1/pyproject.toml0000664000175100017520000000404515237630637020272 0ustar blagandzigblagandzig[project] name = "tomobase" version = "0.0.0dev1" authors = [ { name = "Henri Payno", email = "henri.payno@esrf.fr" }, { name = "Pierre Paleo", email = "pierre.paleo@esrf.fr" }, { name = "Pierre-Olivier Autran", email = "pierre-olivier.autran@esrf.fr" }, { name = "Jérôme Lesaint", email = "jerome.lesaint@esrf.fr" }, ] description = "Common base classes for tomography data objects" readme = { file = "README.md", content-type = "text/markdown" } license = "MIT" requires-python = ">=3.10" keywords = ["NXtomo", "nexus", "tomography", "esrf"] classifiers = [ "Intended Audience :: Education", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Environment :: Console", "Operating System :: POSIX", "Natural Language :: English", "Topic :: Scientific/Engineering :: Physics", "Topic :: Software Development :: Libraries :: Python Modules" ] dependencies = [ "h5py>=3.0", "silx>=2.0", "numpy", ] [project.optional-dependencies] test = [ "pytest", "ruff", ] doc = [ "Sphinx>=4.0.0, <5.2.0", "pydata_sphinx_theme", "sphinx-design", "sphinx-autodoc-typehints", "myst-parser", ] [build-system] requires = [ "setuptools>=61.0", "wheel", "numpy" ] build-backend = "setuptools.build_meta" [tool.sphinx] source-dir = "./doc" [tool.ruff] line-length = 88 target-version = "py310" [tool.ruff.lint] # `select` (not `extend-select`): pin the rule set explicitly so a new ruff # release changing its defaults cannot break the CI. select = [ "E", # pycodestyle errors "F", # pyflakes "I", # isort "S", # flake8-bandit ] ignore = [ "E501", # line length: `ruff format` is the authority "S101", # assert ] [tool.ruff.lint.isort] known-first-party = ["tomobase"] [tool.setuptools.packages.find] where = ["."] tomobase-v0.0.0.dev1/.gitignore0000664000175100017520000000012115237630637017335 0ustar blagandzigblagandzig__pycache__/ *.py[cod] *.egg-info/ .pytest_cache/ build/ dist/ doc/generated htmltomobase-v0.0.0.dev1/README.md0000664000175100017520000000140215237630637016627 0ustar blagandzigblagandzig# tomobase ![tomobase](doc/img/tomobase-lockup.svg) Common base classes and utilities shared by [tomoscan](https://gitlab.esrf.fr/tomotools/tomoscan) (tomography scan reading) and [volio](https://gitlab.esrf.fr/tomotools/volio) (volume load/save): * the `TomoObject` / `BaseIdentifier` * `Series`, `Source`, `Progress`, `CoordinateSystem` * shared IO, geometry and deprecation utilities ## installation ### using pypi To install the latest 'tomobase' pip package ``` bash pip install tomobase ``` ### using gitlab repository ``` bash pip install git+https://gitlab.esrf.fr/tomotools/tomobase.git ``` ## documentation General documentation can be found here: [https://tomotools.gitlab-pages.esrf.fr/tomobase/](https://tomotools.gitlab-pages.esrf.fr/tomobase/) tomobase-v0.0.0.dev1/LICENSE0000664000175100017520000000236215237630637016363 0ustar blagandzigblagandzig The tomobase library provides common base classes shared by the ESRF tomography tools (tomoscan, volio). tomobase is distributed under the MIT license. The MIT license follows: Copyright (c) European Synchrotron Radiation Facility (ESRF) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tomobase-v0.0.0.dev1/tomobase/0000775000175100017520000000000015237630637017164 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/tomobase/geometry/0000775000175100017520000000000015237630637021017 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/tomobase/geometry/shape.py0000664000175100017520000000062315237630637022472 0ustar blagandzigblagandzigdef get_subvolume_shape(chunk, volume_shape): """ Get the shape of a sub-volume to extract in a volume. :param chunk: tuple of slice :param volume_shape: tuple of int """ shape = [] for c, v in zip(chunk, volume_shape): start = c.start or 0 end = c.stop or v if end < 0: end += v shape.append(end - start) return tuple(shape) tomobase-v0.0.0.dev1/tomobase/geometry/bounding_box.py0000664000175100017520000000512015237630637024044 0ustar blagandzigblagandzigimport numpy class _BoundingBox: def __init__(self, v1, v2): if not numpy.isscalar(v1): v1 = tuple(v1) if not numpy.isscalar(v2): v2 = tuple(v2) self._min = min(v1, v2) self._max = max(v1, v2) @property def min(self) -> float: return self._min @property def max(self) -> float: return self._max def __str__(self): return f"({self.min}, {self.max})" def __eq__(self, other): if not isinstance(other, _BoundingBox): return False else: return self.min == other.min and self.max == other.max def get_overlap(self, other_bb): raise NotImplementedError("Base class") class BoundingBox1D(_BoundingBox): def get_overlap(self, other_bb): if not isinstance(other_bb, BoundingBox1D): raise TypeError(f"Can't compare a {BoundingBox1D} with {type(other_bb)}") if ( (self.max >= other_bb.min and self.min <= other_bb.max) or (other_bb.max >= self.min and other_bb.min <= self.max) or (other_bb.min <= self.min and other_bb.max >= self.max) ): return BoundingBox1D( max(self.min, other_bb.min), min(self.max, other_bb.max) ) else: return None def __eq__(self, other): if isinstance(other, (tuple, list)): return len(other) == 2 and self.min == other[0] and self.max == other[1] else: return super().__eq__(other) def __hash__(self): return hash((self._min, self._max)) class BoundingBox3D(_BoundingBox): def get_overlap(self, other_bb): if not isinstance(other_bb, BoundingBox3D): raise TypeError(f"Can't compare a {BoundingBox3D} with {type(other_bb)}") self_bb_0 = BoundingBox1D(self.min[0], self.max[0]) self_bb_1 = BoundingBox1D(self.min[1], self.max[1]) self_bb_2 = BoundingBox1D(self.min[2], self.max[2]) other_bb_0 = BoundingBox1D(other_bb.min[0], other_bb.max[0]) other_bb_1 = BoundingBox1D(other_bb.min[1], other_bb.max[1]) other_bb_2 = BoundingBox1D(other_bb.min[2], other_bb.max[2]) overlap_0 = self_bb_0.get_overlap(other_bb_0) overlap_1 = self_bb_1.get_overlap(other_bb_1) overlap_2 = self_bb_2.get_overlap(other_bb_2) if overlap_0 is not None and overlap_1 is not None and overlap_2 is not None: return BoundingBox3D( (overlap_0.min, overlap_1.min, overlap_2.min), (overlap_0.max, overlap_1.max, overlap_2.max), ) tomobase-v0.0.0.dev1/tomobase/geometry/__init__.py0000664000175100017520000000055715237630637023137 0ustar blagandzigblagandzig"""Geometry helpers used by tomobase. This package exposes simple bounding box types and small utilities for working with shapes and sub-volumes. The helpers are intentionally minimal and designed to be easy to reuse in higher-level code. """ from .bounding_box import BoundingBox1D, BoundingBox3D # noqa: F401 from .shape import get_subvolume_shape # noqa: F401 tomobase-v0.0.0.dev1/tomobase/tests/0000775000175100017520000000000015237630637020326 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/tomobase/tests/test_progress.py0000664000175100017520000000112715237630637023604 0ustar blagandzigblagandzig# coding: utf-8 import tomobase.progress def test_progress(): """Simple test of the Progress API""" progress = tomobase.progress.Progress("this is progress") progress.reset() progress.startProcess() progress.setMaxAdvancement(80) for adv in (10, 20, 50, 70): progress.setAdvancement(adv) for _ in range(10): progress.increaseAdvancement(1) def test_advancement(): """Simple test of the _Advancement API""" for i in range(4): tomobase.progress._Advancement.getNextStep( tomobase.progress._Advancement.getStep(i) ) tomobase-v0.0.0.dev1/tomobase/tests/test_identifiers.py0000664000175100017520000000073615237630637024252 0ustar blagandzigblagandzig# coding: utf-8 """test of the tomobase.identifiers module""" import pytest from tomobase.identifiers import BaseIdentifier from tomobase.tomoobject import TomoObject def test_base_identifier(): identifier = BaseIdentifier(TomoObject) assert identifier == identifier assert identifier != "some other str" with pytest.raises(NotImplementedError): identifier.scheme with pytest.raises(NotImplementedError): BaseIdentifier.from_str("test") tomobase-v0.0.0.dev1/tomobase/tests/test_shape.py0000664000175100017520000000057215237630637023043 0ustar blagandzigblagandzig# coding: utf-8 from tomobase.geometry.shape import get_subvolume_shape def test_get_subvolume_shape(): volume_shape = (10, 20, 30) assert get_subvolume_shape( (slice(0, 5), slice(0, 20), slice(0, 30)), volume_shape ) == (5, 20, 30) assert get_subvolume_shape( (slice(2, 8), slice(None, None), slice(0, -5)), volume_shape ) == (6, 20, 25) tomobase-v0.0.0.dev1/tomobase/tests/test_bounding_box.py0000664000175100017520000000446015237630637024420 0ustar blagandzigblagandzig# coding: utf-8 import pytest from tomobase.geometry.bounding_box import BoundingBox1D, BoundingBox3D, _BoundingBox def test_bounding_box_base(): bb = _BoundingBox(0, 1) with pytest.raises(NotImplementedError): bb.get_overlap(None) def test_bounding_box_1D(): """ check if BoundingBox1D is working properly """ # check overlaping bb1 = BoundingBox1D(0.0, 1.0) bb2 = BoundingBox1D(0.2, 1.0) assert bb1.get_overlap(bb2) == BoundingBox1D(0.2, 1.0) assert bb2.get_overlap(bb1) == BoundingBox1D(0.2, 1.0) bb1 = BoundingBox1D(0.0, 1.0) bb2 = BoundingBox1D(0.2, 0.8) assert bb1.get_overlap(bb2) == BoundingBox1D(0.2, 0.8) assert bb2.get_overlap(bb1) == BoundingBox1D(0.2, 0.8) bb1 = BoundingBox1D(0.0, 1.0) bb2 = BoundingBox1D(1.0, 1.2) assert bb2.get_overlap(bb1) == BoundingBox1D(1.0, 1.0) # check outside bb1 = BoundingBox1D(0.0, 1.0) bb2 = BoundingBox1D(2.0, 2.2) assert bb2.get_overlap(bb1) is None assert bb1.get_overlap(bb2) is None # check on fully including in the other bb1 = BoundingBox1D(0.0, 1.0) bb2 = BoundingBox1D(0.1, 0.3) assert bb2.get_overlap(bb1) == BoundingBox1D(0.1, 0.3) assert bb1.get_overlap(bb2) == BoundingBox1D(0.1, 0.3) with pytest.raises(TypeError): bb1.get_overlap(None) def test_bounding_box_3D(): """ check if BoundingBox3D is working properly """ # check overlaping bb1 = BoundingBox3D((0.0, -0.1, 0.0), [1.0, 0.8, 0.9]) bb2 = BoundingBox3D([0.2, 0.0, 0.1], (1.0, 2.0, 3.0)) assert bb1.get_overlap(bb2) == BoundingBox3D((0.2, 0.0, 0.1), (1.0, 0.8, 0.9)) assert bb2.get_overlap(bb1) == BoundingBox3D((0.2, 0.0, 0.1), (1.0, 0.8, 0.9)) # check outside bb1 = BoundingBox3D((0.0, -0.1, 0.0), [1.0, 0.8, 0.9]) bb2 = BoundingBox3D([0.2, 0.0, -2.1], (1.0, 2.0, -1.0)) assert bb2.get_overlap(bb1) is None assert bb1.get_overlap(bb2) is None # check on fully including in the other bb1 = BoundingBox3D((0.0, 0.1, 0.2), (1.0, 1.1, 1.2)) bb2 = BoundingBox3D((-2.0, -3.0, -4.0), (2.0, 2.0, 2.0)) assert bb2.get_overlap(bb1) == BoundingBox3D((0.0, 0.1, 0.2), (1.0, 1.1, 1.2)) assert bb1.get_overlap(bb2) == BoundingBox3D((0.0, 0.1, 0.2), (1.0, 1.1, 1.2)) with pytest.raises(TypeError): bb1.get_overlap(None) tomobase-v0.0.0.dev1/tomobase/tests/test_tomoobject.py0000664000175100017520000000050515237630637024104 0ustar blagandzigblagandzig# coding: utf-8 """test of the tomobase.tomoobject module""" import pytest from tomobase.tomoobject import TomoObject def test_tomoobject(): obj = TomoObject() with pytest.raises(NotImplementedError): obj.from_identifier("test") with pytest.raises(NotImplementedError): obj.get_identifier() tomobase-v0.0.0.dev1/tomobase/tests/__init__.py0000664000175100017520000000000015237630637022425 0ustar blagandzigblagandzigtomobase-v0.0.0.dev1/tomobase/tests/conftest.py0000664000175100017520000000040415237630637022523 0ustar blagandzigblagandzigfrom tempfile import TemporaryDirectory import pytest @pytest.fixture(scope="session", autouse=True) def changetmp(request): with TemporaryDirectory(prefix="pytest-tomobase-") as temp_dir: request.config.option.basetemp = temp_dir yield tomobase-v0.0.0.dev1/tomobase/tests/test_version.py0000664000175100017520000000020315237630637023417 0ustar blagandzigblagandzig# coding: utf-8 import tomobase def test_version(): assert isinstance(tomobase.__version__, str), "version should be a str" tomobase-v0.0.0.dev1/tomobase/tests/test_io.py0000664000175100017520000000324615237630637022353 0ustar blagandzigblagandzigimport os import h5py import numpy from tomobase.utils.io import cast_to_default_types, check_virtual_sources_exist def test_vds(tmp_path): h5_file_without_vds = os.path.join(tmp_path, "h5_file_without_vds.hdf5") with h5py.File(h5_file_without_vds, mode="w") as h5f: h5f["data"] = numpy.random.random((120, 120)) assert check_virtual_sources_exist(h5_file_without_vds, "data") h5_file_with_vds = os.path.join(tmp_path, "h5_file_with_vds.hdf5") # create some dataset for i in range(4): filename = os.path.join(tmp_path, f"{i}.h5") with h5py.File(filename, mode="w") as h5f: h5f.create_dataset("data", (100,), "i4", numpy.arange(100)) layout = h5py.VirtualLayout(shape=(4, 100), dtype="i4") for i in range(4): filename = os.path.join(tmp_path, f"{i}.h5") layout[i] = h5py.VirtualSource(filename, "data", shape=(100,)) with h5py.File(h5_file_with_vds, mode="w") as h5f: # create the virtual dataset h5f.create_virtual_dataset("data", layout, fillvalue=-5) assert check_virtual_sources_exist(h5_file_with_vds, "data") def test_cast_to_default_types(): """test 'cast_to_default_types' function""" assert cast_to_default_types( { "a": numpy.float64(25.6), "b": { "c": numpy.zeros(5, dtype=numpy.int32), "d": numpy.ones(4, dtype=numpy.uint64), }, "e": (numpy.float32(45.5), numpy.float32(5.5)), "f": numpy.int16(5), } ) == { "a": 25.6, "b": { "c": [0] * 5, "d": [1] * 4, }, "e": [45.5, 5.5], "f": 5, } tomobase-v0.0.0.dev1/tomobase/series.py0000664000175100017520000001114715237630637021034 0ustar blagandzigblagandzig# coding: utf-8 """Module with the Series class for grouping TomoObjects""" from __future__ import annotations import numpy from tomobase.identifiers import BaseIdentifier from tomobase.tomoobject import TomoObject __all__ = [ "Series", ] class Series(list): """ A series can be view as an extended list of :class:`TomoObject`. This allow the user to define a relation between tomo objects like scans or volumes. """ def __init__( self, name: str | None = None, iterable: ( list[TomoObject] | tuple[TomoObject, ...] | numpy.ndarray | None ) = None, use_identifiers=False, ) -> None: if name is not None and not isinstance(name, str): raise TypeError( f"name should be None or an instance of str. Get {type(name)} instead" ) self._name = "Unknow" if name is None else name self.__use_identifiers = use_identifiers if iterable is None: iterable = [] super().__init__() for item in iterable: self.append(item) @property def name(self) -> str: return self._name @name.setter def name(self, name: str): if not isinstance(name, str): raise TypeError("name is expected to be an instance of str") self._name = name @property def use_identifiers(self): return self.__use_identifiers def append(self, object: TomoObject): if not isinstance(object, TomoObject): raise TypeError( f"object is expected to be an instance of {TomoObject} not {type(object)}" ) if self.use_identifiers: super().append(object.get_identifier().to_str()) else: super().append(object) def remove(self, object: TomoObject): if not isinstance(object, TomoObject): raise TypeError( f"object is expected to be an instance of {TomoObject} not {type(object)}" ) if self.use_identifiers: super().remove(object.get_identifier().to_str()) else: super().remove(object) def to_dict_of_str(self) -> dict: """ call for each scan DatasetIdentifier.to_str() if use dataset identifier. Otherwise return a default list with dataset identifiers """ objects = [] for dataset in self: if self.use_identifiers: objects.append(dataset) else: objects.append(dataset.get_identifier().to_str()) return { "objects": objects, "name": self.name, "use_identifiers": self.use_identifiers, } @staticmethod def from_dict_of_str(dict_, factory=None, use_identifiers: bool | None = None): """ create a Series from its definition from a dictionary :param dict_: dictionary containing the series to create :param factory: factory to use in order to create objects defined from their Identifier. Must provide a ``create_tomo_object_from_identifier`` method. :param use_identifiers: use_identifiers can be overwrite when creating the series :return: created Series """ if factory is None: raise ValueError( "factory must be provided to reconstruct objects from identifiers" ) name = dict_["name"] objects = dict_["objects"] if use_identifiers is None: use_identifiers = dict_.get("use_identifiers", False) instanciated_scans = [] for tomo_obj in objects: if isinstance(tomo_obj, (str, BaseIdentifier)): instanciated_scans.append( factory.create_tomo_object_from_identifier(identifier=tomo_obj) ) else: raise TypeError( f"elements of dict_['objects'] are expected to be an instance of TomoObject, DatasetIdentifier or str representing a DatasetIdentifier. Not {type(tomo_obj)}" ) return Series( name=name, use_identifiers=use_identifiers, iterable=instanciated_scans ) def __contains__(self, tomo_obj: BaseIdentifier): if self.use_identifiers: key = tomo_obj.get_identifier().to_str() else: key = tomo_obj return super().__contains__(key) def __eq__(self, other): if not isinstance(other, Series): return False return self.name == other.name and super().__eq__(other) def __ne__(self, other): return not self.__eq__(other) tomobase-v0.0.0.dev1/tomobase/progress.py0000664000175100017520000000426415237630637021410 0ustar blagandzigblagandzig# coding: utf-8 """Utilities for reporting simple progress indicators to stdout. This module provides the :class:`Progress` helper for displaying a basic ASCII progress bar on the terminal and the internal ``_Advancement`` enum used to render simple spinner-like steps. The implementation is intentionally minimal and dependency-free so it can be used in scripts that don't require a heavy progress library. """ import logging import sys from enum import Enum _logger = logging.getLogger(__name__) __all__ = [ "Progress", ] class _Advancement(Enum): step_1 = "\\" step_2 = "-" step_3 = "/" step_4 = "|" @staticmethod def getNextStep(step): if step is _Advancement.step_1: return _Advancement.step_2 elif step is _Advancement.step_2: return _Advancement.step_3 elif step is _Advancement.step_3: return _Advancement.step_4 else: return _Advancement.step_1 @staticmethod def getStep(value): if value % 4 == 0: return _Advancement.step_4 elif value % 3 == 0: return _Advancement.step_3 elif value % 2 == 0: return _Advancement.step_2 else: return _Advancement.step_1 class Progress(object): """Simple interface for defining advancement on a 100 percentage base""" def __init__(self, name): self._name = name self.reset() def reset(self, max_=None): self._nProcessed = 0 self._maxProcessed = max_ def startProcess(self): self.setAdvancement(0) def setAdvancement(self, value): length = 20 # modify this to change the length block = int(round(length * value / 100)) msg = f"\r{self._name}: [{'#' * block + '-' * (length - block)}] {round(value, 2)}%" if value >= 100: msg += " DONE\r\n" sys.stdout.write(msg) sys.stdout.flush() def endProcess(self): self.setAdvancement(100) def setMaxAdvancement(self, n): self._maxProcessed = n def increaseAdvancement(self, i=1): self._nProcessed += i self.setAdvancement((self._nProcessed / self._maxProcessed) * 100) tomobase-v0.0.0.dev1/tomobase/utils/0000775000175100017520000000000015237630637020324 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/tomobase/utils/io.py0000664000175100017520000001306515237630637021312 0ustar blagandzigblagandzigfrom __future__ import annotations import functools import io import logging import os import traceback from contextlib import contextmanager import numpy from silx.io.utils import open as hdf5_open _logger = logging.getLogger(__name__) depreclog = logging.getLogger("tomoscan.DEPRECATION") deprecache = set([]) def deprecated_warning( type_, name, reason=None, replacement=None, since_version=None, only_once=True, skip_backtrace_count=0, ): """ Function to log a deprecation warning :param type_: Nature of the object to be deprecated: "Module", "Function", "Class" ... :param name: Object name. :param reason: Reason for deprecating this function (e.g. "feature no longer provided", :param replacement: Name of replacement function (if the reason for deprecating was to rename the function) :param since_version: First version for which the function was deprecated (e.g. "0.5.0"). :param only_once: If true, the deprecation warning will only be generated one time for each different call locations. Default is true. :param skip_backtrace_count: Amount of last backtrace to ignore when logging the backtrace """ if not depreclog.isEnabledFor(logging.WARNING): return msg = "%s %s is deprecated" if since_version is not None: msg += " since version %s" % since_version msg += "." if reason is not None: msg += " Reason: %s." % reason if replacement is not None: msg += " Use '%s' instead." % replacement msg += "\n%s" limit = 2 + skip_backtrace_count backtrace = "".join(traceback.format_stack(limit=limit)[0]) backtrace = backtrace.rstrip() if only_once: data = (msg, type_, name, backtrace) if data in deprecache: return else: deprecache.add(data) depreclog.warning(msg, type_, name, backtrace) def deprecated( func=None, reason=None, replacement=None, since_version=None, only_once=True, skip_backtrace_count=1, ): """ Decorator that deprecates the use of a function :param reason: Reason for deprecating this function (e.g. "feature no longer provided", :param replacement: Name of replacement function (if the reason for deprecating was to rename the function) :param since_version: First version for which the function was deprecated (e.g. "0.5.0"). :param only_once: If true, the deprecation warning will only be generated one time. Default is true. :param skip_backtrace_count: Amount of last backtrace to ignore when logging the backtrace """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): deprecated_warning( type_="Function", name=func.__name__, reason=reason, replacement=replacement, since_version=since_version, only_once=only_once, skip_backtrace_count=skip_backtrace_count, ) return func(*args, **kwargs) return wrapper if func is not None: return decorator(func) return decorator def filter_esrf_mounting_points(path: str): """ filter path like '/mnt/multipath-shares' or '/gpfs/easy' """ parts = path.split(os.sep) if len(parts) > 4: if parts[0] == "" and parts[1] in ("gpfs", "mnt"): new_parts = list(parts[0:1]) + list(parts[3:]) path = os.sep.join(new_parts) return path @contextmanager def catch_log_messages(logger_name=depreclog.name, level=logging.WARNING): log_capture = io.StringIO() logger = logging.getLogger(logger_name) handler = logging.StreamHandler(log_capture) handler.setLevel(level) logger.addHandler(handler) try: yield log_capture finally: logger.removeHandler(handler) def check_virtual_sources_exist(fname, data_path): """ Check that a virtual dataset points to actual data. :param fname: HDF5 file path :param data_path: Path within the HDF5 file :return bool res: Whether the virtual dataset points to actual data. """ with hdf5_open(fname) as f: if data_path not in f: _logger.error(f"No dataset {data_path} in file {fname}") return False dptr = f[data_path] if not dptr.is_virtual: return True for vsource in dptr.virtual_sources(): vsource_fname = os.path.join( os.path.dirname(dptr.file.filename), vsource.file_name ) if not os.path.isfile(vsource_fname): _logger.error(f"No such file: {vsource_fname}") return False elif not check_virtual_sources_exist(vsource_fname, vsource.dset_name): _logger.error(f"Error with virtual source {vsource_fname}") return False return True def cast_to_default_types(data: any): """Function that cast non default python types to default python type""" if isinstance(data, dict): return {key: cast_to_default_types(value) for key, value in data.items()} elif isinstance(data, (list, tuple)): return [cast_to_default_types(item) for item in data] elif isinstance(data, (numpy.integer, numpy.int_)): return int(data) elif isinstance(data, numpy.floating): return float(data) elif isinstance(data, numpy.ndarray): return data.tolist() elif isinstance(data, numpy.bool_): return bool(data) else: return data tomobase-v0.0.0.dev1/tomobase/utils/hdf5.py0000664000175100017520000000441515237630637021530 0ustar blagandzigblagandzigimport contextlib from os import path import h5py from silx.io.url import DataUrl from silx.io.utils import open as hdf5_open class _BaseReader(contextlib.AbstractContextManager): def __init__(self, url: DataUrl): if not isinstance(url, DataUrl): raise TypeError(f"url should be an instance of DataUrl. Not {type(url)}") if url.scheme() not in ("silx", "h5py"): raise ValueError("Valid scheme are silx and h5py") if url.data_slice() is not None: raise ValueError( "Data slices are not managed. Data path should " "point to a bliss node (h5py.Group)" ) self._url = url self._file_handler = None def __exit__(self, *exc): return self._file_handler.close() class DatasetReader(_BaseReader): """Context manager used to read a bliss node""" def __enter__(self): self._file_handler = hdf5_open(filename=self._url.file_path()) entry = self._file_handler[self._url.data_path()] if not isinstance(entry, h5py.Dataset): raise ValueError( f"Data path ({self._url.path()}) should point to a dataset (h5py.Dataset)" ) return entry def get_data_sources(fname: str, data_path: str, recursive=True) -> dict: """ Return the nested dict of data_sources. Key are (source) file path and keys are identical dict or data_path to the source :param recursive: If True then go trhough all linked files else won't 'solve' link """ with hdf5_open(fname) as f: if data_path not in f: return None dptr = f[data_path] if not dptr.is_virtual: return {fname: data_path} sources = {} for vsource in dptr.virtual_sources(): vsource_fname = path.normpath( path.join(path.dirname(dptr.file.filename), vsource.file_name) ) if not path.isfile(vsource_fname): sources[vsource_fname] = None if recursive: sources[path.normpath(vsource_fname)] = get_data_sources( vsource_fname, vsource.dset_name ) else: sources[path.normpath(vsource_fname)] = vsource.dset_name return sources tomobase-v0.0.0.dev1/tomobase/utils/decorator.py0000664000175100017520000000214015237630637022655 0ustar blagandzigblagandzigimport functools def _docstring(dest, origin): """Implementation of docstring decorator. It patches dest.__doc__. """ if not isinstance(dest, type) and isinstance(origin, type): # func is not a class, but origin is, get the method with the same name try: origin = getattr(origin, dest.__name__) except AttributeError: raise ValueError(f"origin class has no {dest.__name__} method") dest.__doc__ = origin.__doc__ return dest def docstring(origin): """Decorator to initialize the docstring from another source. This is useful to duplicate a docstring for inheritance and composition. If origin is a method or a function, it copies its docstring. If origin is a class, the docstring is copied from the method of that class which has the same name as the method/function being decorated. :param origin: The method, function or class from which to get the docstring :raises ValueError: If the origin class has not method n case the """ return functools.partial(_docstring, origin=origin) tomobase-v0.0.0.dev1/tomobase/utils/__init__.py0000664000175100017520000000034015237630637022432 0ustar blagandzigblagandzig"""General utils""" from .decorator import docstring # noqa F401 import numpy def is_numpy_scalar_dtype(par): try: numpy.dtype(par) res = True except TypeError: res = False return res tomobase-v0.0.0.dev1/tomobase/utils/url.py0000664000175100017520000000207615237630637021505 0ustar blagandzigblagandzig# coding: utf-8 import numpy class UrlSettings: FILE_PATH_KEY = "file_path" DATA_PATH_KEY = "path" FILE_PREFIX = "file_prefix" def split_query(query: str) -> dict: result = dict() for s in query.split("&"): if not s: continue name, _, value = s.partition("=") prev_value = result.get(name) if prev_value: value = join_string(prev_value, value, "/") result[name] = value return result def join_query( query_items: list[tuple[str, str]] | tuple[tuple[str, str], ...] | numpy.ndarray, ) -> str: return "&".join(f"{k}={v}" for k, v in query_items) def join_string(a: str, b: str, sep: str): aslash = a.endswith(sep) bslash = b.startswith(sep) if aslash and bslash: return a[:-1] + b elif aslash or bslash: return a + b else: return a + sep + b def join_path(path_items: tuple) -> str: if not isinstance(path_items, tuple): raise TypeError return ":".join(path_items) def split_path(path: str): return path.split(":") tomobase-v0.0.0.dev1/tomobase/identifiers/0000775000175100017520000000000015237630637021471 5ustar blagandzigblagandzigtomobase-v0.0.0.dev1/tomobase/identifiers/base.py0000664000175100017520000000203315237630637022753 0ustar blagandzigblagandzig"""Module dedicated to identifiers. Identifiers allow to retrieve an object (scan or volume) from an url - identifier""" class BaseIdentifier: TOMO_TYPE = None def __init__(self, object): self._dataset_builder = object.from_identifier @property def tomo_type(self): return self.TOMO_TYPE def recreate_object(self): """Recreate the dataset from the identifier""" return self._dataset_builder(self) def short_description(self) -> str: """short description of the identifier""" return "" @property def scheme(self) -> str: raise NotImplementedError("Base class") def to_str(self): return str(self) @staticmethod def from_str(identifier): raise NotImplementedError("base class") def __eq__(self, __o: object) -> bool: if isinstance(__o, BaseIdentifier): return __o.to_str() == self.to_str() elif isinstance(__o, str): return __o == self.to_str() else: return False tomobase-v0.0.0.dev1/tomobase/identifiers/__init__.py0000664000175100017520000000050515237630637023602 0ustar blagandzigblagandzig"""Identifier helpers to locate and reconstruct tomo objects. Identifiers are small objects that encode how to re-create a tomographic object (scan or volume). This package exposes base identifier classes and mixins for common identifier types (HDF5, folder-based, etc.). """ from .base import BaseIdentifier # noqa: F401 tomobase-v0.0.0.dev1/tomobase/identifiers/folderidentifiers.py0000664000175100017520000001011615237630637025543 0ustar blagandzigblagandzig# coding: utf-8 import os from urllib.parse import ParseResult, urlparse from tomobase.utils.url import ( UrlSettings, join_path, join_query, split_path, split_query, ) __all__ = ["BaseFolderIdentifierMixIn", "BaseFolderAndfilePrefixIdentifierMixIn"] class BaseFolderIdentifierMixIn: """Identifier specific to a folder. Used for single frame edf and jp2g for example""" def __init__(self, object, folder, tomo_type): super().__init__(object) self._folder = os.path.abspath(os.path.abspath(folder)) self.__tomo_type = tomo_type def short_description(self) -> str: return ParseResult( scheme=self.scheme, path=join_path((self.tomo_type, os.path.basename(self.folder))), query=None, netloc=None, params=None, fragment=None, ).geturl() @property def tomo_type(self): # warning: this property will probably be overwrite return self.__tomo_type @property def folder(self): return self._folder @property def scheme(self) -> str: raise NotImplementedError("base class") def __str__(self): return ParseResult( scheme=self.scheme, path=join_path((self.tomo_type, self.folder)), query=None, netloc=None, params=None, fragment=None, ).geturl() def __eq__(self, other): if isinstance(other, BaseFolderIdentifierMixIn): return self.folder == other.folder and self.tomo_type == other.tomo_type else: return super().__eq__(other) def __hash__(self): return hash(self.folder) class BaseFolderAndfilePrefixIdentifierMixIn(BaseFolderIdentifierMixIn): def __init__(self, object, folder, file_prefix, tomo_type): super().__init__(object, folder, tomo_type) self._file_prefix = file_prefix def short_description(self) -> str: query = [] if self.file_prefix not in (None, ""): query.append( ("file_prefix", self.file_prefix), ) return ParseResult( scheme=self.scheme, path=join_path((self.tomo_type, self.folder)), query=join_query(query), netloc=None, params=None, fragment=None, ).geturl() @property def file_prefix(self) -> str: return self._file_prefix def __str__(self): query = [] if self.file_prefix not in (None, ""): query.append( ("file_prefix", self.file_prefix), ) return ParseResult( scheme=self.scheme, path=join_path((self.tomo_type, self.folder)), query=join_query(query), netloc=None, params=None, fragment=None, ).geturl() def __eq__(self, other): return super().__eq__(other) and self.file_prefix == other.file_prefix def __hash__(self): return hash(self.folder) + hash(self.file_prefix) @staticmethod def _from_str_to_single_frame_identifier( identifier: str, SingleFrameIdentifierClass, ObjClass: type ): """ Common function to build an identifier from a str. Might be moved to the factory directly one day ? """ info = urlparse(identifier) paths = split_path(info.path) if len(paths) == 1: jp2k_folder = paths[0] tomo_type = None elif len(paths) == 2: tomo_type, jp2k_folder = paths else: raise ValueError("Failed to parse path string:", info.path) if tomo_type is not None and tomo_type != SingleFrameIdentifierClass.TOMO_TYPE: raise TypeError( f"provided identifier fits {tomo_type} and not {SingleFrameIdentifierClass.TOMO_TYPE}" ) queries = split_query(info.query) file_prefix = queries.get(UrlSettings.FILE_PREFIX, None) return SingleFrameIdentifierClass( object=ObjClass, folder=jp2k_folder, file_prefix=file_prefix ) tomobase-v0.0.0.dev1/tomobase/identifiers/hdf5identifier.py0000664000175100017520000000437215237630637024742 0ustar blagandzigblagandzig# coding: utf-8 import os from urllib.parse import ParseResult from tomobase.identifiers.base import BaseIdentifier from tomobase.utils import docstring from tomobase.utils.url import UrlSettings, join_path, join_query __all__ = ["HDF5IdentifierMixIn"] class HDF5IdentifierMixIn: """Shared logic for identifiers pointing to an entry inside an HDF5 file. Used by both scan identifiers (e.g. tomoscan's NXtomoScanIdentifier) and volume identifiers (e.g. volio's HDF5VolumeIdentifier). """ def __init__(self, object, hdf5_file, entry, tomo_type): super().__init__(object) self._file_path = os.path.abspath(os.path.abspath(hdf5_file)) self._data_path = entry self._tomo_type = tomo_type @property def tomo_type(self): return self._tomo_type @docstring(BaseIdentifier) def short_description(self) -> str: return ParseResult( scheme=self.scheme, path=join_path( (self.tomo_type, os.path.basename(self._file_path)), ), query=join_query( ((UrlSettings.DATA_PATH_KEY, self.data_path),), ), netloc=None, params=None, fragment=None, ).geturl() @property def file_path(self): return self._file_path @property def data_path(self): return self._data_path @property @docstring(BaseIdentifier) def scheme(self) -> str: return "hdf5" def __str__(self): return ParseResult( scheme=self.scheme, path=join_path( (self.tomo_type, self._file_path), ), query=join_query( ((UrlSettings.DATA_PATH_KEY, self.data_path),), ), netloc=None, params=None, fragment=None, ).geturl() def __eq__(self, other): if isinstance(other, HDF5IdentifierMixIn): return ( self.tomo_type == other.tomo_type and self._file_path == other._file_path and self._data_path == other._data_path ) else: return super().__eq__(other) def __hash__(self): return hash((self._file_path, self._data_path)) tomobase-v0.0.0.dev1/tomobase/tomoobject.py0000664000175100017520000000316615237630637021711 0ustar blagandzigblagandzig# coding: utf-8 """Base interfaces for tomographic objects. This module defines :class:`TomoObject`, the abstract base interface for objects representing tomographic data (scans or volumes). Concrete implementations should provide methods to build identifiers, obtain bounding boxes and to materialise objects from identifiers. """ from __future__ import annotations from tomobase.geometry import BoundingBox1D from .identifiers import BaseIdentifier __all__ = [ "TomoObject", ] class TomoObject: """Parent class of all tomographic object in tomoscan""" @staticmethod def from_identifier(identifier: str | BaseIdentifier): """Return the Dataset from a identifier""" raise NotImplementedError("Base class") def get_identifier(self) -> BaseIdentifier: """dataset unique identifier. Can be for example a hdf5 and en entry from which the dataset can be rebuild""" raise NotImplementedError("Base class") def get_bounding_box(self, axis: str | int | None = None) -> BoundingBox1D: """ Return the bounding box covered by the Tomo object axis is expected to be in (0, 1, 2) or (x==0, y==1, z==2) """ raise NotImplementedError("Base class") def build_drac_metadata(self) -> dict: """ build icat metadata dictionary filling NXtomo definition following icat definition """ raise NotImplementedError() def clear_cache(self) -> None: """ A TomoObj can have some values store in the cache (some metadata read for example). This function should clear all caches. """ pass tomobase-v0.0.0.dev1/tomobase/__init__.py0000664000175100017520000000070515237630637021277 0ustar blagandzigblagandzig"""Top-level package for tomobase. This package provides lightweight helpers and base classes used by tomography-related tools: identifiers for locating datasets, small geometry utilities (bounding boxes and shapes), and base interfaces for tomographic objects and series collections. Keep this module minimal: the public API lives in the submodules. """ from importlib.metadata import version as _get_version __version__ = _get_version("tomobase") tomobase-v0.0.0.dev1/tomobase/source.py0000664000175100017520000000373215237630637021043 0ustar blagandzigblagandzig"""Information regarding beamline or radiation sources used for scans. This module defines the :class:`SourceType` enumeration describing common beamline and source types (synchrotron, reactor, laser, etc.) and the :class:`Source` helper that stores a source `name` and `type`. """ from __future__ import annotations from enum import Enum class SourceType(Enum): SPALLATION_NEUTRON = "Spallation Neutron Source" PULSED_REACTOR_NEUTRON_SOURCE = "Pulsed Reactor Neutron Source" REACTOR_NEUTRON_SOURCE = "Reactor Neutron Source" SYNCHROTRON_X_RAY_SOURCE = "Synchrotron X-ray Source" PULSED_MUON_SOURCE = "Pulsed Muon Source" ROTATING_ANODE_X_RAY = "Rotating Anode X-ray" FIXED_TUBE_X_RAY = "Fixed Tube X-ray" UV_LASER = "UV Laser" FREE_ELECTRON_LASER = "Free-Electron Laser" OPTICAL_LASER = "Optical Laser" ION_SOURCE = "Ion Source" UV_PLASMA_SOURCE = "UV Plasma Source" METAL_JET_X_RAY = "Metal Jet X-ray" class Source: """Small container for metadata about the experimental source. The :class:`Source` object stores a human-readable `name` and a `type` drawn from :class:`SourceType`. It is intentionally lightweight and used as metadata attached to higher-level objects. """ def __init__(self, name=None, type=None): self._name = name self._type = type @property def name(self) -> str | None: return self._name @name.setter def name(self, name: str | None): if not isinstance(name, (str, type(None))): raise TypeError("name is expected to be None or a str") self._name = name @property def type(self) -> SourceType | None: return self._type @type.setter def type(self, type_: str | SourceType | None): if type_ is None: self._type = None else: type_ = SourceType(type_) self._type = type_ def __str__(self): return f"source (name: {self.name}, type: {self.type})" tomobase-v0.0.0.dev1/tomobase/coordinatesystem.py0000664000175100017520000000102715237630637023132 0ustar blagandzigblagandzigfrom enum import Enum class CoordinateSystem(Enum): """ * ESRF coordinate system:: Z axis ^ Y axis | / x-ray |/ --------> ------> X axis * McStas coordinate system:: Y axis ^ X axis | / x-ray | / --------> ------> Z axis """ McStas = "McStas" ESRF = "ESRF" tomobase-v0.0.0.dev1/CHANGELOG.md0000664000175100017520000000154715237630637017173 0ustar blagandzigblagandzig# Changelog All notable changes to this project will be documented in this file. The format follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) conventions and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). ## [0.0.0dev0] - Unreleased ### Added - Initial extraction of `tomobase` from `tomoscan` (2.5.1): common base classes and utilities shared by `tomoscan` (>=3.0.0) and the new `volio` package. - `TomoObject`, `BaseIdentifier`, `BaseFolderIdentifierMixIn`/`BaseFolderAndfilePrefixIdentifierMixIn`, `HDF5IdentifierMixIn`. - `Series`, `Source`, `Progress`, `CoordinateSystem`. - `utils.io` (`deprecated`, `deprecated_warning`, `filter_esrf_mounting_points`), `utils.geometry` (`BoundingBox1D`, `BoundingBox3D`), `utils.decorator` (`docstring`), `utils.hdf5` (`DatasetReader`, `get_data_sources`).